Project

General

Profile

1 6179 cjones
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000-2011 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author:  $'
7
 *     '$Date:  $'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23
24
package edu.ucsb.nceas.metacat.dataone;
25
26 7417 leinfelder
import java.io.ByteArrayInputStream;
27 7860 leinfelder
import java.io.ByteArrayOutputStream;
28 7850 leinfelder
import java.io.File;
29 8800 leinfelder
import java.io.FileInputStream;
30 7850 leinfelder
import java.io.FileOutputStream;
31 6228 cjones
import java.io.IOException;
32 6179 cjones
import java.io.InputStream;
33 8190 leinfelder
import java.io.InputStreamReader;
34 7860 leinfelder
import java.io.OutputStreamWriter;
35 9387 walker
import java.io.StringReader;
36 7849 leinfelder
import java.io.UnsupportedEncodingException;
37 7860 leinfelder
import java.io.Writer;
38 7021 leinfelder
import java.math.BigInteger;
39 7849 leinfelder
import java.net.URISyntaxException;
40 9352 walker
import java.nio.charset.Charset;
41 6228 cjones
import java.security.NoSuchAlgorithmException;
42 7860 leinfelder
import java.sql.SQLException;
43 7417 leinfelder
import java.util.ArrayList;
44 6525 leinfelder
import java.util.Calendar;
45 6179 cjones
import java.util.Date;
46 8437 walker
import java.util.HashMap;
47 7680 tao
import java.util.HashSet;
48 7860 leinfelder
import java.util.Hashtable;
49 6250 cjones
import java.util.List;
50 7849 leinfelder
import java.util.Map;
51 7417 leinfelder
import java.util.Set;
52 6389 leinfelder
import java.util.Timer;
53 7489 leinfelder
import java.util.UUID;
54 7418 leinfelder
import java.util.Vector;
55 6179 cjones
56 6542 leinfelder
import javax.servlet.http.HttpServletRequest;
57 9387 walker
import javax.xml.transform.Result;
58
import javax.xml.transform.Source;
59
import javax.xml.transform.TransformerException;
60
import javax.xml.transform.TransformerFactory;
61
import javax.xml.transform.dom.DOMSource;
62
import javax.xml.transform.stream.StreamResult;
63 6542 leinfelder
64 6258 cjones
import org.apache.commons.io.IOUtils;
65 6179 cjones
import org.apache.log4j.Logger;
66 8810 leinfelder
import org.dataone.client.v2.CNode;
67
import org.dataone.client.v2.itk.D1Client;
68
import org.dataone.client.v2.MNode;
69
import org.dataone.client.v2.formats.ObjectFormatCache;
70 6552 leinfelder
import org.dataone.client.auth.CertificateManager;
71 8810 leinfelder
import org.dataone.client.v2.formats.ObjectFormatInfo;
72 6552 leinfelder
import org.dataone.configuration.Settings;
73 7849 leinfelder
import org.dataone.ore.ResourceMapFactory;
74 6795 cjones
import org.dataone.service.exceptions.BaseException;
75 6179 cjones
import org.dataone.service.exceptions.IdentifierNotUnique;
76
import org.dataone.service.exceptions.InsufficientResources;
77
import org.dataone.service.exceptions.InvalidRequest;
78
import org.dataone.service.exceptions.InvalidSystemMetadata;
79
import org.dataone.service.exceptions.InvalidToken;
80
import org.dataone.service.exceptions.NotAuthorized;
81
import org.dataone.service.exceptions.NotFound;
82
import org.dataone.service.exceptions.NotImplemented;
83
import org.dataone.service.exceptions.ServiceFailure;
84 6185 leinfelder
import org.dataone.service.exceptions.SynchronizationFailed;
85 6179 cjones
import org.dataone.service.exceptions.UnsupportedType;
86 8810 leinfelder
import org.dataone.service.mn.tier1.v2.MNCore;
87
import org.dataone.service.mn.tier1.v2.MNRead;
88
import org.dataone.service.mn.tier2.v2.MNAuthorization;
89
import org.dataone.service.mn.tier3.v2.MNStorage;
90
import org.dataone.service.mn.tier4.v2.MNReplication;
91
import org.dataone.service.mn.v2.MNPackage;
92
import org.dataone.service.mn.v2.MNQuery;
93
import org.dataone.service.mn.v2.MNView;
94 8591 leinfelder
import org.dataone.service.types.v1.AccessRule;
95 6366 leinfelder
import org.dataone.service.types.v1.Checksum;
96 7144 leinfelder
import org.dataone.service.types.v1.DescribeResponse;
97 6366 leinfelder
import org.dataone.service.types.v1.Event;
98
import org.dataone.service.types.v1.Identifier;
99 8810 leinfelder
import org.dataone.service.types.v2.Log;
100
import org.dataone.service.types.v2.LogEntry;
101
import org.dataone.service.types.v2.OptionList;
102 6366 leinfelder
import org.dataone.service.types.v1.MonitorInfo;
103
import org.dataone.service.types.v1.MonitorList;
104 8810 leinfelder
import org.dataone.service.types.v2.Node;
105
import org.dataone.service.types.v2.NodeList;
106 6366 leinfelder
import org.dataone.service.types.v1.NodeReference;
107
import org.dataone.service.types.v1.NodeState;
108
import org.dataone.service.types.v1.NodeType;
109 8810 leinfelder
import org.dataone.service.types.v2.ObjectFormat;
110 9353 tao
import org.dataone.service.types.v1.Group;
111 6366 leinfelder
import org.dataone.service.types.v1.ObjectFormatIdentifier;
112
import org.dataone.service.types.v1.ObjectList;
113
import org.dataone.service.types.v1.Permission;
114
import org.dataone.service.types.v1.Ping;
115 9340 tao
import org.dataone.service.types.v1.Replica;
116 6528 cjones
import org.dataone.service.types.v1.ReplicationStatus;
117 6366 leinfelder
import org.dataone.service.types.v1.Schedule;
118
import org.dataone.service.types.v1.Service;
119
import org.dataone.service.types.v1.Services;
120
import org.dataone.service.types.v1.Session;
121
import org.dataone.service.types.v1.Subject;
122
import org.dataone.service.types.v1.Synchronization;
123 8810 leinfelder
import org.dataone.service.types.v2.SystemMetadata;
124 7417 leinfelder
import org.dataone.service.types.v1.util.AuthUtils;
125 6366 leinfelder
import org.dataone.service.types.v1.util.ChecksumUtil;
126 7417 leinfelder
import org.dataone.service.types.v1_1.QueryEngineDescription;
127
import org.dataone.service.types.v1_1.QueryEngineList;
128 7418 leinfelder
import org.dataone.service.types.v1_1.QueryField;
129 6476 jones
import org.dataone.service.util.Constants;
130 8594 leinfelder
import org.dataone.service.util.TypeMarshaller;
131 7849 leinfelder
import org.dspace.foresite.OREException;
132
import org.dspace.foresite.OREParserException;
133
import org.dspace.foresite.ORESerialiserException;
134
import org.dspace.foresite.ResourceMap;
135 8437 walker
import org.ecoinformatics.datamanager.parser.DataPackage;
136
import org.ecoinformatics.datamanager.parser.Entity;
137
import org.ecoinformatics.datamanager.parser.generic.DataPackageParserInterface;
138
import org.ecoinformatics.datamanager.parser.generic.Eml200DataPackageParser;
139 9387 walker
import org.w3c.dom.Document;
140 6179 cjones
141 7448 leinfelder
import edu.ucsb.nceas.ezid.EZIDException;
142 7417 leinfelder
import edu.ucsb.nceas.metacat.DBQuery;
143 7860 leinfelder
import edu.ucsb.nceas.metacat.DBTransform;
144 6234 cjones
import edu.ucsb.nceas.metacat.EventLog;
145 6230 cjones
import edu.ucsb.nceas.metacat.IdentifierManager;
146 6234 cjones
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
147 7417 leinfelder
import edu.ucsb.nceas.metacat.MetaCatServlet;
148 6389 leinfelder
import edu.ucsb.nceas.metacat.MetacatHandler;
149 9478 tao
import edu.ucsb.nceas.metacat.ReadOnlyChecker;
150 7772 tao
import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines;
151 7757 leinfelder
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
152 6648 leinfelder
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
153 7662 tao
import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler;
154 7620 tao
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
155 6340 cjones
import edu.ucsb.nceas.metacat.properties.PropertyService;
156 7418 leinfelder
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
157 8026 leinfelder
import edu.ucsb.nceas.metacat.util.DeleteOnCloseFileInputStream;
158 7441 leinfelder
import edu.ucsb.nceas.metacat.util.DocumentUtil;
159 8810 leinfelder
import edu.ucsb.nceas.metacat.util.SkinUtil;
160 6542 leinfelder
import edu.ucsb.nceas.metacat.util.SystemUtil;
161 6340 cjones
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
162 8190 leinfelder
import edu.ucsb.nceas.utilities.XMLUtilities;
163 8800 leinfelder
import edu.ucsb.nceas.utilities.export.HtmlToPdf;
164 7850 leinfelder
import gov.loc.repository.bagit.Bag;
165
import gov.loc.repository.bagit.BagFactory;
166
import gov.loc.repository.bagit.writer.impl.ZipWriter;
167 6230 cjones
168 6179 cjones
/**
169
 * Represents Metacat's implementation of the DataONE Member Node
170
 * service API. Methods implement the various MN* interfaces, and methods common
171
 * to both Member Node and Coordinating Node interfaces are found in the
172
 * D1NodeService base class.
173 6288 cjones
 *
174
 * Implements:
175
 * MNCore.ping()
176
 * MNCore.getLogRecords()
177
 * MNCore.getObjectStatistics()
178
 * MNCore.getOperationStatistics()
179
 * MNCore.getStatus()
180
 * MNCore.getCapabilities()
181
 * MNRead.get()
182
 * MNRead.getSystemMetadata()
183
 * MNRead.describe()
184
 * MNRead.getChecksum()
185
 * MNRead.listObjects()
186
 * MNRead.synchronizationFailed()
187
 * MNAuthorization.isAuthorized()
188
 * MNAuthorization.setAccessPolicy()
189
 * MNStorage.create()
190
 * MNStorage.update()
191
 * MNStorage.delete()
192 9177 tao
 * MNStorage.updateSystemMetadata()
193 6288 cjones
 * MNReplication.replicate()
194
 *
195 6179 cjones
 */
196 6599 cjones
public class MNodeService extends D1NodeService
197 8810 leinfelder
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage, MNQuery, MNView, MNPackage {
198 6179 cjones
199 7772 tao
    //private static final String PATHQUERY = "pathquery";
200 7849 leinfelder
	public static final String UUID_SCHEME = "UUID";
201
	public static final String DOI_SCHEME = "DOI";
202 7489 leinfelder
	private static final String UUID_PREFIX = "urn:uuid:";
203 9387 walker
204
	private static String XPATH_EML_ID = "/eml:eml/@packageId";
205 7418 leinfelder
206
	/* the logger instance */
207 6475 jones
    private Logger logMetacat = null;
208 6795 cjones
209
    /* A reference to a remote Memeber Node */
210 9295 tao
    //private MNode mn;
211 6795 cjones
212
    /* A reference to a Coordinating Node */
213
    private CNode cn;
214 6241 cjones
215 6795 cjones
216 6475 jones
    /**
217
     * Singleton accessor to get an instance of MNodeService.
218
     *
219
     * @return instance - the instance of MNodeService
220
     */
221 6542 leinfelder
    public static MNodeService getInstance(HttpServletRequest request) {
222
        return new MNodeService(request);
223 6179 cjones
    }
224
225 6475 jones
    /**
226
     * Constructor, private for singleton access
227
     */
228 6542 leinfelder
    private MNodeService(HttpServletRequest request) {
229
        super(request);
230 6475 jones
        logMetacat = Logger.getLogger(MNodeService.class);
231 6552 leinfelder
232
        // set the Member Node certificate file location
233
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
234 6310 cjones
    }
235 6475 jones
236
    /**
237
     * Deletes an object from the Member Node, where the object is either a
238
     * data object or a science metadata object.
239
     *
240
     * @param session - the Session object containing the credentials for the Subject
241
     * @param pid - The object identifier to be deleted
242
     *
243
     * @return pid - the identifier of the object used for the deletion
244
     *
245
     * @throws InvalidToken
246
     * @throws ServiceFailure
247
     * @throws NotAuthorized
248
     * @throws NotFound
249
     * @throws NotImplemented
250
     * @throws InvalidRequest
251
     */
252
    @Override
253
    public Identifier delete(Session session, Identifier pid)
254 6610 cjones
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
255 6475 jones
256 9478 tao
        if(isReadOnlyMode()) {
257 9481 tao
            throw new ServiceFailure("2902", ReadOnlyChecker.DATAONEERROR);
258 9478 tao
        }
259 7162 leinfelder
    	// only admin of  the MN or the CN is allowed a full delete
260
        boolean allowed = false;
261 7330 leinfelder
        allowed = isAdminAuthorized(session);
262 8360 tao
263 9050 tao
        String serviceFailureCode = "2902";
264
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
265
        if(sid != null) {
266
            pid = sid;
267
        }
268
269 8360 tao
        //check if it is the authoritative member node
270
        if(!allowed) {
271
            allowed = isAuthoritativeMNodeAdmin(session, pid);
272
        }
273
274 7162 leinfelder
        if (!allowed) {
275 7245 cjones
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
276 7162 leinfelder
        }
277
278 7077 leinfelder
    	// defer to superclass implementation
279
        return super.delete(session, pid);
280 6250 cjones
    }
281
282 6475 jones
    /**
283
     * Updates an existing object by creating a new object identified by
284
     * newPid on the Member Node which explicitly obsoletes the object
285
     * identified by pid through appropriate changes to the SystemMetadata
286
     * of pid and newPid
287
     *
288
     * @param session - the Session object containing the credentials for the Subject
289
     * @param pid - The identifier of the object to be updated
290
     * @param object - the new object bytes
291
     * @param sysmeta - the new system metadata describing the object
292
     *
293
     * @return newPid - the identifier of the new object
294
     *
295
     * @throws InvalidToken
296
     * @throws ServiceFailure
297
     * @throws NotAuthorized
298
     * @throws NotFound
299
     * @throws NotImplemented
300
     * @throws IdentifierNotUnique
301
     * @throws UnsupportedType
302
     * @throws InsufficientResources
303
     * @throws InvalidSystemMetadata
304
     * @throws InvalidRequest
305
     */
306
    @Override
307 6575 cjones
    public Identifier update(Session session, Identifier pid, InputStream object,
308
        Identifier newPid, SystemMetadata sysmeta)
309
        throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique,
310
        UnsupportedType, InsufficientResources, NotFound,
