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