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