311
        InvalidSystemMetadata, NotImplemented, InvalidRequest {
312 9600 tao
        try {
313 9601 tao
            if(isReadOnlyMode()) {
314
                throw new ServiceFailure("1310", ReadOnlyChecker.DATAONEERROR);
315
            }
316
317
            //transform a sid to a pid if it is applicable
318
            String serviceFailureCode = "1310";
319
            Identifier sid = getPIDForSID(pid, serviceFailureCode);
320
            if(sid != null) {
321
                pid = sid;
322
            }
323 9044 tao
324 9601 tao
            String localId = null;
325
            boolean allowed = false;
326
            boolean isScienceMetadata = false;
327 6575 cjones
328 9601 tao
            if (session == null) {
329
            	throw new InvalidToken("1210", "No session has been provided");
330
            }
331
            Subject subject = session.getSubject();
332
333
            // verify the pid is valid format
334
            if (!isValidIdentifier(pid)) {
335
            	throw new InvalidRequest("1202", "The provided identifier is invalid.");
336
            }
337 6575 cjones
338 9601 tao
            // verify the new pid is valid format
339
            if (!isValidIdentifier(newPid)) {
340
                throw new InvalidRequest("1202", "The provided identifier is invalid.");
341 9200 tao
            }
342 9601 tao
343
            // make sure that the newPid doesn't exists
344
            boolean idExists = true;
345
            try {
346
                idExists = IdentifierManager.getInstance().identifierExists(newPid.getValue());
347
            } catch (SQLException e) {
348
                throw new ServiceFailure("1310",
349
                                        "The requested identifier " + newPid.getValue() +
350
                                        " couldn't be determined if it is unique since : "+e.getMessage());
351 7400 leinfelder
            }
352 9601 tao
            if (idExists) {
353
                    throw new IdentifierNotUnique("1220",
354
                              "The requested identifier " + newPid.getValue() +
355
                              " is already used by another object and" +
356
                              "therefore can not be used for this object. Clients should choose" +
357
                              "a new identifier that is unique and retry the operation or " +
358
                              "use CN.reserveIdentifier() to reserve one.");
359
360
            }
361
362
363
364
            // check for the existing identifier
365
            try {
366
                localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
367
368
            } catch (McdbDocNotFoundException e) {
369
                throw new InvalidRequest("1202", "The object with the provided " +
370
                    "identifier was not found.");
371
372
            } catch (SQLException ee) {
373
                throw new ServiceFailure("1310", "The object with the provided " +
374
                        "identifier "+pid.getValue()+" can't be identified since - "+ee.getMessage());
375
            }
376
377
            // set the originating node
378
            NodeReference originMemberNode = this.getCapabilities().getIdentifier();
379
            sysmeta.setOriginMemberNode(originMemberNode);
380
381
            // set the submitter to match the certificate
382
            sysmeta.setSubmitter(subject);
383
            // set the dates
384
            Date now = Calendar.getInstance().getTime();
385
            sysmeta.setDateSysMetadataModified(now);
386
            sysmeta.setDateUploaded(now);
387
388
            // make sure serial version is set to something
389
            BigInteger serialVersion = sysmeta.getSerialVersion();
390
            if (serialVersion == null) {
391
            	sysmeta.setSerialVersion(BigInteger.ZERO);
392
            }
393
394
            // does the subject have WRITE ( == update) priveleges on the pid?
395
            //allowed = isAuthorized(session, pid, Permission.WRITE);
396
            //CN having the permission is allowed; user with the write permission and calling on the authoritative node is allowed.
397
            allowed = allowUpdating(session, pid, Permission.WRITE);
398
            if (allowed) {
399
400
            	// check quality of SM
401
            	if (sysmeta.getObsoletedBy() != null) {
402
            		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
403
            	}
404
            	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
405
            		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
406
            	}
407
408
                // get the existing system metadata for the object
409
                SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
410
                //System.out.println("the archive is "+existingSysMeta.getArchived());
411
                //Base on documentation, we can't update an archived object:
412
                //The update operation MUST fail with Exceptions.InvalidRequest on objects that have the Types.SystemMetadata.archived property set to true.
413
                if(existingSysMeta.getArchived() != null && existingSysMeta.getArchived()) {
414
                    throw new InvalidRequest("1202","An archived object"+pid.getValue()+" can't be updated");
415 9046 tao
                }
416 9601 tao
417
                // check for previous update
418
                // see: https://redmine.dataone.org/issues/3336
419
                Identifier existingObsoletedBy = existingSysMeta.getObsoletedBy();
420
                if (existingObsoletedBy != null) {
421
                	throw new InvalidRequest("1202",
422
                			"The previous identifier has already been made obsolete by: " + existingObsoletedBy.getValue());
423
                }
424
                //check the sid in the system metadata. If it exists, it should be non-exist or match the old sid in the previous system metadata.
425
                Identifier sidInSys = sysmeta.getSeriesId();
426
                if(sidInSys != null) {
427
                    if (!isValidIdentifier(sidInSys)) {
428
                        throw new InvalidSystemMetadata("1300", "The provided series id in the system metadata is invalid.");
429
                    }
430
                    Identifier previousSid = existingSysMeta.getSeriesId();
431
                    if(previousSid != null) {
432
                        // there is a previous sid, if the new sid doesn't match it, the new sid should be non-existing.
433
                        if(!sidInSys.getValue().equals(previousSid.getValue())) {
434
                            try {
435
                                idExists = IdentifierManager.getInstance().identifierExists(sidInSys.getValue());
436
                            } catch (SQLException e) {
437
                                throw new ServiceFailure("1310",
438
                                                        "The requested identifier " + sidInSys.getValue() +
439
                                                        " couldn't be determined if it is unique since : "+e.getMessage());
440
                            }
441
                            if(idExists) {
442
                                throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata doesn't match the previous series id "
443
                                                                +previousSid.getValue()+", so it should NOT exist. However, it was used by another object.");
444
                            }
445
                        }
446
                    } else {
447
                        // there is no previous sid, the new sid should be non-existing.
448 9044 tao
                        try {
449
                            idExists = IdentifierManager.getInstance().identifierExists(sidInSys.getValue());
450
                        } catch (SQLException e) {
451
                            throw new ServiceFailure("1310",
452
                                                    "The requested identifier " + sidInSys.getValue() +
453
                                                    " couldn't be determined if it is unique since : "+e.getMessage());
454
                        }
455
                        if(idExists) {
456 9601 tao
                            throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata should NOT exist since the previous series id is null."
457
                                                            +"However, it was used by another object.");
458 9044 tao
                        }
459
                    }
460 9601 tao
                    //the series id equals the pid (new pid hasn't been registered in the system, so IdentifierManager.getInstance().identifierExists method can't exclude this scenario)
461
                    if(sidInSys.getValue().equals(newPid.getValue())) {
462
                        throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata shouldn't have the same value of the pid.");
463
                    }
464
                }
465
466
                isScienceMetadata = isScienceMetadata(sysmeta);
467
468
                // do we have XML metadata or a data object?
469
                if (isScienceMetadata) {
470
471
                    // update the science metadata XML document
472
                    // TODO: handle non-XML metadata/data documents (like netCDF)
473
                    // TODO: don't put objects into memory using stream to string
474
                    //String objectAsXML = "";
475 9044 tao
                    try {
476 9601 tao
                        //objectAsXML = IOUtils.toString(object, "UTF-8");
477
                    	String formatId = null;
478
                    	if(sysmeta.getFormatId() != null) {
479
                    	    formatId = sysmeta.getFormatId().getValue();
480
                    	}
481
                        localId = insertOrUpdateDocument(object, "UTF-8", pid, session, "update", formatId);
482
483
                        // register the newPid and the generated localId
484
                        if (newPid != null) {
485
                            IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
486
                        }
487
488
                    } catch (IOException e) {
489
                        String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
490
                        logMetacat.info(msg);
491
                        throw new ServiceFailure("1310", msg + ": " + e.getMessage());
492
493 9044 tao
                    }
494 9601 tao
495
                } else {
496
497
                    // update the data object
498
                    localId = insertDataObject(object, newPid, session);
499
500 9044 tao
                }
501 9601 tao
502
                // add the newPid to the obsoletedBy list for the existing sysmeta
503
                existingSysMeta.setObsoletedBy(newPid);
504
                //increase version
505
                BigInteger current = existingSysMeta.getSerialVersion();
506
                //System.out.println("the current version is "+current);
507
                current = current.add(BigInteger.ONE);
508
                //System.out.println("the new current version is "+current);
509
                existingSysMeta.setSerialVersion(current);
510
                // then update the existing system metadata
511
                updateSystemMetadata(existingSysMeta);
512
513
                // prep the new system metadata, add pid to the affected lists
514
                sysmeta.setObsoletes(pid);
515
                //sysmeta.addDerivedFrom(pid);
516
517
                // and insert the new system metadata
518
                insertSystemMetadata(sysmeta);
519
520
                // log the update event
521
                EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
522
523
                // attempt to register the identifier - it checks if it is a doi
524 6475 jones
                try {
525 9601 tao
        			DOIService.getInstance().registerDOI(sysmeta);
526
        		} catch (Exception e) {
527
                    throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
528
        		}
529
530 6475 jones
            } else {
531 9601 tao
                throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
532
                        + " on the Member Node.");
533 6475 jones
            }
534 9600 tao
        } finally {
535
            IOUtils.closeQuietly(object);
536
        }
537 6475 jones
        return newPid;
538 6250 cjones
    }
539 6254 cjones
540 6475 jones
    public Identifier create(Session session, Identifier pid, InputStream object, SystemMetadata sysmeta) throws InvalidToken, ServiceFailure, NotAuthorized,
541
            IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, InvalidRequest {
542 9600 tao
        Identifier resultPid = null;
543
        try {
544 9601 tao
            if(isReadOnlyMode()) {
545
                throw new ServiceFailure("1190", ReadOnlyChecker.DATAONEERROR);
546 9068 tao
            }
547 9601 tao
            // check for null session
548
            if (session == null) {
549
              throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
550 9068 tao
            }
551 9601 tao
            // verify the pid is valid format
552
            if (!isValidIdentifier(pid)) {
553
                throw new InvalidRequest("1102", "The provided identifier is invalid.");
554 9068 tao
            }
555 9601 tao
            // set the submitter to match the certificate
556
            sysmeta.setSubmitter(session.getSubject());
557
            // set the originating node
558
            NodeReference originMemberNode = this.getCapabilities().getIdentifier();
559
            sysmeta.setOriginMemberNode(originMemberNode);
560
561
            // if no authoritative MN, set it to the same
562
            if (sysmeta.getAuthoritativeMemberNode() == null) {
563
            	sysmeta.setAuthoritativeMemberNode(originMemberNode);
564 9068 tao
            }
565 9601 tao
566
            sysmeta.setArchived(false);
567
568
            // set the dates
569
            Date now = Calendar.getInstance().getTime();
570
            sysmeta.setDateSysMetadataModified(now);
571
            sysmeta.setDateUploaded(now);
572
573
            // set the serial version
574
            sysmeta.setSerialVersion(BigInteger.ZERO);
575
576
            // check that we are not attempting to subvert versioning
577
            if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
578
                throw new InvalidSystemMetadata("1180",
579
                  "The supplied system metadata is invalid. " +
580
                  "The obsoletes field cannot have a value when creating entries.");
581
            }
582
583
            if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
584
                throw new InvalidSystemMetadata("1180",
585
                  "The supplied system metadata is invalid. " +
586
                  "The obsoletedBy field cannot have a value when creating entries.");
587
            }
588
589
            // verify the sid in the system metadata
590
            Identifier sid = sysmeta.getSeriesId();
591
            boolean idExists = false;
592
            if(sid != null) {
593
                if (!isValidIdentifier(sid)) {
594
                    throw new InvalidSystemMetadata("1180", "The provided series id is invalid.");
595
                }
596
                try {
597
                    idExists = IdentifierManager.getInstance().identifierExists(sid.getValue());
598
                } catch (SQLException e) {
599
                    throw new ServiceFailure("1190",
600
                                            "The series identifier " + sid.getValue() +
601
                                            " in the system metadata couldn't be determined if it is unique since : "+e.getMessage());
602
                }
603
                if (idExists) {
604
                        throw new InvalidSystemMetadata("1180",
605
                                  "The series identifier " + sid.getValue() +
606
                                  " is already used by another object and" +
607
                                  "therefore can not be used for this object. Clients should choose" +
608
                                  "a new identifier that is unique and retry the operation or " +
609
                                  "use CN.reserveIdentifier() to reserve one.");
610
611
                }
612
                //the series id equals the pid (new pid hasn't been registered in the system, so IdentifierManager.getInstance().identifierExists method can't exclude this scenario )
613
                if(sid.getValue().equals(pid.getValue())) {
614
                    throw new InvalidSystemMetadata("1180", "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
615
                }
616
            }
617
618
            // call the shared impl
619
            resultPid = super.create(session, pid, object, sysmeta);
620
621
            // attempt to register the identifier - it checks if it is a doi
622
            try {
623
    			DOIService.getInstance().registerDOI(sysmeta);
624
    		} catch (Exception e) {
625
    			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
626
    			sf.initCause(e);
627
                throw sf;
628
    		}
629 9600 tao
        } finally {
630
            IOUtils.closeQuietly(object);
631
        }
632 7507 leinfelder
        // return
633
		return resultPid ;
634 6475 jones
    }
635 6250 cjones
636 6475 jones
    /**
637
     * Called by a Coordinating Node to request that the Member Node create a
638
     * copy of the specified object by retrieving it from another Member
639
     * Node and storing it locally so that it can be made accessible to
640
     * the DataONE system.
641
     *
642
     * @param session - the Session object containing the credentials for the Subject
643
     * @param sysmeta - Copy of the CN held system metadata for the object
644
     * @param sourceNode - A reference to node from which the content should be
645
     *                     retrieved. The reference should be resolved by
646
     *                     checking the CN node registry.
647
     *
648
     * @return true if the replication succeeds
649
     *
650
     * @throws ServiceFailure
651
     * @throws NotAuthorized
652
     * @throws NotImplemented
653
     * @throws UnsupportedType
654
     * @throws InsufficientResources
655
     * @throws InvalidRequest
656
     */
657
    @Override
