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