Project

General

Profile

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