658 6786 cjones
    public boolean replicate(Session session, SystemMetadata sysmeta,
659
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
660
            NotAuthorized, InvalidRequest, InsufficientResources,
661
            UnsupportedType {
662 9480 tao
        /*if(isReadOnlyMode()) {
663 9478 tao
            throw new InvalidRequest("2153", "The Metacat member node is on the read-only mode and your request can't be fulfiled. Please try again later.");
664 9480 tao
        }*/
665 6786 cjones
666 6875 cjones
        if (session != null && sysmeta != null && sourceNode != null) {
667
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
668
                            "\tSession.Subject      = "                           +
669
                            session.getSubject().getValue() + "\n"                +
670 7082 cjones
                            "\tidentifier           = "                           +
671
                            sysmeta.getIdentifier().getValue()                    +
672 6875 cjones
                            "\n" + "\tSource NodeReference ="                     +
673
                            sourceNode.getValue());
674
        }
675 6475 jones
        boolean result = false;
676 6786 cjones
        String nodeIdStr = null;
677 6651 cjones
        NodeReference nodeId = null;
678 6786 cjones
679 6795 cjones
        // get the referenced object
680
        Identifier pid = sysmeta.getIdentifier();
681 9068 tao
        // verify the pid is valid format
682
        if (!isValidIdentifier(pid)) {
683
            throw new InvalidRequest("2153", "The provided identifier in the system metadata is invalid.");
684
        }
685 6795 cjones
686
        // get from the membernode
687
        // TODO: switch credentials for the server retrieval?
688
        this.cn = D1Client.getCN();
689
        InputStream object = null;
690
        Session thisNodeSession = null;
691
        SystemMetadata localSystemMetadata = null;
692
        BaseException failure = null;
693 6818 cjones
        String localId = null;
694
695 6795 cjones
        // TODO: check credentials
696
        // cannot be called by public
697 7063 leinfelder
        if (session == null || session.getSubject() == null) {
698 7082 cjones
            String msg = "No session was provided to replicate identifier " +
699
            sysmeta.getIdentifier().getValue();
700 8414 tao
            logMetacat.error(msg);
701 7192 cjones
            throw new NotAuthorized("2152", msg);
702
703 6795 cjones
        }
704
705
706 6651 cjones
        // get the local node id
707
        try {
708 7030 cjones
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
709 6651 cjones
            nodeId = new NodeReference();
710
            nodeId.setValue(nodeIdStr);
711 6786 cjones
712 6651 cjones
        } catch (PropertyNotFoundException e1) {
713 7030 cjones
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
714 6795 cjones
            failure = new ServiceFailure("2151", msg);
715 8414 tao
            //setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
716 6795 cjones
            logMetacat.error(msg);
717 8414 tao
            //return true;
718
            throw new ServiceFailure("2151", msg);
719 6786 cjones
720 6651 cjones
        }
721 6795 cjones
722 6475 jones
        try {
723 6786 cjones
            try {
724 9544 tao
                // do we already have a replica?
725 6822 cjones
                try {
726 9544 tao
                    localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
727
                    // if we have a local id, get the local object
728
                    try {
729
                        object = MetacatHandler.read(localId);
730
                    } catch (Exception e) {
731
                        // NOTE: we may already know about this ID because it could be a data file described by a metadata file
732
                        // https://redmine.dataone.org/issues/2572
733
                        // TODO: fix this so that we don't prevent ourselves from getting replicas
734
735
                        // let the CN know that the replication failed
736
                        logMetacat.warn("Object content not found on this node despite having localId: " + localId);
737
                        String msg = "Can't read the object bytes properly, replica is invalid.";
738
                        ServiceFailure serviceFailure = new ServiceFailure("2151", msg);
739
                        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, serviceFailure);
740
                        logMetacat.warn(msg);
741
                        throw serviceFailure;
742
743
                    }
744
745
                } catch (McdbDocNotFoundException e) {
746
                    logMetacat.info("No replica found. Continuing.");
747 6822 cjones
748 9544 tao
                } catch (SQLException ee) {
749
                    throw new ServiceFailure("2151", "Couldn't identify the local id of the object with the specified identifier "
750
                                            +pid.getValue()+" since - "+ee.getMessage());
751 6822 cjones
                }
752 9544 tao
753
                // no local replica, get a replica
754
                if ( object == null ) {
755
                    /*boolean success = true;
756
                    try {
757
                        //use the v2 ping api to connect the source node
758
                        mn.ping();
759
                    } catch (Exception e) {
760
                        success = false;
761
                    }*/
762
                    D1NodeVersionChecker checker = new D1NodeVersionChecker(sourceNode);
763
                    String nodeVersion = checker.getVersion("MNRead");
764
                    if(nodeVersion != null && nodeVersion.equals(D1NodeVersionChecker.V1)) {
765
                        //The source node is a v1 node, we use the v1 api
766
                        org.dataone.client.v1.MNode mNodeV1 =  org.dataone.client.v1.itk.D1Client.getMN(sourceNode);
767
                        object = mNodeV1.getReplica(thisNodeSession, pid);
768
                    } else if (nodeVersion != null && nodeVersion.equals(D1NodeVersionChecker.V2)){
769
                     // session should be null to use the default certificate
770
                        // location set in the Certificate manager
771
                        MNode mn = D1Client.getMN(sourceNode);
772
                        object = mn.getReplica(thisNodeSession, pid);
773
                    } else {
774
                        throw new ServiceFailure("2151", "The version of MNRead service is "+nodeVersion+" in the source node "+sourceNode.getValue()+" and it is supported. Please check the information in the cn");
775
                    }
776
777
                    logMetacat.info("MNodeService.getReplica() called for identifier "
778
                                    + pid.getValue());
779 6822 cjones
780 9544 tao
                }
781
782
            } catch (InvalidToken e) {
783
                String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
784
                failure = new ServiceFailure("2151", msg);
785
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
786
                logMetacat.error(msg);
787
                throw new ServiceFailure("2151", msg);
788
789
            } catch (NotFound e) {
790
                String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
791
                failure = new ServiceFailure("2151", msg);
792
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
793
                logMetacat.error(msg);
794
                throw new ServiceFailure("2151", msg);
795
796
            } catch (NotAuthorized e) {
797
                String msg = "Could not retrieve object to replicate (NotAuthorized): "+ e.getMessage();
798
                failure = new ServiceFailure("2151", msg);
799
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
800
                logMetacat.error(msg);
801
                throw new ServiceFailure("2151", msg);
802
            } catch (NotImplemented e) {
803
                String msg = "Could not retrieve object to replicate (mn.getReplica NotImplemented): "+ e.getMessage();
804
                failure = new ServiceFailure("2151", msg);
805
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
806
                logMetacat.error(msg);
807
                throw new ServiceFailure("2151", msg);
808
            } catch (ServiceFailure e) {
809
                String msg = "Could not retrieve object to replicate (ServiceFailure): "+ e.getMessage();
810
                failure = new ServiceFailure("2151", msg);
811
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
812
                logMetacat.error(msg);
813
                throw new ServiceFailure("2151", msg);
814
            } catch (InsufficientResources e) {
815
                String msg = "Could not retrieve object to replicate (InsufficientResources): "+ e.getMessage();
816
                failure = new ServiceFailure("2151", msg);
817
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
818
                logMetacat.error(msg);
819
                throw new ServiceFailure("2151", msg);
820 6819 cjones
            }
821 9544 tao
822
            // verify checksum on the object, if supported
823
            if (object.markSupported()) {
824
                Checksum givenChecksum = sysmeta.getChecksum();
825
                Checksum computedChecksum = null;
826 9288 tao
                try {
827 9544 tao
                    computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
828
                    object.reset();
829
830 9288 tao
                } catch (Exception e) {
831 9544 tao
                    String msg = "Error computing checksum on replica: " + e.getMessage();
832
                    logMetacat.error(msg);
833
                    ServiceFailure sf = new ServiceFailure("2151", msg);
834
                    sf.initCause(e);
835
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, sf);
836
                    throw sf;
837 9287 tao
                }
838 9544 tao
                if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
839
                    logMetacat.error("Given    checksum for " + pid.getValue() +
840
                        "is " + givenChecksum.getValue());
841
                    logMetacat.error("Computed checksum for " + pid.getValue() +
842
                        "is " + computedChecksum.getValue());
843
                    String msg = "Computed checksum does not match declared checksum";
844
                    failure = new ServiceFailure("2151", msg);
845
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
846
                    throw new ServiceFailure("2151", msg);
847
                }
848 6786 cjones
            }
849
850 9544 tao
            // add it to local store
851
            Identifier retPid;
852 6786 cjones
            try {
853 9544 tao
                // skip the MN.create -- this mutates the system metadata and we don't want it to
854
                if ( localId == null ) {
855
                    // TODO: this will fail if we already "know" about the identifier
856
                    // FIXME: see https://redmine.dataone.org/issues/2572
857
                    retPid = super.create(session, pid, object, sysmeta);
858
                    result = (retPid.getValue().equals(pid.getValue()));
859
                }
860
861 6786 cjones
            } catch (Exception e) {
862 9544 tao
                String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
863 8414 tao
                failure = new ServiceFailure("2151", msg);
864
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
865 9544 tao
                logMetacat.error(msg);
866 8414 tao
                throw new ServiceFailure("2151", msg);
867 9544 tao
868 6786 cjones
            }
869 9544 tao
        } finally {
870
            IOUtils.closeQuietly(object);
871 6693 leinfelder
        }
872 9544 tao
873 6786 cjones
874 6795 cjones
        // finish by setting the replication status
875
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
876 6475 jones
        return result;
877
878 6250 cjones
    }
879 9287 tao
880
    /*
881
     * If the given node supports v2 replication.
882
     */
883
    private boolean supportV2Replication(Node node) throws InvalidRequest {
884
        return supportVersionReplication(node, "v2");
885
    }
886
887
    /*
888
     * If the given node support the the given version replication. Return true if it does.
889
     */
890
    private boolean supportVersionReplication(Node node, String version) throws InvalidRequest{
891
        boolean support = false;
892
        if(node == null) {
893
            throw new InvalidRequest("2153", "There is no capacity information about the node in the replicate.");
894
        } else {
895
            Services services = node.getServices();
896
            if(services == null) {
897
                throw new InvalidRequest("2153", "Can't get replica from a node which doesn't have the replicate service.");
898
            } else {
899
               List<Service> list = services.getServiceList();
900
               if(list == null) {
901
                   throw new InvalidRequest("2153", "Can't get replica from a node which doesn't have the replicate service.");
902
               } else {
903
                   for(Service service : list) {
904
                       if(service != null && service.getName() != null && service.getName().equals("MNReplication") &&
905
                               service.getVersion() != null && service.getVersion().equalsIgnoreCase(version) && service.getAvailable() == true ) {
906
                           support = true;
907
908
                       }
909
                   }
910
               }
911
            }
912
        }
913
        return support;
914
    }
915 6179 cjones
916 6475 jones
    /**
917
     * Return the object identified by the given object identifier
918
     *
919
     * @param session - the Session object containing the credentials for the Subject
920
     * @param pid - the object identifier for the given object
921
     *
922
     * @return inputStream - the input stream of the given object
923
     *
924
     * @throws InvalidToken
925
     * @throws ServiceFailure
926
     * @throws NotAuthorized
927
     * @throws InvalidRequest
928
     * @throws NotImplemented
929
     */
930
    @Override
931 6610 cjones
    public InputStream get(Session session, Identifier pid)
932
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
933 6258 cjones
934 6475 jones
        return super.get(session, pid);
935 6258 cjones
936 6259 cjones
    }
937 6258 cjones
938 6475 jones
    /**
939
     * Returns a Checksum for the specified object using an accepted hashing algorithm
940
     *
941
     * @param session - the Session object containing the credentials for the Subject
942
     * @param pid - the object identifier for the given object
943
     * @param algorithm -  the name of an algorithm that will be used to compute
944
     *                     a checksum of the bytes of the object
945
     *
946
     * @return checksum - the checksum of the given object
947
     *
948
     * @throws InvalidToken
949
     * @throws ServiceFailure
950
     * @throws NotAuthorized
951
     * @throws NotFound
952
     * @throws InvalidRequest
953
     * @throws NotImplemented
954
     */
955
    @Override
956 6610 cjones
    public Checksum getChecksum(Session session, Identifier pid, String algorithm)
957
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
958
        InvalidRequest, NotImplemented {
959 6258 cjones
960 6475 jones
        Checksum checksum = null;
961 9040 tao
        String serviceFailure = "1410";
962
        String notFound = "1420";
963
        //Checkum only handles the pid, not sid
964
        checkV1SystemMetaPidExist(pid, serviceFailure, "The checksum for the object specified by "+pid.getValue()+" couldn't be returned ",  notFound,
965
                "The object specified by "+pid.getValue()+" does not exist at this node.");
966 6475 jones
        InputStream inputStream = get(session, pid);
967
968 6259 cjones
        try {
969 6475 jones
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
970
971
        } catch (NoSuchAlgorithmException e) {
972
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
973
                    + e.getMessage());
974 6259 cjones
        } catch (IOException e) {
975 6475 jones
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
976
                    + e.getMessage());
977 6259 cjones
        }
978 6382 cjones
979 6475 jones
        if (checksum == null) {
980
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
981
        }
982 6258 cjones
983 6475 jones
        return checksum;
984 6259 cjones
    }
985 6179 cjones
986 6475 jones
    /**
987
     * Return the system metadata for a given object
988
     *
989
     * @param session - the Session object containing the credentials for the Subject
990
     * @param pid - the object identifier for the given object
991
     *
992
     * @return inputStream - the input stream of the given system metadata object
993
     *
994
     * @throws InvalidToken
995
     * @throws ServiceFailure
996
     * @throws NotAuthorized
997
     * @throws NotFound
998
     * @throws InvalidRequest
999
     * @throws NotImplemented
1000
     */
1001
    @Override
1002 6610 cjones
    public SystemMetadata getSystemMetadata(Session session, Identifier pid)
1003
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
1004
        NotImplemented {
1005 6341 leinfelder
1006 6475 jones
        return super.getSystemMetadata(session, pid);
1007
    }
1008 6341 leinfelder
1009 6475 jones
    /**
1010
     * Retrieve the list of objects present on the MN that match the calling parameters
1011
     *
1012
     * @param session - the Session object containing the credentials for the Subject
1013
     * @param startTime - Specifies the beginning of the time range from which
1014
     *                    to return object (>=)
1015
     * @param endTime - Specifies the beginning of the time range from which
1016
     *                  to return object (>=)
1017
     * @param objectFormat - Restrict results to the specified object format
1018
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
1019
     * @param start - The zero-based index of the first value, relative to the
1020
     *                first record of the resultset that matches the parameters.
1021
     * @param count - The maximum number of entries that should be returned in
1022
     *                the response. The Member Node may return less entries
1023
     *                than specified in this value.
1024
     *
1025
     * @return objectList - the list of objects matching the criteria
1026
     *
1027
     * @throws InvalidToken
1028
     * @throws ServiceFailure
1029
     * @throws NotAuthorized
1030
     * @throws InvalidRequest
1031
     * @throws NotImplemented
1032
     */
1033
    @Override
1034 8810 leinfelder
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Identifier identifier, Boolean replicaStatus, Integer start,
1035 6475 jones
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
1036 9249 tao
        NodeReference nodeId = null;
1037
        if(!replicaStatus) {
1038
            //not include those objects whose authoritative node is not this mn
1039
            nodeId = new NodeReference();
1040
            try {
1041
                String currentNodeId = PropertyService.getInstance().getProperty("dataone.nodeId"); // return only pids for which this mn
1042
                nodeId.setValue(currentNodeId);
1043
            } catch(Exception e) {
1044
                throw new ServiceFailure("1580", e.getMessage());
1045
            }
1046
        }
1047
        return super.listObjects(session, startTime, endTime, objectFormatId, identifier, nodeId, start, count);
1048 6229 cjones
    }
1049 6179 cjones
1050 6475 jones
    /**
1051 6476 jones
     * Return a description of the node's capabilities and services.
1052 6475 jones
     *
1053
     * @return node - the technical capabilities of the Member Node
1054
     *
1055
     * @throws ServiceFailure
1056
     * @throws NotAuthorized
1057
     * @throws InvalidRequest
1058
     * @throws NotImplemented
1059
     */
1060
    @Override
1061 6610 cjones
    public Node getCapabilities()
1062
        throws NotImplemented, ServiceFailure {
1063 6179 cjones
1064 6475 jones
        String nodeName = null;
1065
        String nodeId = null;
1066 6492 jones
        String subject = null;
1067 6938 cjones
        String contactSubject = null;
1068 6475 jones
        String nodeDesc = null;
1069 6476 jones
        String nodeTypeString = null;
1070
        NodeType nodeType = null;
1071 9253 tao
        List<String> mnCoreServiceVersions = null;
1072
        List<String> mnReadServiceVersions = null;
1073
        List<String> mnAuthorizationServiceVersions = null;
1074
        List<String> mnStorageServiceVersions = null;
1075
        List<String> mnReplicationServiceVersions = null;
1076 6179 cjones
1077 6475 jones
        boolean nodeSynchronize = false;
1078
        boolean nodeReplicate = false;
1079 9253 tao
        List<String> mnCoreServiceAvailables = null;
1080
        List<String> mnReadServiceAvailables = null;
1081
        List<String> mnAuthorizationServiceAvailables = null;
1082
        List<String> mnStorageServiceAvailables = null;
1083
        List<String> mnReplicationServiceAvailables = null;
1084 6179 cjones
1085 6475 jones
        try {
1086
            // get the properties of the node based on configuration information
1087 9253 tao
            nodeName = Settings.getConfiguration().getString("dataone.nodeName");
1088
            nodeId = Settings.getConfiguration().getString("dataone.nodeId");
1089
            subject = Settings.getConfiguration().getString("dataone.subject");
1090
            contactSubject = Settings.getConfiguration().getString("dataone.contactSubject");
1091
            nodeDesc = Settings.getConfiguration().getString("dataone.nodeDescription");
1092
            nodeTypeString = Settings.getConfiguration().getString("dataone.nodeType");
1093 6476 jones
            nodeType = NodeType.convert(nodeTypeString);
1094 9253 tao
            nodeSynchronize = new Boolean(Settings.getConfiguration().getString("dataone.nodeSynchronize")).booleanValue();
1095
            nodeReplicate = new Boolean(Settings.getConfiguration().getString("dataone.nodeReplicate")).booleanValue();
1096 6475 jones
1097 6476 jones
            // Set the properties of the node based on configuration information and
1098
            // calls to current status methods
1099 7286 leinfelder
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
1100 6476 jones
            Node node = new Node();
1101 6542 leinfelder
            node.setBaseURL(serviceName + "/" + nodeTypeString);
1102 6476 jones
            node.setDescription(nodeDesc);
1103 6475 jones
1104 6476 jones
            // set the node's health information
1105
            node.setState(NodeState.UP);
1106
1107
            // set the ping response to the current value
1108
            Ping canPing = new Ping();
1109
            canPing.setSuccess(false);
1110
            try {
1111 6803 leinfelder
            	Date pingDate = ping();
1112
                canPing.setSuccess(pingDate != null);
1113
            } catch (BaseException e) {
1114 6476 jones
                e.printStackTrace();
1115 6803 leinfelder
                // guess it can't be pinged
1116 6476 jones
            }
1117 6610 cjones
1118 6476 jones
            node.setPing(canPing);
1119 6475 jones
1120 6476 jones
            NodeReference identifier = new NodeReference();
1121
            identifier.setValue(nodeId);
1122
            node.setIdentifier(identifier);
1123 6492 jones
            Subject s = new Subject();
1124
            s.setValue(subject);
1125
            node.addSubject(s);
1126 6938 cjones
            Subject contact = new Subject();
1127
            contact.setValue(contactSubject);
1128
            node.addContactSubject(contact);
1129 6476 jones
            node.setName(nodeName);
1130
            node.setReplicate(nodeReplicate);
1131
            node.setSynchronize(nodeSynchronize);
1132 6475 jones
1133 6476 jones
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
1134
            Services services = new Services();
1135 6475 jones
1136 9253 tao
            mnCoreServiceVersions = Settings.getConfiguration().getList("dataone.mnCore.serviceVersion");
1137
            mnCoreServiceAvailables = Settings.getConfiguration().getList("dataone.mnCore.serviceAvailable");
1138
            if(mnCoreServiceVersions != null && mnCoreServiceAvailables != null && mnCoreServiceVersions.size() == mnCoreServiceAvailables.size()) {
1139
                for(int i=0; i<mnCoreServiceVersions.size(); i++) {
1140
                    String version = mnCoreServiceVersions.get(i);
1141
                    boolean available = new Boolean(mnCoreServiceAvailables.get(i)).booleanValue();
1142
                    Service sMNCore = new Service();
1143
                    sMNCore.setName("MNCore");
1144
                    sMNCore.setVersion(version);
1145
                    sMNCore.setAvailable(available);
1146
                    services.addService(sMNCore);
1147
                }
1148
            }
1149
1150
            mnReadServiceVersions = Settings.getConfiguration().getList("dataone.mnRead.serviceVersion");
1151
            mnReadServiceAvailables = Settings.getConfiguration().getList("dataone.mnRead.serviceAvailable");
1152
            if(mnReadServiceVersions != null && mnReadServiceAvailables != null && mnReadServiceVersions.size()==mnReadServiceAvailables.size()) {
1153
                for(int i=0; i<mnReadServiceVersions.size(); i++) {
1154
                    String version = mnReadServiceVersions.get(i);
1155
                    boolean available = new Boolean(mnReadServiceAvailables.get(i)).booleanValue();
1156
                    Service sMNRead = new Service();
1157
                    sMNRead.setName("MNRead");
1158
                    sMNRead.setVersion(version);
1159
                    sMNRead.setAvailable(available);
1160
                    services.addService(sMNRead);
1161
                }
1162
            }
1163
1164
            mnAuthorizationServiceVersions = Settings.getConfiguration().getList("dataone.mnAuthorization.serviceVersion");
1165
            mnAuthorizationServiceAvailables = Settings.getConfiguration().getList("dataone.mnAuthorization.serviceAvailable");
1166
            if(mnAuthorizationServiceVersions != null && mnAuthorizationServiceAvailables != null && mnAuthorizationServiceVersions.size()==mnAuthorizationServiceAvailables.size()) {
1167
                for(int i=0; i<mnAuthorizationServiceVersions.size(); i++) {
1168
                    String version = mnAuthorizationServiceVersions.get(i);
1169
                    boolean available = new Boolean(mnAuthorizationServiceAvailables.get(i)).booleanValue();
1170
                    Service sMNAuthorization = new Service();
1171
                    sMNAuthorization.setName("MNAuthorization");
1172
                    sMNAuthorization.setVersion(version);
1173
                    sMNAuthorization.setAvailable(available);
1174
                    services.addService(sMNAuthorization);
1175
                }
1176
            }
1177
1178
            mnStorageServiceVersions = Settings.getConfiguration().getList("dataone.mnStorage.serviceVersion");
1179
            mnStorageServiceAvailables = Settings.getConfiguration().getList("dataone.mnStorage.serviceAvailable");
1180
            if(mnStorageServiceVersions != null && mnStorageServiceAvailables != null && mnStorageServiceVersions.size() == mnStorageServiceAvailables.size()) {
1181
                for(int i=0; i<mnStorageServiceVersions.size(); i++) {
1182
                    String version = mnStorageServiceVersions.get(i);
1183
                    boolean available = new Boolean(mnStorageServiceAvailables.get(i)).booleanValue();
1184
                    Service sMNStorage = new Service();
1185
                    sMNStorage.setName("MNStorage");
1186
                    sMNStorage.setVersion(version);
1187
                    sMNStorage.setAvailable(available);
1188
                    services.addService(sMNStorage);
1189
                }
1190
            }
1191
1192
            mnReplicationServiceVersions = Settings.getConfiguration().getList("dataone.mnReplication.serviceVersion");
1193
            mnReplicationServiceAvailables = Settings.getConfiguration().getList("dataone.mnReplication.serviceAvailable");
1194
            if(mnReplicationServiceVersions != null && mnReplicationServiceAvailables != null && mnReplicationServiceVersions.size() == mnReplicationServiceAvailables.size()) {
1195
                for (int i=0; i<mnReplicationServiceVersions.size(); i++) {
1196
                    String version = mnReplicationServiceVersions.get(i);
1197
                    boolean available = new Boolean(mnReplicationServiceAvailables.get(i)).booleanValue();
1198
                    Service sMNReplication = new Service();
1199
                    sMNReplication.setName("MNReplication");
1200
                    sMNReplication.setVersion(version);
1201
                    sMNReplication.setAvailable(available);
1202
                    services.addService(sMNReplication);
1203
                }
1204
            }
1205
1206 6476 jones
            node.setServices(services);
1207 6475 jones
1208 6476 jones
            // Set the schedule for synchronization
1209
            Synchronization synchronization = new Synchronization();
1210
            Schedule schedule = new Schedule();
1211
            Date now = new Date();
1212 6689 leinfelder
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
1213
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
1214
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
1215
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
1216
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
1217
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
1218
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
1219 6476 jones
            synchronization.setSchedule(schedule);
1220
            synchronization.setLastHarvested(now);
1221
            synchronization.setLastCompleteHarvest(now);
1222
            node.setSynchronization(synchronization);
1223 6475 jones
1224 6476 jones
            node.setType(nodeType);
1225
            return node;
1226 6475 jones
1227 6476 jones
        } catch (PropertyNotFoundException pnfe) {
1228
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
1229
            logMetacat.error(msg);
1230
            throw new ServiceFailure("2162", msg);
1231
        }
1232 6228 cjones
    }
1233 6179 cjones
1234 8810 leinfelder
1235 6179 cjones
1236 6475 jones
    /**
1237
     * A callback method used by a CN to indicate to a MN that it cannot
1238
     * complete synchronization of the science metadata identified by pid.  Log
1239
     * the event in the metacat event log.
1240
     *
1241
     * @param session
1242
     * @param syncFailed
1243
     *
1244
     * @throws ServiceFailure
1245
     * @throws NotAuthorized
1246
     * @throws NotImplemented
1247
     */
1248
    @Override
1249 6991 leinfelder
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed)
1250 6610 cjones
        throws NotImplemented, ServiceFailure, NotAuthorized {
1251 6179 cjones
1252 6475 jones
        String localId;
1253 7075 cjones
        Identifier pid;
1254
        if ( syncFailed.getPid() != null ) {
1255
            pid = new Identifier();
1256
            pid.setValue(syncFailed.getPid());
1257
            boolean allowed;
1258
1259
            //are we allowed? only CNs
1260
            try {
1261 7142 leinfelder
                allowed = isAdminAuthorized(session);
1262 7075 cjones
                if ( !allowed ){
1263
                    throw new NotAuthorized("2162",
1264
                            "Not allowed to call synchronizationFailed() on this node.");
1265
                }
1266
            } catch (InvalidToken e) {
1267
                throw new NotAuthorized("2162",
1268
                        "Not allowed to call synchronizationFailed() on this node.");
1269 6331 leinfelder
1270 7075 cjones
            }
1271
1272
        } else {
1273
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1274
1275
        }
1276
1277 6475 jones
        try {
1278 7075 cjones
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1279 6475 jones
        } catch (McdbDocNotFoundException e) {
1280 7075 cjones
            throw new ServiceFailure("2161", "The identifier specified by " +
1281
                    syncFailed.getPid() + " was not found on this node.");
1282 6179 cjones
1283 9024 tao
        } catch (SQLException e) {
1284
            throw new ServiceFailure("2161", "Couldn't identify the local id of the identifier specified by " +
1285
                    syncFailed.getPid() + " since "+e.getMessage());
1286 6475 jones
        }
1287
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1288
        // method is changed to include the URL as a parameter
1289 9380 rnahf
        logMetacat.warn("Synchronization for the object identified by " +
1290 7075 cjones
                pid.getValue() + " failed from " + syncFailed.getNodeId() +
1291 9380 rnahf
                " with message: " + syncFailed.getDescription() +
1292
                ". Logging the event to the Metacat EventLog as a 'syncFailed' event.");
1293 6475 jones
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1294 6532 leinfelder
        String principal = Constants.SUBJECT_PUBLIC;
1295 6506 leinfelder
        if (session != null && session.getSubject() != null) {
1296 6575 cjones
          principal = session.getSubject().getValue();
1297 6506 leinfelder
        }
1298
        try {
1299 6575 cjones
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1300 6506 leinfelder
        } catch (Exception e) {
1301 7075 cjones
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1302 6991 leinfelder
        }
1303 6475 jones
        //EventLog.getInstance().log("CN URL WILL GO HERE",
1304
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1305 6991 leinfelder
        return true;
1306 6179 cjones
1307 6260 cjones
    }
1308
1309 6475 jones
    /**
1310
     * Essentially a get() but with different logging behavior
1311
     */
1312
    @Override
1313 6540 cjones
    public InputStream getReplica(Session session, Identifier pid)
1314 9314 tao
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken, NotFound {
1315 6179 cjones
1316 6540 cjones
        logMetacat.info("MNodeService.getReplica() called.");
1317
1318 6653 leinfelder
        // cannot be called by public
1319
        if (session == null) {
1320
        	throw new InvalidToken("2183", "No session was provided.");
1321
        }
1322
1323 6631 cjones
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1324
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1325
             "\tIdentifier           = " + pid.getValue());
1326
1327 6475 jones
        InputStream inputStream = null; // bytes to be returned
1328
        handler = new MetacatHandler(new Timer());
1329
        boolean allowed = false;
1330
        String localId; // the metacat docid for the pid
1331 6179 cjones
1332 6475 jones
        // get the local docid from Metacat
1333
        try {
1334
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1335
        } catch (McdbDocNotFoundException e) {
1336 9314 tao
            throw new NotFound("2185", "The object specified by " +
1337 6610 cjones
                    pid.getValue() + " does not exist at this node.");
1338
1339 9024 tao
        } catch (SQLException e) {
1340
            throw new ServiceFailure("2181", "The local id of the object specified by " +
1341
                    pid.getValue() + " couldn't be identified since "+e.getMessage());
1342 6475 jones
        }
1343 6234 cjones
1344 6552 leinfelder
        Subject targetNodeSubject = session.getSubject();
1345 6185 leinfelder
1346 6552 leinfelder
        // check for authorization to replicate, null session to act as this source MN
1347 6610 cjones
        try {
1348 6777 leinfelder
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1349 6610 cjones
        } catch (InvalidToken e1) {
1350
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
1351
                + e1.getMessage());
1352
1353
        } catch (NotFound e1) {
1354 9314 tao
            throw new NotFound("2185", "Could not find the object "+pid.getValue()+" in this node - "
1355 6610 cjones
                    + e1.getMessage());
1356 6384 cjones
1357 6610 cjones
        } catch (InvalidRequest e1) {
1358
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
1359
                    + e1.getMessage());
1360
1361
        }
1362
1363 6540 cjones
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1364
            " for identifier " + pid.getValue());
1365
1366 6475 jones
        // if the person is authorized, perform the read
1367
        if (allowed) {
1368
            try {
1369 6986 jones
                inputStream = MetacatHandler.read(localId);
1370 6475 jones
            } catch (Exception e) {
1371 9314 tao
                throw new ServiceFailure("2181", "The object specified by " +
1372 6610 cjones
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1373 6475 jones
            }
1374 9314 tao
        } else {
1375
            throw new NotAuthorized("2182", "The pid "+pid.getValue()+" is not authorized to be read by the client.");
1376 6475 jones
        }
1377 6384 cjones
1378 6475 jones
        // if we fail to set the input stream
1379
        if (inputStream == null) {
1380 6610 cjones
            throw new ServiceFailure("2181", "The object specified by " +
1381 9314 tao
                pid.getValue() + " can't be returned from the node.");
1382 6475 jones
        }
1383
1384
        // log the replica event
1385
        String principal = null;
1386
        if (session.getSubject() != null) {
1387
            principal = session.getSubject().getValue();
1388
        }
1389 6576 cjones
        EventLog.getInstance().log(request.getRemoteAddr(),
1390
            request.getHeader("User-Agent"), principal, localId, "replicate");
1391 6475 jones
1392
        return inputStream;
1393
    }
1394 9340 tao
1395 6573 cjones
    /**
1396 6600 cjones
     * A method to notify the Member Node that the authoritative copy of
1397 6599 cjones
     * system metadata on the Coordinating Nodes has changed.
1398 9340 tao
     *
1399 6599 cjones
     * @param session   Session information that contains the identity of the
1400
     *                  calling user as retrieved from the X.509 certificate
1401
     *                  which must be traceable to the CILogon service.
1402
     * @param serialVersion   The serialVersion of the system metadata
1403
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1404
     * @throws NotImplemented
1405
     * @throws ServiceFailure
1406
     * @throws NotAuthorized
1407
     * @throws InvalidRequest
1408
     * @throws InvalidToken
1409
     */
1410 6991 leinfelder
    public boolean systemMetadataChanged(Session session, Identifier pid,
1411 6599 cjones
        long serialVersion, Date dateSysMetaLastModified)
1412
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1413
        InvalidToken {
1414 9340 tao
        boolean needCheckAuthoriativeNode = true;
1415
        return systemMetadataChanged(needCheckAuthoriativeNode, session, pid,serialVersion, dateSysMetaLastModified);
1416
    }
1417
1418
    /**
1419
     * A method to notify the Member Node that the authoritative copy of
1420
     * system metadata on the Coordinating Nodes has changed.
1421
     * @param needCheckAuthoriativeNode  this is for the dataone version 2. In the
1422
     * version 2, there are two scenarios:
1423
     * 1. If the node is the authoritative node, it only accepts serial version and replica list.
1424
     * 2. If the node is a replica, it accepts everything.
1425
     * For the v1, api, the parameter should be false.
1426
     * @param session   Session information that contains the identity of the
1427
     *                  calling user as retrieved from the X.509 certificate
1428
     *                  which must be traceable to the CILogon service.
1429
     * @param serialVersion   The serialVersion of the system metadata
1430
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1431
     * @throws NotImplemented
1432
     * @throws ServiceFailure
1433
     * @throws NotAuthorized
1434
     * @throws InvalidRequest
1435
     * @throws InvalidToken
1436
     */
1437
    public boolean systemMetadataChanged(boolean needCheckAuthoriativeNode, Session session, Identifier pid,
1438
        long serialVersion, Date dateSysMetaLastModified)
1439
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1440
        InvalidToken {
1441 6599 cjones
1442 9479 tao
        /*if(isReadOnlyMode()) {
1443 9478 tao
            throw new InvalidRequest("1334", "The Metacat member node is on the read-only mode and your request can't be fulfiled. Please try again later.");
1444 9479 tao
        }*/
1445 7600 cjones
        // cannot be called by public
1446
        if (session == null) {
1447 9051 tao
        	throw new InvalidToken("1332", "No session was provided.");
1448 7600 cjones
        }
1449
1450 9051 tao
        String serviceFailureCode = "1333";
1451
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
1452
        if(sid != null) {
1453
            pid = sid;
1454
        }
1455
1456 6600 cjones
        SystemMetadata currentLocalSysMeta = null;
1457
        SystemMetadata newSysMeta = null;
1458
        CNode cn = D1Client.getCN();
1459
        NodeList nodeList = null;
1460
        Subject callingSubject = null;
1461
        boolean allowed = false;
1462
1463
        // are we allowed to call this?
1464
        callingSubject = session.getSubject();
1465
        nodeList = cn.listNodes();
1466
1467
        for(Node node : nodeList.getNodeList()) {
1468
            // must be a CN
1469
            if ( node.getType().equals(NodeType.CN)) {
1470
               List<Subject> subjectList = node.getSubjectList();
1471
               // the calling subject must be in the subject list
1472
               if ( subjectList.contains(callingSubject)) {
1473
                   allowed = true;
1474
1475
               }
1476
1477
            }
1478
        }
1479
1480
        if (!allowed ) {
1481
            String msg = "The subject identified by " + callingSubject.getValue() +
1482
              " is not authorized to call this service.";
1483
            throw new NotAuthorized("1331", msg);
1484
1485
        }
1486
        try {
1487 9340 tao
            HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1488 6600 cjones
1489 9340 tao
            // compare what we have locally to what is sent in the change notification
1490 6600 cjones
            try {
1491 9340 tao
                currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1492
1493
            } catch (RuntimeException e) {
1494
                String msg = "SystemMetadata for pid " + pid.getValue() +
1495
                  " couldn't be updated because it couldn't be found locally: " +
1496
                  e.getMessage();
1497 6692 leinfelder
                logMetacat.error(msg);
1498 9340 tao
                ServiceFailure sf = new ServiceFailure("1333", msg);
1499 6692 leinfelder
                sf.initCause(e);
1500 9340 tao
                throw sf;
1501 6600 cjones
            }
1502 6692 leinfelder
1503 9340 tao
            if(currentLocalSysMeta == null) {
1504
                throw new InvalidRequest("1334", "We can't find the system metadata in the node for the id "+pid.getValue());
1505
            }
1506 9382 tao
            if (currentLocalSysMeta.getSerialVersion().longValue() <= serialVersion ) {
1507 9340 tao
                try {
1508
                    newSysMeta = cn.getSystemMetadata(null, pid);
1509
                } catch (NotFound e) {
1510
                    // huh? you just said you had it
1511
                	String msg = "On updating the local copy of system metadata " +
1512
                    "for pid " + pid.getValue() +", the CN reports it is not found." +
1513
                    " The error message was: " + e.getMessage();
1514
                    logMetacat.error(msg);
1515
                    //ServiceFailure sf = new ServiceFailure("1333", msg);
1516
                    InvalidRequest sf = new InvalidRequest("1334", msg);
1517
                    sf.initCause(e);
1518
                    throw sf;
1519 9056 tao
                }
1520 9340 tao
1521
                //check about the sid in the system metadata
1522
                Identifier newSID = newSysMeta.getSeriesId();
1523
                if(newSID != null) {
1524
                    if (!isValidIdentifier(newSID)) {
1525
                        throw new InvalidRequest("1334", "The series identifier in the new system metadata is invalid.");
1526
                    }
1527
                    Identifier currentSID = currentLocalSysMeta.getSeriesId();
1528
                    if( currentSID != null && currentSID.getValue() != null) {
1529
                        if(!newSID.getValue().equals(currentSID.getValue())) {
1530
                            //newSID doesn't match the currentSID. The newSID shouldn't be used.
1531
                            try {
1532
                                if(IdentifierManager.getInstance().identifierExists(newSID.getValue())) {
1533
                                    throw new InvalidRequest("1334", "The series identifier "+newSID.getValue()+" in the new system metadata has been used by another object.");
1534
                                }
1535
                            } catch (SQLException sql) {
1536
                                throw new ServiceFailure("1333", "Couldn't determine if the SID "+newSID.getValue()+" in the system metadata exists in the node since "+sql.getMessage());
1537
                            }
1538
1539
                        }
1540
                    } else {
1541
                        //newSID shouldn't be used
1542 9056 tao
                        try {
1543
                            if(IdentifierManager.getInstance().identifierExists(newSID.getValue())) {
1544
                                throw new InvalidRequest("1334", "The series identifier "+newSID.getValue()+" in the new system metadata has been used by another object.");
1545
                            }
1546
                        } catch (SQLException sql) {
1547
                            throw new ServiceFailure("1333", "Couldn't determine if the SID "+newSID.getValue()+" in the system metadata exists in the node since "+sql.getMessage());
1548
                        }
1549
                    }
1550 9340 tao
                }
1551
                // update the local copy of system metadata for the pid
1552
                try {
1553
                    if(needCheckAuthoriativeNode) {
1554
                        //this is for the v2 api.
1555
                        if(isAuthoritativeNode(pid)) {
1556
                            //this is the authoritative node, so we only accept replica and serial version
1557 9375 tao
                            logMetacat.debug("MNodeService.systemMetadataChanged - this is the authoritative node for the pid "+pid.getValue());
1558 9340 tao
                            List<Replica> replicas = newSysMeta.getReplicaList();
1559
                            newSysMeta = currentLocalSysMeta;
1560
                            newSysMeta.setSerialVersion(new BigInteger((new Long(serialVersion)).toString()));
1561
                            newSysMeta.setReplicaList(replicas);
1562 9375 tao
                        } else {
1563
                            //we need to archive the object in the replica node
1564
                            logMetacat.debug("MNodeService.systemMetadataChanged - this is NOT the authoritative node for the pid "+pid.getValue());
1565
                            logMetacat.debug("MNodeService.systemMetadataChanged - the new value of archive is "+newSysMeta.getArchived()+" for the pid "+pid.getValue());
1566
                            logMetacat.debug("MNodeService.systemMetadataChanged - the local value of archive is "+currentLocalSysMeta.getArchived()+" for the pid "+pid.getValue());
1567
                            if (newSysMeta.getArchived() != null && newSysMeta.getArchived() == true  &&
1568
                                    ((currentLocalSysMeta.getArchived() != null && currentLocalSysMeta.getArchived() == false ) || currentLocalSysMeta.getArchived() == null)){
1569
                                logMetacat.debug("MNodeService.systemMetadataChanged - start to archive object "+pid.getValue());
1570
                                boolean logArchive = false;
1571
                                boolean needUpdateModificationDate = false;
1572
                                try {
1573
                                    archiveObject(logArchive, session, pid, newSysMeta, needUpdateModificationDate);
1574
                                } catch (NotFound e) {
1575
                                    throw new InvalidRequest("1334", "Can't find the pid "+pid.getValue()+" for archive.");
1576
                                }
1577
1578 9384 tao
                            } else if((newSysMeta.getArchived() == null || newSysMeta.getArchived() == false) && (currentLocalSysMeta.getArchived() != null && currentLocalSysMeta.getArchived() == true )) {
1579
                                throw new InvalidRequest("1334", "The pid "+pid.getValue()+" has been archived and it can't be reset to false.");
1580 9375 tao
                            }
1581 9056 tao
                        }
1582
                    }
1583 9340 tao
                    HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1584
                    logMetacat.info("Updated local copy of system metadata for pid " +
1585
                        pid.getValue() + " after change notification from the CN.");
1586
1587
                    // TODO: consider inspecting the change for archive
1588
                    // see: https://projects.ecoinformatics.org/ecoinfo/issues/6417
1589
    //                if (newSysMeta.getArchived() != null && newSysMeta.getArchived().booleanValue()) {
1590
    //                	try {
1591
    //						this.archive(session, newSysMeta.getIdentifier());
1592
    //					} catch (NotFound e) {
1593
    //						// do we care? nothing to do about it now
1594
    //						logMetacat.error(e.getMessage(), e);
1595
    //					}
1596
    //                }
1597
1598
                } catch (RuntimeException e) {
1599
                    String msg = "SystemMetadata for pid " + pid.getValue() +
1600
                      " couldn't be updated: " +
1601
                      e.getMessage();
1602
                    logMetacat.error(msg);
1603
                    ServiceFailure sf = new ServiceFailure("1333", msg);
1604
                    sf.initCause(e);
1605
                    throw sf;
1606 9056 tao
                }
1607 6600 cjones
1608 9375 tao
                try {
1609
                    String localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1610
                    EventLog.getInstance().log(request.getRemoteAddr(),
1611
                            request.getHeader("User-Agent"), session.getSubject().getValue(),
1612
                            localId, "updateSystemMetadata");
1613
                } catch (Exception e) {
1614
                    // do nothing, no localId to log with
1615
                    logMetacat.warn("MNodeService.systemMetadataChanged - Could not log 'updateSystemMetadata' event because no localId was found for pid: " + pid.getValue());
1616
                }
1617
1618 9340 tao
1619 6600 cjones
            }
1620 9340 tao
        } finally {
1621
            HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
1622
        }
1623
1624 9382 tao
        if (currentLocalSysMeta.getSerialVersion().longValue() <= serialVersion ) {
1625 9248 leinfelder
            // attempt to re-register the identifier (it checks if it is a doi)
1626
            try {
1627 9340 tao
                DOIService.getInstance().registerDOI(newSysMeta);
1628
            } catch (Exception e) {
1629
                logMetacat.warn("Could not [re]register DOI: " + e.getMessage(), e);
1630
            }
1631 9248 leinfelder
1632 8464 leinfelder
            // submit for indexing
1633
            try {
1634 9340 tao
                MetacatSolrIndex.getInstance().submit(newSysMeta.getIdentifier(), newSysMeta, null, true);
1635
            } catch (Exception e) {
1636 8464 leinfelder
                logMetacat.error("Could not submit changed systemMetadata for indexing, pid: " + newSysMeta.getIdentifier().getValue(), e);
1637 9340 tao
            }
1638 6600 cjones
        }
1639
1640 6991 leinfelder
        return true;
1641
1642 6599 cjones
    }
1643
1644 6795 cjones
    /*
1645
     * Set the replication status for the object on the Coordinating Node
1646
     *
1647
     * @param session - the session for the this target node
1648
     * @param pid - the identifier of the object being updated
1649
     * @param nodeId - the identifier of this target node
1650
     * @param status - the replication status to set
1651
     * @param failure - the exception to include, if any
1652
     */
1653
    private void setReplicationStatus(Session session, Identifier pid,
1654
        NodeReference nodeId, ReplicationStatus status, BaseException failure)
1655
        throws ServiceFailure, NotImplemented, NotAuthorized,
1656
        InvalidRequest {
1657
1658
        // call the CN as the MN to set the replication status
1659
        try {
1660
            this.cn = D1Client.getCN();
1661
            this.cn.setReplicationStatus(session, pid, nodeId,
1662
                    status, failure);
1663
1664
        } catch (InvalidToken e) {
1665 7091 leinfelder
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (InvalidToken): " + e.getMessage();
1666
            logMetacat.error(msg);
1667
        	throw new ServiceFailure("2151",
1668
                    msg);
1669 6795 cjones
1670
        } catch (NotFound e) {
1671 7091 leinfelder
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (NotFound): " + e.getMessage();
1672
            logMetacat.error(msg);
1673
        	throw new ServiceFailure("2151",
1674
                    msg);
1675 6795 cjones
1676
        }
1677
    }
1678 8591 leinfelder
1679
    private SystemMetadata makePublicIfNot(SystemMetadata sysmeta, Identifier pid) throws ServiceFailure, InvalidToken, NotFound, NotImplemented, InvalidRequest {
1680
    	// check if it is publicly readable
1681
		boolean isPublic = false;
1682
		Subject publicSubject = new Subject();
1683
		publicSubject.setValue(Constants.SUBJECT_PUBLIC);
1684
		Session publicSession = new Session();
1685
		publicSession.setSubject(publicSubject);
1686
		AccessRule publicRule = new AccessRule();
1687
		publicRule.addPermission(Permission.READ);
1688
		publicRule.addSubject(publicSubject);
1689
1690
		// see if we need to add the rule
1691
		try {
1692
			isPublic = this.isAuthorized(publicSession, pid, Permission.READ);
1693
		} catch (NotAuthorized na) {
1694
			// well, certainly not authorized for public read!
1695
		}
1696
		if (!isPublic) {
1697
			sysmeta.getAccessPolicy().addAllow(publicRule);
1698
		}
1699
1700
		return sysmeta;
1701
    }
1702 7099 leinfelder
1703
	@Override
1704 7441 leinfelder
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1705 7099 leinfelder
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1706
			InvalidRequest {
1707 7448 leinfelder
1708 8210 leinfelder
		// check for null session
1709
        if (session == null) {
1710
          throw new InvalidToken("2190", "Session is required to generate an Identifier at this Node.");
1711
        }
1712
1713 7441 leinfelder
		Identifier identifier = new Identifier();
1714 7448 leinfelder
1715 7489 leinfelder
		// handle different schemes
1716
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1717
			// UUID
1718
			UUID uuid = UUID.randomUUID();
1719
            identifier.setValue(UUID_PREFIX + uuid.toString());
1720
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1721 7512 leinfelder
			// generate a DOI
1722 7448 leinfelder
			try {
1723 7512 leinfelder
				identifier = DOIService.getInstance().generateDOI();
1724 7448 leinfelder
			} catch (EZIDException e) {
1725 7512 leinfelder
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1726
				sf.initCause(e);
1727
				throw sf;
1728 7448 leinfelder
			}
1729 7489 leinfelder
		} else {
1730
			// default if we don't know the scheme
1731
			if (fragment != null) {
1732
				// for now, just autogen with fragment
1733
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1734
				identifier.setValue(autogenId);
1735
			} else {
1736
				// autogen with no fragment
1737
				String autogenId = DocumentUtil.generateDocumentId(0);
1738
				identifier.setValue(autogenId);
1739
			}
1740 7448 leinfelder
		}
1741
1742 7441 leinfelder
		// TODO: reserve the identifier with the CN. We can only do this when
1743
		// 1) the MN is part of a CN cluster
1744
		// 2) the request is from an authenticated user
1745
1746
		return identifier;
1747 7099 leinfelder
	}
1748 7144 leinfelder
1749 8810 leinfelder
1750 7144 leinfelder
1751
	@Override
1752 8810 leinfelder
	public QueryEngineDescription getQueryEngineDescription(Session session, String engine)
1753 7144 leinfelder
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1754
			NotFound {
1755 7772 tao
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1756 8162 tao
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1757
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1758
            }
1759 7634 tao
	        QueryEngineDescription qed = new QueryEngineDescription();
1760 7772 tao
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1761 7634 tao
	        qed.setQueryEngineVersion("1.0");
1762
	        qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1763
	        Vector<String> pathsForIndexing = null;
1764
	        try {
1765
	            pathsForIndexing = SystemUtil.getPathsForIndexing();
1766
	        } catch (MetacatUtilException e) {
1767
	            logMetacat.warn("Could not get index paths", e);
1768
	        }
1769
	        for (String fieldName: pathsForIndexing) {
1770
	            QueryField field = new QueryField();
1771
	            field.addDescription("Indexed field for path '" + fieldName + "'");
1772
	            field.setName(fieldName);
1773
	            field.setReturnable(true);
1774
	            field.setSearchable(true);
1775
	            field.setSortable(false);
1776
	            // TODO: determine type and multivaluedness
1777
	            field.setType(String.class.getName());
1778
	            //field.setMultivalued(true);
1779
	            qed.addQueryField(field);
1780
	        }
1781
	        return qed;
1782 7772 tao
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1783 7781 tao
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1784 7772 tao
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1785 7781 tao
            }
1786 7634 tao
	        try {
1787 7662 tao
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1788 7634 tao
	            return qed;
1789
	        } catch (Exception e) {
1790
	            e.printStackTrace();
1791
	            throw new ServiceFailure("Solr server error", e.getMessage());
1792
	        }
1793
	    } else {
1794
	        throw new NotFound("404", "The Metacat member node can't find the query engine - "+engine);
1795
	    }
1796
1797 7417 leinfelder
	}
1798
1799
	@Override
1800 8810 leinfelder
	public QueryEngineList listQueryEngines(Session session) throws InvalidToken,
1801 7417 leinfelder
			ServiceFailure, NotAuthorized, NotImplemented {
1802
		QueryEngineList qel = new QueryEngineList();
1803 7781 tao
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1804
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1805
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1806 7772 tao
		for(String name : enables) {
1807
		    qel.addQueryEngine(name);
1808 7781 tao
		}
1809 7417 leinfelder
		return qel;
1810
	}
1811
1812
	@Override
1813 8810 leinfelder
	public InputStream query(Session session, String engine, String query) throws InvalidToken,
1814 7417 leinfelder
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1815
			NotFound {
1816 7648 tao
	    String user = Constants.SUBJECT_PUBLIC;
1817
        String[] groups= null;
1818 7680 tao
        Set<Subject> subjects = null;
1819 7648 tao
        if (session != null) {
1820
            user = session.getSubject().getValue();
1821 7680 tao
            subjects = AuthUtils.authorizedClientSubjects(session);
1822 7648 tao
            if (subjects != null) {
1823
                List<String> groupList = new ArrayList<String>();
1824
                for (Subject subject: subjects) {
1825
                    groupList.add(subject.getValue());
1826
                }
1827
                groups = groupList.toArray(new String[0]);
1828
            }
1829 7680 tao
        } else {
1830
            //add the public user subject to the set
1831
            Subject subject = new Subject();
1832
            subject.setValue(Constants.SUBJECT_PUBLIC);
1833 7757 leinfelder
            subjects = new HashSet<Subject>();
1834 7680 tao
            subjects.add(subject);
1835 7648 tao
        }
1836 7680 tao
        //System.out.println("====== user is "+user);
1837
        //System.out.println("====== groups are "+groups);
1838 7772 tao
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1839 8162 tao
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1840
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1841
            }
1842 7417 leinfelder
			try {
1843
				DBQuery queryobj = new DBQuery();
1844 7648 tao
1845 7417 leinfelder
				String results = queryobj.performPathquery(query, user, groups);
1846 7757 leinfelder
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1847
				ctbais.setContentType("text/xml");
1848
				return ctbais;
1849 7417 leinfelder
1850
			} catch (Exception e) {
1851 7757 leinfelder
				throw new ServiceFailure("Pathquery error", e.getMessage());
1852 7417 leinfelder
			}
1853
1854 7772 tao
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1855 7781 tao
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1856 7772 tao
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1857 7781 tao
		    }
1858 7634 tao
		    logMetacat.info("The query is ==================================== \n"+query);
1859 7620 tao
		    try {
1860 7634 tao
1861 7680 tao
                return MetacatSolrIndex.getInstance().query(query, subjects);
1862 7620 tao
            } catch (Exception e) {
1863
                // TODO Auto-generated catch block
1864
                throw new ServiceFailure("Solr server error", e.getMessage());
1865
            }
1866 7417 leinfelder
		}
1867
		return null;
1868
	}
1869 7849 leinfelder
1870
	/**
1871
	 * Given an existing Science Metadata PID, this method mints a DOI
1872
	 * and updates the original object "publishing" the update with the DOI.
1873
	 * This includes updating the ORE map that describes the Science Metadata+data.
1874
	 * TODO: ensure all referenced objects allow public read
1875
	 *
1876
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1877
	 *
1878
	 * @param originalIdentifier
1879
	 * @param request
1880 7864 leinfelder
	 * @throws InvalidRequest
1881
	 * @throws NotImplemented
1882
	 * @throws NotAuthorized
1883
	 * @throws ServiceFailure
1884
	 * @throws InvalidToken
1885 7849 leinfelder
	 * @throws NotFound
1886 7864 leinfelder
	 * @throws InvalidSystemMetadata
1887
	 * @throws InsufficientResources
1888
	 * @throws UnsupportedType
1889
	 * @throws IdentifierNotUnique
1890 7849 leinfelder
	 */
1891 9387 walker
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken,
1892
	ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique,
1893
	UnsupportedType, InsufficientResources, InvalidSystemMetadata, IOException {
1894 7849 leinfelder
1895 9092 tao
	    String serviceFailureCode = "1030";
1896
	    Identifier sid = getPIDForSID(originalIdentifier, serviceFailureCode);
1897
	    if(sid != null) {
1898
	        originalIdentifier = sid;
1899
	    }
1900 8141 leinfelder
		// get the original SM
1901
		SystemMetadata originalSystemMetadata = this.getSystemMetadata(session, originalIdentifier);
1902
1903 8594 leinfelder
		// make copy of it using the marshaller to ensure DEEP copy
1904
		SystemMetadata sysmeta = null;
1905 8141 leinfelder
		try {
1906 8594 leinfelder
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
1907
			TypeMarshaller.marshalTypeToOutputStream(originalSystemMetadata, baos);
1908
			sysmeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1909 8141 leinfelder
		} catch (Exception e) {
1910
			// report as service failure
1911
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1912
			sf.initCause(e);
1913
			throw sf;
1914
		}
1915
1916 7849 leinfelder
		// mint a DOI for the new revision
1917
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1918 8141 leinfelder
1919
		// set new metadata values
1920 7849 leinfelder
		sysmeta.setIdentifier(newIdentifier);
1921
		sysmeta.setObsoletes(originalIdentifier);
1922
		sysmeta.setObsoletedBy(null);
1923
1924 8591 leinfelder
		// ensure it is publicly readable
1925
		sysmeta = makePublicIfNot(sysmeta, originalIdentifier);
1926
1927 9387 walker
		//Get the bytes
1928
		InputStream inputStream = null;
1929
		boolean isScienceMetadata = isScienceMetadata(sysmeta);
1930
		//If it's a science metadata doc, we want to update the packageId first
1931
		if(isScienceMetadata){
1932
			InputStream originalObject = this.get(session, originalIdentifier);
1933
1934
			//Edit the science metadata with the new package Id (EML)
1935
			inputStream = editScienceMetadata(session, originalObject, originalIdentifier, newIdentifier);
1936
		}
1937
		else{
1938
			inputStream = this.get(session, originalIdentifier);
1939
		}
1940 7849 leinfelder
1941
		// update the object
1942 7864 leinfelder
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
1943 7849 leinfelder
1944 7864 leinfelder
		// update ORE that references the scimeta
1945 8190 leinfelder
		// first try the naive method, then check the SOLR index
1946 7849 leinfelder
		try {
1947 7864 leinfelder
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1948 7849 leinfelder
1949 7864 leinfelder
			Identifier potentialOreIdentifier = new Identifier();
1950
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1951 7849 leinfelder
1952 7864 leinfelder
			InputStream oreInputStream = null;
1953
			try {
1954
				oreInputStream = this.get(session, potentialOreIdentifier);
1955
			} catch (NotFound nf) {
1956
				// this is probably okay for many sci meta data docs
1957
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1958 8190 leinfelder
				// try the SOLR index
1959 8200 leinfelder
				List<Identifier> potentialOreIdentifiers = this.lookupOreFor(originalIdentifier, false);
1960 8190 leinfelder
				if (potentialOreIdentifiers != null) {
1961
					potentialOreIdentifier = potentialOreIdentifiers.get(0);
1962
					try {
1963
						oreInputStream = this.get(session, potentialOreIdentifier);
1964
					} catch (NotFound nf2) {
1965
						// this is probably okay for many sci meta data docs
1966
						logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1967
					}
1968
				}
1969 7864 leinfelder
			}
1970
			if (oreInputStream != null) {
1971
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1972
1973
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1974
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1975
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1976 8591 leinfelder
1977 7864 leinfelder
				// reconstruct the ORE with the new identifiers
1978
				sciMetaMap.remove(originalIdentifier);
1979
				sciMetaMap.put(newIdentifier, dataIdentifiers);
1980
1981
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1982
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1983
1984
				// get the original ORE SM and update the values
1985 8141 leinfelder
				SystemMetadata originalOreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1986
				SystemMetadata oreSysMeta = new SystemMetadata();
1987
				try {
1988 8594 leinfelder
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
1989
					TypeMarshaller.marshalTypeToOutputStream(originalOreSysMeta, baos);
1990
					oreSysMeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1991 8141 leinfelder
				} catch (Exception e) {
1992
					// report as service failure
1993
					ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1994
					sf.initCause(e);
1995
					throw sf;
1996
				}
1997
1998 7864 leinfelder
				oreSysMeta.setIdentifier(newOreIdentifier);
1999
				oreSysMeta.setObsoletes(potentialOreIdentifier);
2000
				oreSysMeta.setObsoletedBy(null);
2001
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
2002
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
2003
2004 8591 leinfelder
				// ensure ORE is publicly readable
2005 8594 leinfelder
				oreSysMeta = makePublicIfNot(oreSysMeta, potentialOreIdentifier);
2006 8591 leinfelder
2007
				// ensure all data objects allow public read
2008
				List<String> pidsToSync = new ArrayList<String>();
2009
				for (Identifier dataId: dataIdentifiers) {
2010
					SystemMetadata dataSysMeta = this.getSystemMetadata(session, dataId);
2011
					dataSysMeta = makePublicIfNot(dataSysMeta, dataId);
2012
					this.updateSystemMetadata(dataSysMeta);
2013
					pidsToSync.add(dataId.getValue());
2014
				}
2015
				SyncAccessPolicy sap = new SyncAccessPolicy();
2016
				try {
2017
					sap.sync(pidsToSync);
2018
				} catch (Exception e) {
2019
					// ignore
2020
					logMetacat.warn("Error attempting to sync access for data objects when publishing package");
2021
				}
2022
2023 7864 leinfelder
				// save the updated ORE
2024
				this.update(
2025
						session,
2026
						potentialOreIdentifier,
2027
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")),
2028
						newOreIdentifier,
2029
						oreSysMeta);
2030
2031 8362 leinfelder
			} else {
2032
				// create a new ORE for them
2033
				// https://projects.ecoinformatics.org/ecoinfo/issues/6194
2034
				try {
2035
					// find the local id for the NEW package.
2036
					String newLocalId = IdentifierManager.getInstance().getLocalId(newIdentifier.getValue());
2037
2038
					@SuppressWarnings("unused")
2039
					SystemMetadata extraSysMeta = SystemMetadataFactory.createSystemMetadata(newLocalId, true, false);
2040 8591 leinfelder
					// should be done generating the ORE here, and the same permissions were used from the metadata object
2041 8362 leinfelder
2042
				} catch (Exception e) {
2043
					// oops, guess there was a problem - no package for you
2044
					logMetacat.error("Could not generate new ORE for published object: " + newIdentifier.getValue(), e);
2045
				}
2046 7864 leinfelder
			}
2047
		} catch (McdbDocNotFoundException e) {
2048
			// report as service failure
2049
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2050
			sf.initCause(e);
2051
			throw sf;
2052
		} catch (UnsupportedEncodingException e) {
2053
			// report as service failure
2054
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2055
			sf.initCause(e);
2056
			throw sf;
2057
		} catch (OREException e) {
2058
			// report as service failure
2059
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2060
			sf.initCause(e);
2061
			throw sf;
2062
		} catch (URISyntaxException e) {
2063
			// report as service failure
2064
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2065
			sf.initCause(e);
2066
			throw sf;
2067
		} catch (OREParserException e) {
2068
			// report as service failure
2069
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2070
			sf.initCause(e);
2071
			throw sf;
2072
		} catch (ORESerialiserException e) {
2073
			// report as service failure
2074
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2075
			sf.initCause(e);
2076
			throw sf;
2077
		} catch (NoSuchAlgorithmException e) {
2078
			// report as service failure
2079
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2080
			sf.initCause(e);
2081
			throw sf;
2082 9024 tao
		} catch (SQLException e) {
2083
            // report as service failure
2084
            ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2085
            sf.initCause(e);
2086
            throw sf;
2087
        }
2088 7849 leinfelder
2089
		return newIdentifier;
2090
	}
2091 7850 leinfelder
2092
	/**
2093 9387 walker
	   * Update a science metadata document with its new Identifier
2094
	   *
2095
	   * @param session - the Session object containing the credentials for the Subject
2096
	   * @param object - the InputStream for the XML object to be edited
2097
	   * @param pid - the Identifier of the XML object to be updated
2098
	   * @param newPid = the new Identifier to give to the modified XML doc
2099
	   *
2100
	   * @return newObject - The InputStream for the modified XML object
2101
	   *
2102
	   * @throws ServiceFailure
2103
	   * @throws IOException
2104
	   * @throws UnsupportedEncodingException
2105
	   * @throws InvalidToken
2106
	   * @throws NotAuthorized
2107
	   * @throws NotFound
2108
	   * @throws NotImplemented
2109
	   */
2110
	  public InputStream editScienceMetadata(Session session, InputStream object, Identifier pid, Identifier newPid)
2111
	  	throws ServiceFailure, IOException, UnsupportedEncodingException, InvalidToken, NotAuthorized, NotFound, NotImplemented {
2112
2113
		logMetacat.debug("D1NodeService.editScienceMetadata() called.");
2114
2115
		 InputStream newObject = null;
2116
2117
	    try{
2118
	    	//Get the root node of the XML document
2119
	    	byte[] xmlBytes  = IOUtils.toByteArray(object);
2120
	        String xmlStr = new String(xmlBytes, "UTF-8");
2121
2122
	    	Document doc = XMLUtilities.getXMLReaderAsDOMDocument(new StringReader(xmlStr));
2123
		    org.w3c.dom.Node docNode = doc.getDocumentElement();
2124
2125
		    //Get the system metadata for this object
2126
		    SystemMetadata sysMeta = null;
2127
		    try{
2128
		    	sysMeta = getSystemMetadata(session, pid);
2129
		    } catch(NotAuthorized e){
2130
		    	throw new ServiceFailure("1030", "D1NodeService.editScienceMetadata(): " +
2131
		    			"This session is not authorized to access the system metadata for " +
2132
		    			pid.getValue() + " : " + e.getMessage());
2133
		    } catch(NotFound e){
2134
		    	throw new ServiceFailure("1030", "D1NodeService.editScienceMetadata(): " +
2135
		    			"Could not find the system metadata for " +
2136
		    			pid.getValue() + " : " + e.getMessage());
2137
		    }
2138
2139
		    //Get the formatId
2140
	        ObjectFormatIdentifier objFormatId = sysMeta.getFormatId();
2141
	        String formatId = objFormatId.getValue();
2142
2143
	    	//For all EML formats
2144
	        if(formatId.indexOf("eml") == 0){
2145
	        	//Update or add the id attribute
2146
	    	    XMLUtilities.addAttributeNodeToDOMTree(docNode, XPATH_EML_ID, newPid.getValue());
2147
	        }
2148
2149
	        //The modified object InputStream
2150
		    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
2151
		    Source xmlSource = new DOMSource(docNode);
2152
		    Result outputTarget = new StreamResult(outputStream);
2153
		    TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
2154
		    newObject = new ByteArrayInputStream(outputStream.toByteArray());
2155
2156
	    } catch(TransformerException e) {
2157
	    	throw new ServiceFailure("1030", "MNNodeService.editScienceMetadata(): " +
2158
	                "Could not update the ID in the XML document for " +
2159
	                "pid " + pid.getValue() +" : " + e.getMessage());
2160
	    } catch(IOException e){
2161
	    	throw new ServiceFailure("1030", "MNNodeService.editScienceMetadata(): " +
2162
	                "Could not update the ID in the XML document for " +
2163
	                "pid " + pid.getValue() +" : " + e.getMessage());
2164
	    }
2165
2166
	    return newObject;
2167
	  }
2168
2169
	/**
2170 8190 leinfelder
	 * Determines if we already have registered an ORE map for this package
2171
	 * NOTE: uses a solr query to locate OREs for the object
2172
	 * @param guid of the EML/packaging object
2173
	 * @return list of resource map identifiers for the given pid
2174
	 */
2175 8200 leinfelder
	public List<Identifier> lookupOreFor(Identifier guid, boolean includeObsolete) {
2176 8190 leinfelder
		// Search for the ORE if we can find it
2177
		String pid = guid.getValue();
2178
		List<Identifier> retList = null;
2179
		try {
2180 8745 leinfelder
			String query = "fl=id,resourceMap&wt=xml&q=-obsoletedBy:[* TO *]+resourceMap:[* TO *]+id:\"" + pid + "\"";
2181 8200 leinfelder
			if (includeObsolete) {
2182 8745 leinfelder
				query = "fl=id,resourceMap&wt=xml&q=resourceMap:[* TO *]+id:\"" + pid + "\"";
2183 8200 leinfelder
			}
2184
2185 8810 leinfelder
			InputStream results = this.query(null, "solr", query);
2186 8190 leinfelder
			org.w3c.dom.Node rootNode = XMLUtilities.getXMLReaderAsDOMTreeRootNode(new InputStreamReader(results, "UTF-8"));
2187
			//String resultString = XMLUtilities.getDOMTreeAsString(rootNode);
2188
			org.w3c.dom.NodeList nodeList = XMLUtilities.getNodeListWithXPath(rootNode, "//arr[@name=\"resourceMap\"]/str");
2189
			if (nodeList != null && nodeList.getLength() > 0) {
2190
				retList = new ArrayList<Identifier>();
2191
				for (int i = 0; i < nodeList.getLength(); i++) {
2192
					String found = nodeList.item(i).getFirstChild().getNodeValue();
2193
					Identifier oreId = new Identifier();
2194
					oreId.setValue(found);
2195
					retList.add(oreId);
2196
				}
2197
			}
2198
		} catch (Exception e) {
2199
			logMetacat.error("Error checking for resourceMap[s] on pid " + pid + ". " + e.getMessage(), e);
2200
		}
2201
2202
		return retList;
2203
	}
2204
2205 8810 leinfelder
2206
	@Override
2207
	public InputStream getPackage(Session session, ObjectFormatIdentifier formatId,
2208
			Identifier pid) throws InvalidToken, ServiceFailure,
2209
			NotAuthorized, InvalidRequest, NotImplemented, NotFound {
2210 9290 tao
	    if(formatId == null) {
2211
	        throw new InvalidRequest("2873", "The format type can't be null in the getpackage method.");
2212 9305 tao
	    } else if(!formatId.getValue().equals("application/bagit-097")) {
2213 9290 tao
	        throw new NotImplemented("", "The format "+formatId.getValue()+" is not supported in the getpackage method");
2214
	    }
2215 9092 tao
	    String serviceFailureCode = "2871";
2216
	    Identifier sid = getPIDForSID(pid, serviceFailureCode);
2217
	    if(sid != null) {
2218
	        pid = sid;
2219
	    }
2220 7850 leinfelder
		InputStream bagInputStream = null;
2221
		BagFactory bagFactory = new BagFactory();
2222
		Bag bag = bagFactory.createBag();
2223
2224
		// track the temp files we use so we can delete them when finished
2225
		List<File> tempFiles = new ArrayList<File>();
2226
2227
		// the pids to include in the package
2228
		List<Identifier> packagePids = new ArrayList<Identifier>();
2229
2230 7855 leinfelder
		// catch non-D1 service errors and throw as ServiceFailures
2231
		try {
2232 8437 walker
			//Create a map of dataone ids and file names
2233
			Map<Identifier, String> fileNames = new HashMap<Identifier, String>();
2234 7855 leinfelder
2235 8800 leinfelder
			// track the pid-to-file mapping
2236
			StringBuffer pidMapping = new StringBuffer();
2237
2238 7855 leinfelder
			// find the package contents
2239
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2240 8025 leinfelder
			if (ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
2241 8437 walker
				//Get the resource map as a map of Identifiers
2242 7855 leinfelder
				InputStream oreInputStream = this.get(session, pid);
2243
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
2244
				packagePids.addAll(resourceMapStructure.keySet());
2245 8437 walker
				//Loop through each object in this resource map
2246 7855 leinfelder
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
2247 8437 walker
					//Loop through each metadata object in this entry
2248
					Set<Identifier> metadataIdentifiers = entries.keySet();
2249
					for(Identifier metadataID: metadataIdentifiers){
2250
						try{
2251
							//Get the system metadata for this metadata object
2252
							SystemMetadata metadataSysMeta = this.getSystemMetadata(session, metadataID);
2253
2254 8800 leinfelder
							// include user-friendly metadata
2255
							if (ObjectFormatCache.getInstance().getFormat(metadataSysMeta.getFormatId()).getFormatType().equals("METADATA")) {
2256
								InputStream metadataStream = this.get(session, metadataID);
2257
2258
								try {
2259
									// transform
2260
						            String format = "default";
2261
2262
									DBTransform transformer = new DBTransform();
2263
						            String documentContent = IOUtils.toString(metadataStream, "UTF-8");
2264
						            String sourceType = metadataSysMeta.getFormatId().getValue();
2265
						            String targetType = "-//W3C//HTML//EN";
2266
						            ByteArrayOutputStream baos = new ByteArrayOutputStream();
2267
						            Writer writer = new OutputStreamWriter(baos , "UTF-8");
2268
						            // TODO: include more params?
2269
						            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2270
						            String localId = null;
2271
									try {
2272
										localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2273
									} catch (McdbDocNotFoundException e) {
2274
										throw new NotFound("1020", e.getMessage());
2275
									}
2276
									params.put("qformat", new String[] {format});
2277
						            params.put("docid", new String[] {localId});
2278
						            params.put("pid", new String[] {pid.getValue()});
2279
						            params.put("displaymodule", new String[] {"printall"});
2280
2281
						            transformer.transformXMLDocument(
2282
						                    documentContent ,
2283
						                    sourceType,
2284
						                    targetType ,
2285
						                    format,
2286
						                    writer,
2287
						                    params,
2288
						                    null //sessionid
2289
						                    );
2290
2291
						            // finally, get the HTML back
2292
						            ContentTypeByteArrayInputStream resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2293
2294
						            // write to temp file with correct css path
2295
						            File tmpDir = File.createTempFile("package_", "_dir");
2296
						            tmpDir.delete();
2297
						            tmpDir.mkdir();
2298
						            File htmlFile = File.createTempFile("metadata", ".html", tmpDir);
2299
						            File cssDir = new File(tmpDir, format);
2300
						            cssDir.mkdir();
2301
						            File cssFile = new File(tmpDir, format + "/" + format + ".css");
2302
						            String pdfFileName = metadataID.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-METADATA.pdf";
2303
						            File pdfFile = new File(tmpDir, pdfFileName);
2304
						            //File pdfFile = File.createTempFile("metadata", ".pdf", tmpDir);
2305
2306
						            // put the CSS file in place for the html to find it
2307
						            String originalCssPath = SystemUtil.getContextDir() + "/style/skins/" + format + "/" + format + ".css";
2308
						            IOUtils.copy(new FileInputStream(originalCssPath), new FileOutputStream(cssFile));
2309
2310
						            // write the HTML file
2311
						            IOUtils.copy(resultInputStream, new FileOutputStream(htmlFile));
2312
2313
						            // convert to PDF
2314
						            HtmlToPdf.export(htmlFile.getAbsolutePath(), pdfFile.getAbsolutePath());
2315
2316
						            //add to the package
2317
						            bag.addFileToPayload(pdfFile);
2318
									pidMapping.append(metadataID.getValue() + " (pdf)" +  "\t" + "data/" + pdfFile.getName() + "\n");
2319
2320
						            // mark for clean up after we are done
2321
									htmlFile.delete();
2322
									cssFile.delete();
2323
									cssDir.delete();
2324
						            tempFiles.add(tmpDir);
2325
									tempFiles.add(pdfFile); // delete this first later on
2326
2327
								} catch (Exception e) {
2328
									logMetacat.warn("Could not transform metadata", e);
2329
								}
2330
							}
2331
2332
2333 8437 walker
							//If this is in eml format, extract the filename and GUID from each entity in its package
2334
							if (metadataSysMeta.getFormatId().getValue().startsWith("eml://")) {
2335
								//Get the package
2336
								DataPackageParserInterface parser = new Eml200DataPackageParser();
2337
								InputStream emlStream = this.get(session, metadataID);
2338
								parser.parse(emlStream);
2339
								DataPackage dataPackage = parser.getDataPackage();
2340
2341
								//Get all the entities in this package and loop through each to extract its ID and file name
2342
								Entity[] entities = dataPackage.getEntityList();
2343
								for(Entity entity: entities){
2344
									try{
2345
										//Get the file name from the metadata
2346
										String fileNameFromMetadata = entity.getName();
2347
2348
										//Get the ecogrid URL from the metadata
2349
										String ecogridIdentifier = entity.getEntityIdentifier();
2350
										//Parse the ecogrid URL to get the local id
2351
										String idFromMetadata = DocumentUtil.getAccessionNumberFromEcogridIdentifier(ecogridIdentifier);
2352
2353
										//Get the docid and rev pair
2354
										String docid = DocumentUtil.getDocIdFromString(idFromMetadata);
2355
										String rev = DocumentUtil.getRevisionStringFromString(idFromMetadata);
2356
2357
										//Get the GUID
2358
										String guid = IdentifierManager.getInstance().getGUID(docid, Integer.valueOf(rev));
2359
										Identifier dataIdentifier = new Identifier();
2360
										dataIdentifier.setValue(guid);
2361
2362
										//Add the GUID to our GUID & file name map
2363
										fileNames.put(dataIdentifier, fileNameFromMetadata);
2364
									}
2365
									catch(Exception e){
2366
										//Prevent just one entity error
2367
										e.printStackTrace();
2368
										logMetacat.debug(e.getMessage(), e);
2369
									}
2370
								}
2371
							}
2372
						}
2373
						catch(Exception e){
2374
							//Catch errors that would prevent package download
2375
							logMetacat.debug(e.toString());
2376
						}
2377
					}
2378 7855 leinfelder
					packagePids.addAll(entries.keySet());
2379
					for (List<Identifier> dataPids: entries.values()) {
2380
						packagePids.addAll(dataPids);
2381
					}
2382 7850 leinfelder
				}
2383 7855 leinfelder
			} else {
2384
				// just the lone pid in this package
2385 9493 tao
				//packagePids.add(pid);
2386
			    //throw an invalid request exception
2387 9494 tao
			    throw new InvalidRequest("2873", "The given pid "+pid.getValue()+" is not a package id (resource map id). Please use a package id instead.");
2388 7850 leinfelder
			}
2389 8436 walker
2390 8437 walker
			//Create a temp file, then delete it and make a directory with that name
2391
			File tempDir = File.createTempFile("temp", Long.toString(System.nanoTime()));
2392
			tempDir.delete();
2393
			tempDir = new File(tempDir.getPath() + "_dir");
2394
			tempDir.mkdir();
2395 8436 walker
			tempFiles.add(tempDir);
2396 8800 leinfelder
			File pidMappingFile = new File(tempDir, "pid-mapping.txt");
2397 8436 walker
2398 7855 leinfelder
			// loop through the package contents
2399
			for (Identifier entryPid: packagePids) {
2400 8436 walker
				//Get the system metadata for each item
2401 8437 walker
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);
2402 8436 walker
2403
				String objectFormatType = ObjectFormatCache.getInstance().getFormat(entrySysMeta.getFormatId()).getFormatType();
2404 8437 walker
				String fileName = null;
2405 8436 walker
2406 8437 walker
				//TODO: Be more specific of what characters to replace. Make sure periods arent replaced for the filename from metadata
2407
				//Our default file name is just the ID + format type (e.g. walker.1.1-DATA)
2408
				fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + objectFormatType;
2409
2410 9542 leinfelder
				if (fileNames.containsKey(entryPid)){
2411 8437 walker
					//Let's use the file name and extension from the metadata is we have it
2412
					fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + fileNames.get(entryPid).replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
2413
				}
2414
2415 9541 leinfelder
				// ensure there is a file extension for the object
2416
				String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
2417
				fileName += extension;
2418
2419 9542 leinfelder
				// if SM has the file name, ignore everything else and use that
2420
				if (entrySysMeta.getFileName() != null) {
2421
					fileName = entrySysMeta.getFileName().replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
2422
				}
2423
2424 8436 walker
		        //Create a new file for this item and add to the list
2425 8437 walker
				File tempFile = new File(tempDir, fileName);
2426 7855 leinfelder
				tempFiles.add(tempFile);
2427 8436 walker
2428
				InputStream entryInputStream = this.get(session, entryPid);
2429 7855 leinfelder
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
2430
				bag.addFileToPayload(tempFile);
2431
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
2432
			}
2433
2434
			//add the the pid to data file map
2435
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
2436
			bag.addFileAsTag(pidMappingFile);
2437 8026 leinfelder
			tempFiles.add(pidMappingFile);
2438
2439 7855 leinfelder
			bag = bag.makeComplete();
2440 8026 leinfelder
2441 8436 walker
			///Now create the zip file
2442 8437 walker
			//Use the pid as the file name prefix, replacing all non-word characters
2443
			String zipName = pid.getValue().replaceAll("\\W", "_");
2444 8160 leinfelder
2445 8436 walker
			File bagFile = new File(tempDir, zipName+".zip");
2446
2447 7855 leinfelder
			bag.setFile(bagFile);
2448 7860 leinfelder
			ZipWriter zipWriter = new ZipWriter(bagFactory);
2449 7855 leinfelder
			bag.write(zipWriter, bagFile);
2450
			bagFile = bag.getFile();
2451 8026 leinfelder
			// use custom FIS that will delete the file when closed
2452
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
2453
			// also mark for deletion on shutdown in case the stream is never closed
2454
			bagFile.deleteOnExit();
2455 8436 walker
			tempFiles.add(bagFile);
2456 7855 leinfelder
2457 8026 leinfelder
			// clean up other temp files
2458 8800 leinfelder
			for (int i=tempFiles.size()-1; i>=0; i--){
2459
				tempFiles.get(i).delete();
2460 7855 leinfelder
			}
2461 8436 walker
2462 7855 leinfelder
		} catch (IOException e) {
2463
			// report as service failure
2464 9538 tao
		    e.printStackTrace();
2465 7855 leinfelder
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2466
			sf.initCause(e);
2467
			throw sf;
2468
		} catch (OREException e) {
2469
			// report as service failure
2470 9538 tao
		    e.printStackTrace();
2471 7855 leinfelder
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2472
			sf.initCause(e);
2473
			throw sf;
2474
		} catch (URISyntaxException e) {
2475
			// report as service failure
2476 9538 tao
		    e.printStackTrace();
2477 7855 leinfelder
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2478
			sf.initCause(e);
2479
			throw sf;
2480
		} catch (OREParserException e) {
2481
			// report as service failure
2482 9538 tao
		    e.printStackTrace();
2483 7855 leinfelder
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2484
			sf.initCause(e);
2485
			throw sf;
2486 7850 leinfelder
		}
2487
2488
		return bagInputStream;
2489 8810 leinfelder
	}
2490 9353 tao
2491
	 /**
2492
	   * Archives an object, where the object is either a
2493
	   * data object or a science metadata object.
2494
	   *
2495
	   * @param session - the Session object containing the credentials for the Subject
2496
	   * @param pid - The object identifier to be archived
2497
	   *
2498
	   * @return pid - the identifier of the object used for the archiving
2499
	   *
2500
	   * @throws InvalidToken
2501
	   * @throws ServiceFailure
2502
	   * @throws NotAuthorized
2503
	   * @throws NotFound
2504
	   * @throws NotImplemented
2505
	   * @throws InvalidRequest
2506
	   */
2507
	  public Identifier archive(Session session, Identifier pid)
2508
	      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2509 9478 tao
	      if(isReadOnlyMode()) {
2510 9481 tao
	            throw new ServiceFailure("2912", ReadOnlyChecker.DATAONEERROR);
2511 9478 tao
	        }
2512 9353 tao
	      boolean allowed = false;
2513
	      // do we have a valid pid?
2514
	      if (pid == null || pid.getValue().trim().equals("")) {
2515
	          throw new ServiceFailure("1350", "The provided identifier was invalid.");
2516
	      }
2517
2518
	      String serviceFailureCode = "1350";
2519
	      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2520
	      if(sid != null) {
2521
	          pid = sid;
2522
	      }
2523
	      // does the subject have archive (a D1 CHANGE_PERMISSION level) privileges on the pid?
2524
	      try {
2525
	            allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
2526
	        } catch (InvalidRequest e) {
2527
	          throw new ServiceFailure("1350", e.getDescription());
2528
	        }
2529
2530
	      if (allowed) {
2531
	         try {
2532
	             HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
2533
	             logMetacat.debug("MNodeService.archive - lock the identifier "+pid.getValue()+" in the system metadata map.");
2534
	             SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
2535 9354 tao
	             boolean needModifyDate = true;
2536 9373 tao
	             boolean logArchive = true;
2537
	             super.archiveObject(logArchive, session, pid, sysmeta, needModifyDate);
2538 9353 tao
	         } finally {
2539
	             HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
2540
	             logMetacat.debug("MNodeService.archive - unlock the identifier "+pid.getValue()+" in the system metadata map.");
2541
	         }
2542
2543
2544
	      } else {
2545
	          throw new NotAuthorized("1320", "The provided identity does not have " + "permission to archive the object on the Node.");
2546
	      }
2547
2548
	      return pid;
2549
	  }
2550 9190 tao
2551
	/**
2552
	 * Update the system metadata of the specified pid.
2553
	 */
2554 9177 tao
	@Override
2555
	public boolean updateSystemMetadata(Session session, Identifier pid,
2556
            SystemMetadata sysmeta) throws NotImplemented, NotAuthorized,
2557
            ServiceFailure, InvalidRequest, InvalidSystemMetadata, InvalidToken {
2558 9478 tao
2559
	  if(isReadOnlyMode()) {
2560 9481 tao
            throw new ServiceFailure("4868",  ReadOnlyChecker.DATAONEERROR);
2561 9478 tao
      }
2562 9177 tao
	 if(sysmeta == null) {
2563 9478 tao
	     throw  new InvalidRequest("4869", "The system metadata object should NOT be null in the updateSystemMetadata request.");
2564 9177 tao
	 }
2565 9190 tao
	 if(pid == null || pid.getValue() == null) {
2566 9478 tao
         throw new InvalidRequest("4869", "Please specify the id in the updateSystemMetadata request ") ;
2567 9190 tao
     }
2568
2569
     if (session == null) {
2570
         //TODO: many of the thrown exceptions do not use the correct error codes
2571
         //check these against the docs and correct them
2572
         throw new NotAuthorized("4861", "No Session - could not authorize for updating system metadata." +
2573
                 "  If you are not logged in, please do so and retry the request.");
2574
     } else {
2575
         try {
2576
             //Following session can do the change:
2577 9263 tao
           //- Authoritative Member Node (we can use isNodeAdmin since we checked isAuthoritativeNode )
2578 9190 tao
             //- Owner of object (coved by the userHasPermission method)
2579
             //- user subjects with the change permission
2580
             //Note: Coordinating Node can not because MN is authoritative
2581 9263 tao
             /*if(!isAuthoritativeNode(pid)) {
2582
                throw  new InvalidRequest("4863", "Client can only call updateSystemMetadata request on the authoritative memember node.");
2583
             }
2584 9190 tao
             if(!isNodeAdmin(session) && !userHasPermission(session, pid, Permission.CHANGE_PERMISSION)) {
2585
                 throw new NotAuthorized("4861", "The client -"+ session.getSubject().getValue()+ "is not authorized for updating the system metadata of the object "+pid.getValue());
2586 9263 tao
             }*/
2587
             if(!allowUpdating(session, pid, Permission.CHANGE_PERMISSION)) {
2588
                 throw new NotAuthorized("4861", "The client -"+ session.getSubject().getValue()+ "is not authorized for updating the system metadata of the object "+pid.getValue());
2589 9190 tao
             }
2590
         } catch (NotFound e) {
2591 9478 tao
             throw new InvalidRequest("4869", "Can't determine if the client has the permission to update the system metacat of the object with id "+pid.getValue()+" since "+e.getDescription());
2592 9190 tao
         }
2593
2594
     }
2595 9335 tao
      //update the system metadata locally
2596
      boolean success = false;
2597
      try {
2598
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
2599
          SystemMetadata currentSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
2600
          if(currentSysmeta == null) {
2601 9478 tao
              throw  new InvalidRequest("4869", "We can't find the current system metadata on the member node for the id "+pid.getValue());
2602 9335 tao
          }
2603
          Date currentModiDate = currentSysmeta.getDateSysMetadataModified();
2604
          Date commingModiDate = sysmeta.getDateSysMetadataModified();
2605
          if(commingModiDate == null) {
2606 9478 tao
              throw  new InvalidRequest("4869", "The system metadata modification date can't be null.");
2607 9335 tao
          }
2608
          if(currentModiDate != null && commingModiDate.getTime() != currentModiDate.getTime()) {
2609 9478 tao
              throw new InvalidRequest("4869", "Your system metadata modification date is "+commingModiDate.toString()+
2610 9335 tao
                      ". It doesn't match our current system metadata modification date in the member node - "+currentModiDate.toString()+
2611
                      ". Please check if you have got the newest version of the system metadata before the modification.");
2612
          }
2613
          boolean needUpdateModificationDate = true;
2614 9354 tao
          boolean fromCN = false;
2615
          success = updateSystemMetadata(session, pid, sysmeta, needUpdateModificationDate, currentSysmeta, fromCN);
2616 9335 tao
      } finally {
2617
          HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
2618
      }
2619 9177 tao
2620
      if(success) {
2621 9190 tao
          //TODO
2622 9177 tao
          //notify the cns the synchornize the new system metadata.
2623 9246 tao
          this.cn = D1Client.getCN();
2624
          try {
2625 9256 tao
              if(this.cn == null)  {
2626
                  logMetacat.warn("updateSystemMetadata - can't get the instance of the CN. So the system metadata in CN can't be updated.");
2627
              } else {
2628
                  this.cn.synchronize(null, pid);
2629
              }
2630 9257 tao
          } catch (BaseException e) {
2631
              e.printStackTrace();
2632
              logMetacat.error("It is a DataONEBaseException and its detail code is "+e.getDetail_code() +" and its code is "+e.getCode());
2633
              logMetacat.error("Can't update the systemmetadata of pid "+pid.getValue()+" in CNs since "+e.getMessage());
2634 9246 tao
          } catch (Exception e) {
2635 9256 tao
              e.printStackTrace();
2636 9246 tao
              logMetacat.error("Can't update the systemmetadata of pid "+pid.getValue()+" in CNs since "+e.getMessage());
2637
          }
2638 9248 leinfelder
2639
          // attempt to re-register the identifier (it checks if it is a doi)
2640
          try {
2641
        	  DOIService.getInstance().registerDOI(sysmeta);
2642
          } catch (Exception e) {
2643
  			logMetacat.warn("Could not [re]register DOI: " + e.getMessage(), e);
2644
          }
2645 9177 tao
      }
2646
      return success;
2647
    }
2648 9190 tao
2649
	/*
2650
     * Determine if the current node is the authoritative node for the given pid.
2651
     */
2652 9374 tao
    protected boolean isAuthoritativeNode(Identifier pid) throws InvalidRequest {
2653 9190 tao
        boolean isAuthoritativeNode = false;
2654
        if(pid != null && pid.getValue() != null) {
2655
            SystemMetadata sys = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
2656
            if(sys != null) {
2657
                NodeReference node = sys.getAuthoritativeMemberNode();
2658
                if(node != null) {
2659
                    String nodeValue = node.getValue();
2660
                    logMetacat.debug("The authoritative node for id "+pid.getValue()+" is "+nodeValue);
2661
                    //System.out.println("The authoritative node for id "+pid.getValue()+" is "+nodeValue);
2662
                    String currentNodeId = Settings.getConfiguration().getString("dataone.nodeId");
2663
                    logMetacat.debug("The node id in metacat.properties is "+currentNodeId);
2664
                    //System.out.println("The node id in metacat.properties is "+currentNodeId);
2665
                    if(currentNodeId != null && !currentNodeId.trim().equals("") && currentNodeId.equals(nodeValue)) {
2666
                        logMetacat.debug("They are matching");
2667
                        //System.out.println("They are matching");
2668
                        isAuthoritativeNode = true;
2669
                    }
2670 9374 tao
                } else {
2671
                    throw new InvalidRequest("4869", "Coudn't find the authoritative member node in the system metadata associated with the pid "+pid.getValue());
2672 9190 tao
                }
2673 9374 tao
            } else {
2674
                throw new InvalidRequest("4869", "Coudn't find the system metadata associated with the pid "+pid.getValue());
2675 9190 tao
            }
2676 9374 tao
        } else {
2677
            throw new InvalidRequest("4869", "The request pid is null");
2678 9190 tao
        }
2679
        return isAuthoritativeNode;
2680
    }
2681 6795 cjones
2682 9263 tao
    /*
2683
     * Rules are:
2684
     * 1. If the session has an cn object, it is allowed.
2685
     * 2. If it is not a cn object, the client should have approperate permission and it should also happen on the authorative node.
2686
     */
2687 9374 tao
    private boolean allowUpdating(Session session, Identifier pid, Permission permission) throws NotAuthorized, NotFound, InvalidRequest {
2688 9263 tao
        boolean allow = false;
2689
        if(isCNAdmin (session)) {
2690
            allow = true;
2691
        } else {
2692
            if(isAuthoritativeNode(pid)) {
2693
                if(userHasPermission(session, pid, permission)) {
2694
                    allow = true;
2695
                } else {
2696
                    allow = false;
2697
                }
2698
            } else {
2699
                throw new NotAuthorized("4861", "Client can only call the request on the authoritative memember node.");
2700
            }
2701
        }
2702
        return allow;
2703
2704
    }
2705
2706 9478 tao
    /**
2707
     * Check if the metacat is in the read-only mode.
2708
     * @return true if it is; otherwise false.
2709
     */
2710
    protected boolean isReadOnlyMode() {
2711
        boolean readOnly = false;
2712
        ReadOnlyChecker checker = new ReadOnlyChecker();
2713
        readOnly = checker.isReadOnly();
2714
        return readOnly;
2715
    }
2716
2717 6179 cjones
}