Project

General

Profile

1
/**
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
import java.io.ByteArrayInputStream;
27
import java.io.ByteArrayOutputStream;
28
import java.io.File;
29
import java.io.FileOutputStream;
30
import java.io.IOException;
31
import java.io.InputStream;
32
import java.io.InputStreamReader;
33
import java.io.OutputStreamWriter;
34
import java.io.UnsupportedEncodingException;
35
import java.io.Writer;
36
import java.math.BigInteger;
37
import java.net.URISyntaxException;
38
import java.security.NoSuchAlgorithmException;
39
import java.sql.SQLException;
40
import java.util.ArrayList;
41
import java.util.Calendar;
42
import java.util.Date;
43
import java.util.HashMap;
44
import java.util.HashSet;
45
import java.util.Hashtable;
46
import java.util.List;
47
import java.util.Map;
48
import java.util.Set;
49
import java.util.Timer;
50
import java.util.UUID;
51
import java.util.Vector;
52

    
53
import javax.servlet.http.HttpServletRequest;
54

    
55
import org.apache.commons.io.IOUtils;
56
import org.apache.log4j.Logger;
57
import org.dataone.client.CNode;
58
import org.dataone.client.D1Client;
59
import org.dataone.client.MNode;
60
import org.dataone.client.ObjectFormatCache;
61
import org.dataone.client.auth.CertificateManager;
62
import org.dataone.client.formats.ObjectFormatInfo;
63
import org.dataone.configuration.Settings;
64
import org.dataone.ore.ResourceMapFactory;
65
import org.dataone.service.exceptions.BaseException;
66
import org.dataone.service.exceptions.IdentifierNotUnique;
67
import org.dataone.service.exceptions.InsufficientResources;
68
import org.dataone.service.exceptions.InvalidRequest;
69
import org.dataone.service.exceptions.InvalidSystemMetadata;
70
import org.dataone.service.exceptions.InvalidToken;
71
import org.dataone.service.exceptions.NotAuthorized;
72
import org.dataone.service.exceptions.NotFound;
73
import org.dataone.service.exceptions.NotImplemented;
74
import org.dataone.service.exceptions.ServiceFailure;
75
import org.dataone.service.exceptions.SynchronizationFailed;
76
import org.dataone.service.exceptions.UnsupportedType;
77
import org.dataone.service.mn.tier1.v1.MNCore;
78
import org.dataone.service.mn.tier1.v1.MNRead;
79
import org.dataone.service.mn.tier2.v1.MNAuthorization;
80
import org.dataone.service.mn.tier3.v1.MNStorage;
81
import org.dataone.service.mn.tier4.v1.MNReplication;
82
import org.dataone.service.mn.v1.MNQuery;
83
import org.dataone.service.types.v1.AccessRule;
84
import org.dataone.service.types.v1.Checksum;
85
import org.dataone.service.types.v1.DescribeResponse;
86
import org.dataone.service.types.v1.Event;
87
import org.dataone.service.types.v1.Identifier;
88
import org.dataone.service.types.v1.Log;
89
import org.dataone.service.types.v1.LogEntry;
90
import org.dataone.service.types.v1.MonitorInfo;
91
import org.dataone.service.types.v1.MonitorList;
92
import org.dataone.service.types.v1.Node;
93
import org.dataone.service.types.v1.NodeList;
94
import org.dataone.service.types.v1.NodeReference;
95
import org.dataone.service.types.v1.NodeState;
96
import org.dataone.service.types.v1.NodeType;
97
import org.dataone.service.types.v1.ObjectFormat;
98
import org.dataone.service.types.v1.ObjectFormatIdentifier;
99
import org.dataone.service.types.v1.ObjectList;
100
import org.dataone.service.types.v1.Permission;
101
import org.dataone.service.types.v1.Ping;
102
import org.dataone.service.types.v1.ReplicationStatus;
103
import org.dataone.service.types.v1.Schedule;
104
import org.dataone.service.types.v1.Service;
105
import org.dataone.service.types.v1.Services;
106
import org.dataone.service.types.v1.Session;
107
import org.dataone.service.types.v1.Subject;
108
import org.dataone.service.types.v1.Synchronization;
109
import org.dataone.service.types.v1.SystemMetadata;
110
import org.dataone.service.types.v1.util.AuthUtils;
111
import org.dataone.service.types.v1.util.ChecksumUtil;
112
import org.dataone.service.types.v1_1.QueryEngineDescription;
113
import org.dataone.service.types.v1_1.QueryEngineList;
114
import org.dataone.service.types.v1_1.QueryField;
115
import org.dataone.service.util.Constants;
116
import org.dataone.service.util.TypeMarshaller;
117
import org.dspace.foresite.OREException;
118
import org.dspace.foresite.OREParserException;
119
import org.dspace.foresite.ORESerialiserException;
120
import org.dspace.foresite.ResourceMap;
121
import org.ecoinformatics.datamanager.parser.DataPackage;
122
import org.ecoinformatics.datamanager.parser.Entity;
123
import org.ecoinformatics.datamanager.parser.generic.DataPackageParserInterface;
124
import org.ecoinformatics.datamanager.parser.generic.Eml200DataPackageParser;
125

    
126
import edu.ucsb.nceas.ezid.EZIDException;
127
import edu.ucsb.nceas.metacat.DBQuery;
128
import edu.ucsb.nceas.metacat.DBTransform;
129
import edu.ucsb.nceas.metacat.EventLog;
130
import edu.ucsb.nceas.metacat.IdentifierManager;
131
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
132
import edu.ucsb.nceas.metacat.MetaCatServlet;
133
import edu.ucsb.nceas.metacat.MetacatHandler;
134
import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines;
135
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
136
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
137
import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler;
138
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
139
import edu.ucsb.nceas.metacat.properties.PropertyService;
140
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
141
import edu.ucsb.nceas.metacat.util.DeleteOnCloseFileInputStream;
142
import edu.ucsb.nceas.metacat.util.DocumentUtil;
143
import edu.ucsb.nceas.metacat.util.SystemUtil;
144
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
145
import edu.ucsb.nceas.utilities.XMLUtilities;
146
import gov.loc.repository.bagit.Bag;
147
import gov.loc.repository.bagit.BagFactory;
148
import gov.loc.repository.bagit.writer.impl.ZipWriter;
149

    
150
/**
151
 * Represents Metacat's implementation of the DataONE Member Node 
152
 * service API. Methods implement the various MN* interfaces, and methods common
153
 * to both Member Node and Coordinating Node interfaces are found in the
154
 * D1NodeService base class.
155
 * 
156
 * Implements:
157
 * MNCore.ping()
158
 * MNCore.getLogRecords()
159
 * MNCore.getObjectStatistics()
160
 * MNCore.getOperationStatistics()
161
 * MNCore.getStatus()
162
 * MNCore.getCapabilities()
163
 * MNRead.get()
164
 * MNRead.getSystemMetadata()
165
 * MNRead.describe()
166
 * MNRead.getChecksum()
167
 * MNRead.listObjects()
168
 * MNRead.synchronizationFailed()
169
 * MNAuthorization.isAuthorized()
170
 * MNAuthorization.setAccessPolicy()
171
 * MNStorage.create()
172
 * MNStorage.update()
173
 * MNStorage.delete()
174
 * MNReplication.replicate()
175
 * 
176
 */
177
public class MNodeService extends D1NodeService 
178
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage, MNQuery {
179

    
180
    //private static final String PATHQUERY = "pathquery";
181
	public static final String UUID_SCHEME = "UUID";
182
	public static final String DOI_SCHEME = "DOI";
183
	private static final String UUID_PREFIX = "urn:uuid:";
184

    
185
	/* the logger instance */
186
    private Logger logMetacat = null;
187
    
188
    /* A reference to a remote Memeber Node */
189
    private MNode mn;
190
    
191
    /* A reference to a Coordinating Node */
192
    private CNode cn;
193

    
194

    
195
    /**
196
     * Singleton accessor to get an instance of MNodeService.
197
     * 
198
     * @return instance - the instance of MNodeService
199
     */
200
    public static MNodeService getInstance(HttpServletRequest request) {
201
        return new MNodeService(request);
202
    }
203

    
204
    /**
205
     * Constructor, private for singleton access
206
     */
207
    private MNodeService(HttpServletRequest request) {
208
        super(request);
209
        logMetacat = Logger.getLogger(MNodeService.class);
210
        
211
        // set the Member Node certificate file location
212
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
213
    }
214

    
215
    /**
216
     * Deletes an object from the Member Node, where the object is either a 
217
     * data object or a science metadata object.
218
     * 
219
     * @param session - the Session object containing the credentials for the Subject
220
     * @param pid - The object identifier to be deleted
221
     * 
222
     * @return pid - the identifier of the object used for the deletion
223
     * 
224
     * @throws InvalidToken
225
     * @throws ServiceFailure
226
     * @throws NotAuthorized
227
     * @throws NotFound
228
     * @throws NotImplemented
229
     * @throws InvalidRequest
230
     */
231
    @Override
232
    public Identifier delete(Session session, Identifier pid) 
233
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
234

    
235
    	// only admin of  the MN or the CN is allowed a full delete
236
        boolean allowed = false;
237
        allowed = isAdminAuthorized(session);
238
        
239
        //check if it is the authoritative member node
240
        if(!allowed) {
241
            allowed = isAuthoritativeMNodeAdmin(session, pid);
242
        }
243
        
244
        if (!allowed) { 
245
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
246
        }
247
    	
248
    	// defer to superclass implementation
249
        return super.delete(session, pid);
250
    }
251

    
252
    /**
253
     * Updates an existing object by creating a new object identified by 
254
     * newPid on the Member Node which explicitly obsoletes the object 
255
     * identified by pid through appropriate changes to the SystemMetadata 
256
     * of pid and newPid
257
     * 
258
     * @param session - the Session object containing the credentials for the Subject
259
     * @param pid - The identifier of the object to be updated
260
     * @param object - the new object bytes
261
     * @param sysmeta - the new system metadata describing the object
262
     * 
263
     * @return newPid - the identifier of the new object
264
     * 
265
     * @throws InvalidToken
266
     * @throws ServiceFailure
267
     * @throws NotAuthorized
268
     * @throws NotFound
269
     * @throws NotImplemented
270
     * @throws IdentifierNotUnique
271
     * @throws UnsupportedType
272
     * @throws InsufficientResources
273
     * @throws InvalidSystemMetadata
274
     * @throws InvalidRequest
275
     */
276
    @Override
277
    public Identifier update(Session session, Identifier pid, InputStream object, 
278
        Identifier newPid, SystemMetadata sysmeta) 
279
        throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
280
        UnsupportedType, InsufficientResources, NotFound, 
281
        InvalidSystemMetadata, NotImplemented, InvalidRequest {
282

    
283
        String localId = null;
284
        boolean allowed = false;
285
        boolean isScienceMetadata = false;
286
        
287
        if (session == null) {
288
        	throw new InvalidToken("1210", "No session has been provided");
289
        }
290
        Subject subject = session.getSubject();
291

    
292
        // verify the pid is valid format
293
        if (!isValidIdentifier(pid)) {
294
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
295
        }
296

    
297
        // check for the existing identifier
298
        try {
299
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
300
            
301
        } catch (McdbDocNotFoundException e) {
302
            throw new InvalidRequest("1202", "The object with the provided " + 
303
                "identifier was not found.");
304
            
305
        }
306
        
307
        // set the originating node
308
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
309
        sysmeta.setOriginMemberNode(originMemberNode);
310
        
311
        // set the submitter to match the certificate
312
        sysmeta.setSubmitter(subject);
313
        // set the dates
314
        Date now = Calendar.getInstance().getTime();
315
        sysmeta.setDateSysMetadataModified(now);
316
        sysmeta.setDateUploaded(now);
317
        
318
        // make sure serial version is set to something
319
        BigInteger serialVersion = sysmeta.getSerialVersion();
320
        if (serialVersion == null) {
321
        	sysmeta.setSerialVersion(BigInteger.ZERO);
322
        }
323

    
324
        // does the subject have WRITE ( == update) priveleges on the pid?
325
        allowed = isAuthorized(session, pid, Permission.WRITE);
326

    
327
        if (allowed) {
328
        	
329
        	// check quality of SM
330
        	if (sysmeta.getObsoletedBy() != null) {
331
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
332
        	}
333
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
334
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
335
        	}
336

    
337
            // get the existing system metadata for the object
338
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
339

    
340
            // check for previous update
341
            // see: https://redmine.dataone.org/issues/3336
342
            Identifier existingObsoletedBy = existingSysMeta.getObsoletedBy();
343
            if (existingObsoletedBy != null) {
344
            	throw new InvalidRequest("1202", 
345
            			"The previous identifier has already been made obsolete by: " + existingObsoletedBy.getValue());
346
            }
347

    
348
            isScienceMetadata = isScienceMetadata(sysmeta);
349

    
350
            // do we have XML metadata or a data object?
351
            if (isScienceMetadata) {
352

    
353
                // update the science metadata XML document
354
                // TODO: handle non-XML metadata/data documents (like netCDF)
355
                // TODO: don't put objects into memory using stream to string
356
                String objectAsXML = "";
357
                try {
358
                    objectAsXML = IOUtils.toString(object, "UTF-8");
359
                    // give the old pid so we can calculate the new local id 
360
                    localId = insertOrUpdateDocument(objectAsXML, pid, session, "update");
361
                    // register the newPid and the generated localId
362
                    if (newPid != null) {
363
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
364

    
365
                    }
366

    
367
                } catch (IOException e) {
368
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
369
                    logMetacat.info(msg);
370
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
371

    
372
                }
373

    
374
            } else {
375

    
376
                // update the data object
377
                localId = insertDataObject(object, newPid, session);
378

    
379
            }
380
            
381
            // add the newPid to the obsoletedBy list for the existing sysmeta
382
            existingSysMeta.setObsoletedBy(newPid);
383

    
384
            // then update the existing system metadata
385
            updateSystemMetadata(existingSysMeta);
386

    
387
            // prep the new system metadata, add pid to the affected lists
388
            sysmeta.setObsoletes(pid);
389
            //sysmeta.addDerivedFrom(pid);
390

    
391
            // and insert the new system metadata
392
            insertSystemMetadata(sysmeta);
393

    
394
            // log the update event
395
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
396
            
397
            // attempt to register the identifier - it checks if it is a doi
398
            try {
399
    			DOIService.getInstance().registerDOI(sysmeta);
400
    		} catch (EZIDException e) {
401
                throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
402
    		}
403

    
404
        } else {
405
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
406
                    + " on the Member Node.");
407
        }
408

    
409
        return newPid;
410
    }
411

    
412
    public Identifier create(Session session, Identifier pid, InputStream object, SystemMetadata sysmeta) throws InvalidToken, ServiceFailure, NotAuthorized,
413
            IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, InvalidRequest {
414

    
415
        // check for null session
416
        if (session == null) {
417
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
418
        }
419
        // set the submitter to match the certificate
420
        sysmeta.setSubmitter(session.getSubject());
421
        // set the originating node
422
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
423
        sysmeta.setOriginMemberNode(originMemberNode);
424
        sysmeta.setArchived(false);
425

    
426
        // set the dates
427
        Date now = Calendar.getInstance().getTime();
428
        sysmeta.setDateSysMetadataModified(now);
429
        sysmeta.setDateUploaded(now);
430
        
431
        // set the serial version
432
        sysmeta.setSerialVersion(BigInteger.ZERO);
433

    
434
        // check that we are not attempting to subvert versioning
435
        if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
436
            throw new InvalidSystemMetadata("1180", 
437
              "The supplied system metadata is invalid. " +
438
              "The obsoletes field cannot have a value when creating entries.");
439
        }
440
        
441
        if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
442
            throw new InvalidSystemMetadata("1180", 
443
              "The supplied system metadata is invalid. " +
444
              "The obsoletedBy field cannot have a value when creating entries.");
445
        }
446

    
447
        // call the shared impl
448
        Identifier resultPid = super.create(session, pid, object, sysmeta);
449
        
450
        // attempt to register the identifier - it checks if it is a doi
451
        try {
452
			DOIService.getInstance().registerDOI(sysmeta);
453
		} catch (EZIDException e) {
454
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
455
			sf.initCause(e);
456
            throw sf;
457
		}
458
        
459
        // return 
460
		return resultPid ;
461
    }
462

    
463
    /**
464
     * Called by a Coordinating Node to request that the Member Node create a 
465
     * copy of the specified object by retrieving it from another Member 
466
     * Node and storing it locally so that it can be made accessible to 
467
     * the DataONE system.
468
     * 
469
     * @param session - the Session object containing the credentials for the Subject
470
     * @param sysmeta - Copy of the CN held system metadata for the object
471
     * @param sourceNode - A reference to node from which the content should be 
472
     *                     retrieved. The reference should be resolved by 
473
     *                     checking the CN node registry.
474
     * 
475
     * @return true if the replication succeeds
476
     * 
477
     * @throws ServiceFailure
478
     * @throws NotAuthorized
479
     * @throws NotImplemented
480
     * @throws UnsupportedType
481
     * @throws InsufficientResources
482
     * @throws InvalidRequest
483
     */
484
    @Override
485
    public boolean replicate(Session session, SystemMetadata sysmeta,
486
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
487
            NotAuthorized, InvalidRequest, InsufficientResources,
488
            UnsupportedType {
489

    
490
        if (session != null && sysmeta != null && sourceNode != null) {
491
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
492
                            "\tSession.Subject      = "                           +
493
                            session.getSubject().getValue() + "\n"                +
494
                            "\tidentifier           = "                           + 
495
                            sysmeta.getIdentifier().getValue()                    +
496
                            "\n" + "\tSource NodeReference ="                     +
497
                            sourceNode.getValue());
498
        }
499
        boolean result = false;
500
        String nodeIdStr = null;
501
        NodeReference nodeId = null;
502

    
503
        // get the referenced object
504
        Identifier pid = sysmeta.getIdentifier();
505

    
506
        // get from the membernode
507
        // TODO: switch credentials for the server retrieval?
508
        this.mn = D1Client.getMN(sourceNode);
509
        this.cn = D1Client.getCN();
510
        InputStream object = null;
511
        Session thisNodeSession = null;
512
        SystemMetadata localSystemMetadata = null;
513
        BaseException failure = null;
514
        String localId = null;
515
        
516
        // TODO: check credentials
517
        // cannot be called by public
518
        if (session == null || session.getSubject() == null) {
519
            String msg = "No session was provided to replicate identifier " +
520
            sysmeta.getIdentifier().getValue();
521
            logMetacat.error(msg);
522
            throw new NotAuthorized("2152", msg);
523
            
524
        }
525

    
526

    
527
        // get the local node id
528
        try {
529
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
530
            nodeId = new NodeReference();
531
            nodeId.setValue(nodeIdStr);
532

    
533
        } catch (PropertyNotFoundException e1) {
534
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
535
            failure = new ServiceFailure("2151", msg);
536
            //setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
537
            logMetacat.error(msg);
538
            //return true;
539
            throw new ServiceFailure("2151", msg);
540

    
541
        }
542
        
543

    
544
        try {
545
            // do we already have a replica?
546
            try {
547
                localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
548
                // if we have a local id, get the local object
549
                try {
550
                    object = MetacatHandler.read(localId);
551
                } catch (Exception e) {
552
                	// NOTE: we may already know about this ID because it could be a data file described by a metadata file
553
                	// https://redmine.dataone.org/issues/2572
554
                	// TODO: fix this so that we don't prevent ourselves from getting replicas
555
                	
556
                    // let the CN know that the replication failed
557
                	logMetacat.warn("Object content not found on this node despite having localId: " + localId);
558
                	String msg = "Can't read the object bytes properly, replica is invalid.";
559
                    ServiceFailure serviceFailure = new ServiceFailure("2151", msg);
560
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, serviceFailure);
561
                    logMetacat.warn(msg);
562
                    throw serviceFailure;
563
                    
564
                }
565

    
566
            } catch (McdbDocNotFoundException e) {
567
                logMetacat.info("No replica found. Continuing.");
568
                
569
            }
570
            
571
            // no local replica, get a replica
572
            if ( object == null ) {
573
                // session should be null to use the default certificate
574
                // location set in the Certificate manager
575
                object = mn.getReplica(thisNodeSession, pid);
576
                logMetacat.info("MNodeService.getReplica() called for identifier "
577
                                + pid.getValue());
578

    
579
            }
580

    
581
        } catch (InvalidToken e) {            
582
            String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
583
            failure = new ServiceFailure("2151", msg);
584
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
585
            logMetacat.error(msg);
586
            throw new ServiceFailure("2151", msg);
587

    
588
        } catch (NotFound e) {
589
            String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
590
            failure = new ServiceFailure("2151", msg);
591
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
592
            logMetacat.error(msg);
593
            throw new ServiceFailure("2151", msg);
594

    
595
        } catch (NotAuthorized e) {
596
            String msg = "Could not retrieve object to replicate (NotAuthorized): "+ e.getMessage();
597
            failure = new ServiceFailure("2151", msg);
598
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
599
            logMetacat.error(msg);
600
            throw new ServiceFailure("2151", msg);
601
        } catch (NotImplemented e) {
602
            String msg = "Could not retrieve object to replicate (mn.getReplica NotImplemented): "+ e.getMessage();
603
            failure = new ServiceFailure("2151", msg);
604
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
605
            logMetacat.error(msg);
606
            throw new ServiceFailure("2151", msg);
607
        } catch (ServiceFailure e) {
608
            String msg = "Could not retrieve object to replicate (ServiceFailure): "+ e.getMessage();
609
            failure = new ServiceFailure("2151", msg);
610
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
611
            logMetacat.error(msg);
612
            throw new ServiceFailure("2151", msg);
613
        } catch (InsufficientResources e) {
614
            String msg = "Could not retrieve object to replicate (InsufficientResources): "+ e.getMessage();
615
            failure = new ServiceFailure("2151", msg);
616
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
617
            logMetacat.error(msg);
618
            throw new ServiceFailure("2151", msg);
619
        }
620

    
621
        // verify checksum on the object, if supported
622
        if (object.markSupported()) {
623
            Checksum givenChecksum = sysmeta.getChecksum();
624
            Checksum computedChecksum = null;
625
            try {
626
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
627
                object.reset();
628

    
629
            } catch (Exception e) {
630
                String msg = "Error computing checksum on replica: " + e.getMessage();
631
                logMetacat.error(msg);
632
                ServiceFailure sf = new ServiceFailure("2151", msg);
633
                sf.initCause(e);
634
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, sf);
635
                throw sf;
636
            }
637
            if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
638
                logMetacat.error("Given    checksum for " + pid.getValue() + 
639
                    "is " + givenChecksum.getValue());
640
                logMetacat.error("Computed checksum for " + pid.getValue() + 
641
                    "is " + computedChecksum.getValue());
642
                String msg = "Computed checksum does not match declared checksum";
643
                failure = new ServiceFailure("2151", msg);
644
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
645
                throw new ServiceFailure("2151", msg);
646
            }
647
        }
648

    
649
        // add it to local store
650
        Identifier retPid;
651
        try {
652
            // skip the MN.create -- this mutates the system metadata and we don't want it to
653
            if ( localId == null ) {
654
                // TODO: this will fail if we already "know" about the identifier
655
            	// FIXME: see https://redmine.dataone.org/issues/2572
656
                retPid = super.create(session, pid, object, sysmeta);
657
                result = (retPid.getValue().equals(pid.getValue()));
658
            }
659
            
660
        } catch (Exception e) {
661
            String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
662
            failure = new ServiceFailure("2151", msg);
663
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
664
            logMetacat.error(msg);
665
            throw new ServiceFailure("2151", msg);
666
            
667
        }
668

    
669
        // finish by setting the replication status
670
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
671
        return result;
672

    
673
    }
674

    
675
    /**
676
     * Return the object identified by the given object identifier
677
     * 
678
     * @param session - the Session object containing the credentials for the Subject
679
     * @param pid - the object identifier for the given object
680
     * 
681
     * @return inputStream - the input stream of the given object
682
     * 
683
     * @throws InvalidToken
684
     * @throws ServiceFailure
685
     * @throws NotAuthorized
686
     * @throws InvalidRequest
687
     * @throws NotImplemented
688
     */
689
    @Override
690
    public InputStream get(Session session, Identifier pid) 
691
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
692

    
693
        return super.get(session, pid);
694

    
695
    }
696

    
697
    /**
698
     * Returns a Checksum for the specified object using an accepted hashing algorithm
699
     * 
700
     * @param session - the Session object containing the credentials for the Subject
701
     * @param pid - the object identifier for the given object
702
     * @param algorithm -  the name of an algorithm that will be used to compute 
703
     *                     a checksum of the bytes of the object
704
     * 
705
     * @return checksum - the checksum of the given object
706
     * 
707
     * @throws InvalidToken
708
     * @throws ServiceFailure
709
     * @throws NotAuthorized
710
     * @throws NotFound
711
     * @throws InvalidRequest
712
     * @throws NotImplemented
713
     */
714
    @Override
715
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
716
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
717
        InvalidRequest, NotImplemented {
718

    
719
        Checksum checksum = null;
720

    
721
        InputStream inputStream = get(session, pid);
722

    
723
        try {
724
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
725

    
726
        } catch (NoSuchAlgorithmException e) {
727
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
728
                    + e.getMessage());
729
        } catch (IOException e) {
730
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
731
                    + e.getMessage());
732
        }
733

    
734
        if (checksum == null) {
735
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
736
        }
737

    
738
        return checksum;
739
    }
740

    
741
    /**
742
     * Return the system metadata for a given object
743
     * 
744
     * @param session - the Session object containing the credentials for the Subject
745
     * @param pid - the object identifier for the given object
746
     * 
747
     * @return inputStream - the input stream of the given system metadata object
748
     * 
749
     * @throws InvalidToken
750
     * @throws ServiceFailure
751
     * @throws NotAuthorized
752
     * @throws NotFound
753
     * @throws InvalidRequest
754
     * @throws NotImplemented
755
     */
756
    @Override
757
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) 
758
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
759
        NotImplemented {
760

    
761
        return super.getSystemMetadata(session, pid);
762
    }
763

    
764
    /**
765
     * Retrieve the list of objects present on the MN that match the calling parameters
766
     * 
767
     * @param session - the Session object containing the credentials for the Subject
768
     * @param startTime - Specifies the beginning of the time range from which 
769
     *                    to return object (>=)
770
     * @param endTime - Specifies the beginning of the time range from which 
771
     *                  to return object (>=)
772
     * @param objectFormat - Restrict results to the specified object format
773
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
774
     * @param start - The zero-based index of the first value, relative to the 
775
     *                first record of the resultset that matches the parameters.
776
     * @param count - The maximum number of entries that should be returned in 
777
     *                the response. The Member Node may return less entries 
778
     *                than specified in this value.
779
     * 
780
     * @return objectList - the list of objects matching the criteria
781
     * 
782
     * @throws InvalidToken
783
     * @throws ServiceFailure
784
     * @throws NotAuthorized
785
     * @throws InvalidRequest
786
     * @throws NotImplemented
787
     */
788
    @Override
789
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
790
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
791

    
792
        ObjectList objectList = null;
793

    
794
        try {
795
        	// safeguard against large requests
796
            if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
797
            	count = MAXIMUM_DB_RECORD_COUNT;
798
            }
799
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
800
        } catch (Exception e) {
801
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
802
        }
803

    
804
        return objectList;
805
    }
806

    
807
    /**
808
     * Return a description of the node's capabilities and services.
809
     * 
810
     * @return node - the technical capabilities of the Member Node
811
     * 
812
     * @throws ServiceFailure
813
     * @throws NotAuthorized
814
     * @throws InvalidRequest
815
     * @throws NotImplemented
816
     */
817
    @Override
818
    public Node getCapabilities() 
819
        throws NotImplemented, ServiceFailure {
820

    
821
        String nodeName = null;
822
        String nodeId = null;
823
        String subject = null;
824
        String contactSubject = null;
825
        String nodeDesc = null;
826
        String nodeTypeString = null;
827
        NodeType nodeType = null;
828
        String mnCoreServiceVersion = null;
829
        String mnReadServiceVersion = null;
830
        String mnAuthorizationServiceVersion = null;
831
        String mnStorageServiceVersion = null;
832
        String mnReplicationServiceVersion = null;
833

    
834
        boolean nodeSynchronize = false;
835
        boolean nodeReplicate = false;
836
        boolean mnCoreServiceAvailable = false;
837
        boolean mnReadServiceAvailable = false;
838
        boolean mnAuthorizationServiceAvailable = false;
839
        boolean mnStorageServiceAvailable = false;
840
        boolean mnReplicationServiceAvailable = false;
841

    
842
        try {
843
            // get the properties of the node based on configuration information
844
            nodeName = PropertyService.getProperty("dataone.nodeName");
845
            nodeId = PropertyService.getProperty("dataone.nodeId");
846
            subject = PropertyService.getProperty("dataone.subject");
847
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
848
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
849
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
850
            nodeType = NodeType.convert(nodeTypeString);
851
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
852
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
853

    
854
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
855
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
856
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
857
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
858
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
859

    
860
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
861
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
862
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
863
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
864
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
865

    
866
            // Set the properties of the node based on configuration information and
867
            // calls to current status methods
868
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
869
            Node node = new Node();
870
            node.setBaseURL(serviceName + "/" + nodeTypeString);
871
            node.setDescription(nodeDesc);
872

    
873
            // set the node's health information
874
            node.setState(NodeState.UP);
875
            
876
            // set the ping response to the current value
877
            Ping canPing = new Ping();
878
            canPing.setSuccess(false);
879
            try {
880
            	Date pingDate = ping();
881
                canPing.setSuccess(pingDate != null);
882
            } catch (BaseException e) {
883
                e.printStackTrace();
884
                // guess it can't be pinged
885
            }
886
            
887
            node.setPing(canPing);
888

    
889
            NodeReference identifier = new NodeReference();
890
            identifier.setValue(nodeId);
891
            node.setIdentifier(identifier);
892
            Subject s = new Subject();
893
            s.setValue(subject);
894
            node.addSubject(s);
895
            Subject contact = new Subject();
896
            contact.setValue(contactSubject);
897
            node.addContactSubject(contact);
898
            node.setName(nodeName);
899
            node.setReplicate(nodeReplicate);
900
            node.setSynchronize(nodeSynchronize);
901

    
902
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
903
            Services services = new Services();
904

    
905
            Service sMNCore = new Service();
906
            sMNCore.setName("MNCore");
907
            sMNCore.setVersion(mnCoreServiceVersion);
908
            sMNCore.setAvailable(mnCoreServiceAvailable);
909

    
910
            Service sMNRead = new Service();
911
            sMNRead.setName("MNRead");
912
            sMNRead.setVersion(mnReadServiceVersion);
913
            sMNRead.setAvailable(mnReadServiceAvailable);
914

    
915
            Service sMNAuthorization = new Service();
916
            sMNAuthorization.setName("MNAuthorization");
917
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
918
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
919

    
920
            Service sMNStorage = new Service();
921
            sMNStorage.setName("MNStorage");
922
            sMNStorage.setVersion(mnStorageServiceVersion);
923
            sMNStorage.setAvailable(mnStorageServiceAvailable);
924

    
925
            Service sMNReplication = new Service();
926
            sMNReplication.setName("MNReplication");
927
            sMNReplication.setVersion(mnReplicationServiceVersion);
928
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
929

    
930
            services.addService(sMNRead);
931
            services.addService(sMNCore);
932
            services.addService(sMNAuthorization);
933
            services.addService(sMNStorage);
934
            services.addService(sMNReplication);
935
            node.setServices(services);
936

    
937
            // Set the schedule for synchronization
938
            Synchronization synchronization = new Synchronization();
939
            Schedule schedule = new Schedule();
940
            Date now = new Date();
941
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
942
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
943
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
944
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
945
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
946
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
947
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
948
            synchronization.setSchedule(schedule);
949
            synchronization.setLastHarvested(now);
950
            synchronization.setLastCompleteHarvest(now);
951
            node.setSynchronization(synchronization);
952

    
953
            node.setType(nodeType);
954
            return node;
955

    
956
        } catch (PropertyNotFoundException pnfe) {
957
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
958
            logMetacat.error(msg);
959
            throw new ServiceFailure("2162", msg);
960
        }
961
    }
962

    
963
    /**
964
     * Returns the number of operations that have been serviced by the node 
965
     * over time periods of one and 24 hours.
966
     * 
967
     * @param session - the Session object containing the credentials for the Subject
968
     * @param period - An ISO8601 compatible DateTime range specifying the time 
969
     *                 range for which to return operation statistics.
970
     * @param requestor - Limit to operations performed by given requestor identity.
971
     * @param event -  Enumerated value indicating the type of event being examined
972
     * @param format - Limit to events involving objects of the specified format
973
     * 
974
     * @return the desired log records
975
     * 
976
     * @throws InvalidToken
977
     * @throws ServiceFailure
978
     * @throws NotAuthorized
979
     * @throws InvalidRequest
980
     * @throws NotImplemented
981
     */
982
    public MonitorList getOperationStatistics(Session session, Date startTime, 
983
        Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
984
        throws NotImplemented, ServiceFailure, NotAuthorized, InsufficientResources, UnsupportedType {
985

    
986
        MonitorList monitorList = new MonitorList();
987

    
988
        try {
989

    
990
            // get log records first
991
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
992

    
993
            // TODO: aggregate by day or hour -- needs clarification
994
            int count = 1;
995
            for (LogEntry logEntry : logs.getLogEntryList()) {
996
                Identifier pid = logEntry.getIdentifier();
997
                Date logDate = logEntry.getDateLogged();
998
                // if we are filtering by format
999
                if (formatId != null) {
1000
                    SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1001
                    if (!sysmeta.getFormatId().getValue().equals(formatId.getValue())) {
1002
                        // does not match
1003
                        continue;
1004
                    }
1005
                }
1006
                MonitorInfo item = new MonitorInfo();
1007
                item.setCount(count);
1008
                item.setDate(new java.sql.Date(logDate.getTime()));
1009
                monitorList.addMonitorInfo(item);
1010

    
1011
            }
1012
        } catch (Exception e) {
1013
            e.printStackTrace();
1014
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
1015
        }
1016

    
1017
        return monitorList;
1018

    
1019
    }
1020

    
1021
    /**
1022
     * A callback method used by a CN to indicate to a MN that it cannot 
1023
     * complete synchronization of the science metadata identified by pid.  Log
1024
     * the event in the metacat event log.
1025
     * 
1026
     * @param session
1027
     * @param syncFailed
1028
     * 
1029
     * @throws ServiceFailure
1030
     * @throws NotAuthorized
1031
     * @throws NotImplemented
1032
     */
1033
    @Override
1034
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
1035
        throws NotImplemented, ServiceFailure, NotAuthorized {
1036

    
1037
        String localId;
1038
        Identifier pid;
1039
        if ( syncFailed.getPid() != null ) {
1040
            pid = new Identifier();
1041
            pid.setValue(syncFailed.getPid());
1042
            boolean allowed;
1043
            
1044
            //are we allowed? only CNs
1045
            try {
1046
                allowed = isAdminAuthorized(session);
1047
                if ( !allowed ){
1048
                    throw new NotAuthorized("2162", 
1049
                            "Not allowed to call synchronizationFailed() on this node.");
1050
                }
1051
            } catch (InvalidToken e) {
1052
                throw new NotAuthorized("2162", 
1053
                        "Not allowed to call synchronizationFailed() on this node.");
1054

    
1055
            }
1056
            
1057
        } else {
1058
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1059

    
1060
        }
1061
        
1062
        try {
1063
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1064
        } catch (McdbDocNotFoundException e) {
1065
            throw new ServiceFailure("2161", "The identifier specified by " + 
1066
                    syncFailed.getPid() + " was not found on this node.");
1067

    
1068
        }
1069
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1070
        // method is changed to include the URL as a parameter
1071
        logMetacat.debug("Synchronization for the object identified by " + 
1072
                pid.getValue() + " failed from " + syncFailed.getNodeId() + 
1073
                " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
1074
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1075
        String principal = Constants.SUBJECT_PUBLIC;
1076
        if (session != null && session.getSubject() != null) {
1077
          principal = session.getSubject().getValue();
1078
        }
1079
        try {
1080
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1081
        } catch (Exception e) {
1082
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1083
        }
1084
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
1085
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1086
        return true;
1087

    
1088
    }
1089

    
1090
    /**
1091
     * Essentially a get() but with different logging behavior
1092
     */
1093
    @Override
1094
    public InputStream getReplica(Session session, Identifier pid) 
1095
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1096

    
1097
        logMetacat.info("MNodeService.getReplica() called.");
1098

    
1099
        // cannot be called by public
1100
        if (session == null) {
1101
        	throw new InvalidToken("2183", "No session was provided.");
1102
        }
1103
        
1104
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1105
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1106
             "\tIdentifier           = " + pid.getValue());
1107

    
1108
        InputStream inputStream = null; // bytes to be returned
1109
        handler = new MetacatHandler(new Timer());
1110
        boolean allowed = false;
1111
        String localId; // the metacat docid for the pid
1112

    
1113
        // get the local docid from Metacat
1114
        try {
1115
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1116
        } catch (McdbDocNotFoundException e) {
1117
            throw new ServiceFailure("2181", "The object specified by " + 
1118
                    pid.getValue() + " does not exist at this node.");
1119
            
1120
        }
1121

    
1122
        Subject targetNodeSubject = session.getSubject();
1123

    
1124
        // check for authorization to replicate, null session to act as this source MN
1125
        try {
1126
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1127
        } catch (InvalidToken e1) {
1128
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1129
                + e1.getMessage());
1130
            
1131
        } catch (NotFound e1) {
1132
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1133
                    + e1.getMessage());
1134

    
1135
        } catch (InvalidRequest e1) {
1136
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1137
                    + e1.getMessage());
1138

    
1139
        }
1140

    
1141
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1142
            " for identifier " + pid.getValue());
1143

    
1144
        // if the person is authorized, perform the read
1145
        if (allowed) {
1146
            try {
1147
                inputStream = MetacatHandler.read(localId);
1148
            } catch (Exception e) {
1149
                throw new ServiceFailure("1020", "The object specified by " + 
1150
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1151
            }
1152
        }
1153

    
1154
        // if we fail to set the input stream
1155
        if (inputStream == null) {
1156
            throw new ServiceFailure("2181", "The object specified by " + 
1157
                pid.getValue() + "does not exist at this node.");
1158
        }
1159

    
1160
        // log the replica event
1161
        String principal = null;
1162
        if (session.getSubject() != null) {
1163
            principal = session.getSubject().getValue();
1164
        }
1165
        EventLog.getInstance().log(request.getRemoteAddr(), 
1166
            request.getHeader("User-Agent"), principal, localId, "replicate");
1167

    
1168
        return inputStream;
1169
    }
1170

    
1171
    /**
1172
     * A method to notify the Member Node that the authoritative copy of 
1173
     * system metadata on the Coordinating Nodes has changed.
1174
     * 
1175
     * @param session   Session information that contains the identity of the 
1176
     *                  calling user as retrieved from the X.509 certificate 
1177
     *                  which must be traceable to the CILogon service.
1178
     * @param serialVersion   The serialVersion of the system metadata
1179
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1180
     * @throws NotImplemented
1181
     * @throws ServiceFailure
1182
     * @throws NotAuthorized
1183
     * @throws InvalidRequest
1184
     * @throws InvalidToken
1185
     */
1186
    public boolean systemMetadataChanged(Session session, Identifier pid,
1187
        long serialVersion, Date dateSysMetaLastModified) 
1188
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1189
        InvalidToken {
1190
        
1191
        // cannot be called by public
1192
        if (session == null) {
1193
        	throw new InvalidToken("2183", "No session was provided.");
1194
        }
1195

    
1196
        SystemMetadata currentLocalSysMeta = null;
1197
        SystemMetadata newSysMeta = null;
1198
        CNode cn = D1Client.getCN();
1199
        NodeList nodeList = null;
1200
        Subject callingSubject = null;
1201
        boolean allowed = false;
1202
        
1203
        // are we allowed to call this?
1204
        callingSubject = session.getSubject();
1205
        nodeList = cn.listNodes();
1206
        
1207
        for(Node node : nodeList.getNodeList()) {
1208
            // must be a CN
1209
            if ( node.getType().equals(NodeType.CN)) {
1210
               List<Subject> subjectList = node.getSubjectList();
1211
               // the calling subject must be in the subject list
1212
               if ( subjectList.contains(callingSubject)) {
1213
                   allowed = true;
1214
                   
1215
               }
1216
               
1217
            }
1218
        }
1219
        
1220
        if (!allowed ) {
1221
            String msg = "The subject identified by " + callingSubject.getValue() +
1222
              " is not authorized to call this service.";
1223
            throw new NotAuthorized("1331", msg);
1224
            
1225
        }
1226
        
1227
        // compare what we have locally to what is sent in the change notification
1228
        try {
1229
            currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1230
             
1231
        } catch (RuntimeException e) {
1232
            String msg = "SystemMetadata for pid " + pid.getValue() +
1233
              " couldn't be updated because it couldn't be found locally: " +
1234
              e.getMessage();
1235
            logMetacat.error(msg);
1236
            ServiceFailure sf = new ServiceFailure("1333", msg);
1237
            sf.initCause(e);
1238
            throw sf; 
1239
        }
1240
        
1241
        if (currentLocalSysMeta.getSerialVersion().longValue() < serialVersion ) {
1242
            try {
1243
                newSysMeta = cn.getSystemMetadata(null, pid);
1244
            } catch (NotFound e) {
1245
                // huh? you just said you had it
1246
            	String msg = "On updating the local copy of system metadata " + 
1247
                "for pid " + pid.getValue() +", the CN reports it is not found." +
1248
                " The error message was: " + e.getMessage();
1249
                logMetacat.error(msg);
1250
                ServiceFailure sf = new ServiceFailure("1333", msg);
1251
                sf.initCause(e);
1252
                throw sf;
1253
            }
1254
            
1255
            // update the local copy of system metadata for the pid
1256
            try {
1257
                HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1258
                logMetacat.info("Updated local copy of system metadata for pid " +
1259
                    pid.getValue() + " after change notification from the CN.");
1260
                
1261
                // TODO: consider inspecting the change for archive
1262
                // see: https://projects.ecoinformatics.org/ecoinfo/issues/6417
1263
//                if (newSysMeta.getArchived() != null && newSysMeta.getArchived().booleanValue()) {
1264
//                	try {
1265
//						this.archive(session, newSysMeta.getIdentifier());
1266
//					} catch (NotFound e) {
1267
//						// do we care? nothing to do about it now
1268
//						logMetacat.error(e.getMessage(), e);
1269
//					}
1270
//                }
1271
                
1272
            } catch (RuntimeException e) {
1273
                String msg = "SystemMetadata for pid " + pid.getValue() +
1274
                  " couldn't be updated: " +
1275
                  e.getMessage();
1276
                logMetacat.error(msg);
1277
                ServiceFailure sf = new ServiceFailure("1333", msg);
1278
                sf.initCause(e);
1279
                throw sf;
1280
            }
1281
            
1282
            // submit for indexing
1283
            try {
1284
				MetacatSolrIndex.getInstance().submit(newSysMeta.getIdentifier(), newSysMeta, null, true);
1285
			} catch (Exception e) {
1286
                logMetacat.error("Could not submit changed systemMetadata for indexing, pid: " + newSysMeta.getIdentifier().getValue(), e);
1287
			}
1288
        }
1289
        
1290
        return true;
1291
        
1292
    }
1293
    
1294
    /*
1295
     * Set the replication status for the object on the Coordinating Node
1296
     * 
1297
     * @param session - the session for the this target node
1298
     * @param pid - the identifier of the object being updated
1299
     * @param nodeId - the identifier of this target node
1300
     * @param status - the replication status to set
1301
     * @param failure - the exception to include, if any
1302
     */
1303
    private void setReplicationStatus(Session session, Identifier pid, 
1304
        NodeReference nodeId, ReplicationStatus status, BaseException failure) 
1305
        throws ServiceFailure, NotImplemented, NotAuthorized, 
1306
        InvalidRequest {
1307
        
1308
        // call the CN as the MN to set the replication status
1309
        try {
1310
            this.cn = D1Client.getCN();
1311
            this.cn.setReplicationStatus(session, pid, nodeId,
1312
                    status, failure);
1313
            
1314
        } catch (InvalidToken e) {
1315
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (InvalidToken): " + e.getMessage();
1316
            logMetacat.error(msg);
1317
        	throw new ServiceFailure("2151",
1318
                    msg);
1319
            
1320
        } catch (NotFound e) {
1321
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (NotFound): " + e.getMessage();
1322
            logMetacat.error(msg);
1323
        	throw new ServiceFailure("2151",
1324
                    msg);
1325
            
1326
        }
1327
    }
1328
    
1329
    private SystemMetadata makePublicIfNot(SystemMetadata sysmeta, Identifier pid) throws ServiceFailure, InvalidToken, NotFound, NotImplemented, InvalidRequest {
1330
    	// check if it is publicly readable
1331
		boolean isPublic = false;
1332
		Subject publicSubject = new Subject();
1333
		publicSubject.setValue(Constants.SUBJECT_PUBLIC);
1334
		Session publicSession = new Session();
1335
		publicSession.setSubject(publicSubject);
1336
		AccessRule publicRule = new AccessRule();
1337
		publicRule.addPermission(Permission.READ);
1338
		publicRule.addSubject(publicSubject);
1339
		
1340
		// see if we need to add the rule
1341
		try {
1342
			isPublic = this.isAuthorized(publicSession, pid, Permission.READ);
1343
		} catch (NotAuthorized na) {
1344
			// well, certainly not authorized for public read!
1345
		}
1346
		if (!isPublic) {
1347
			sysmeta.getAccessPolicy().addAllow(publicRule);
1348
		}
1349
		
1350
		return sysmeta;
1351
    }
1352

    
1353
	@Override
1354
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1355
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1356
			InvalidRequest {
1357
		
1358
		// check for null session
1359
        if (session == null) {
1360
          throw new InvalidToken("2190", "Session is required to generate an Identifier at this Node.");
1361
        }
1362
		
1363
		Identifier identifier = new Identifier();
1364
		
1365
		// handle different schemes
1366
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1367
			// UUID
1368
			UUID uuid = UUID.randomUUID();
1369
            identifier.setValue(UUID_PREFIX + uuid.toString());
1370
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1371
			// generate a DOI
1372
			try {
1373
				identifier = DOIService.getInstance().generateDOI();
1374
			} catch (EZIDException e) {
1375
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1376
				sf.initCause(e);
1377
				throw sf;
1378
			}
1379
		} else {
1380
			// default if we don't know the scheme
1381
			if (fragment != null) {
1382
				// for now, just autogen with fragment
1383
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1384
				identifier.setValue(autogenId);			
1385
			} else {
1386
				// autogen with no fragment
1387
				String autogenId = DocumentUtil.generateDocumentId(0);
1388
				identifier.setValue(autogenId);
1389
			}
1390
		}
1391
		
1392
		// TODO: reserve the identifier with the CN. We can only do this when
1393
		// 1) the MN is part of a CN cluster
1394
		// 2) the request is from an authenticated user
1395
		
1396
		return identifier;
1397
	}
1398

    
1399
	@Override
1400
	public boolean isAuthorized(Identifier pid, Permission permission)
1401
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1402
			NotAuthorized, NotImplemented {
1403

    
1404
		return isAuthorized(null, pid, permission);
1405
	}
1406

    
1407
	@Override
1408
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1409
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1410
			InvalidRequest {
1411

    
1412
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1413
	}
1414

    
1415
	@Override
1416
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1417
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1418
			NotAuthorized, NotImplemented, ServiceFailure {
1419

    
1420
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1421
	}
1422

    
1423
	@Override
1424
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1425
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1426

    
1427
		return describe(null, pid);
1428
	}
1429

    
1430
	@Override
1431
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
1432
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
1433

    
1434
		return get(null, pid);
1435
	}
1436

    
1437
	@Override
1438
	public Checksum getChecksum(Identifier pid, String algorithm)
1439
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1440
			ServiceFailure, NotFound {
1441

    
1442
		return getChecksum(null, pid, algorithm);
1443
	}
1444

    
1445
	@Override
1446
	public SystemMetadata getSystemMetadata(Identifier pid)
1447
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1448
			NotFound {
1449

    
1450
		return getSystemMetadata(null, pid);
1451
	}
1452

    
1453
	@Override
1454
	public ObjectList listObjects(Date startTime, Date endTime,
1455
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1456
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1457
			NotImplemented, ServiceFailure {
1458

    
1459
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1460
	}
1461

    
1462
	@Override
1463
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1464
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1465

    
1466
		return synchronizationFailed(null, syncFailed);
1467
	}
1468

    
1469
	@Override
1470
	public InputStream getReplica(Identifier pid) throws InvalidToken,
1471
			NotAuthorized, NotImplemented, ServiceFailure, NotFound,
1472
			InsufficientResources {
1473

    
1474
		return getReplica(null, pid);
1475
	}
1476

    
1477
	@Override
1478
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1479
			throws NotImplemented, ServiceFailure, NotAuthorized,
1480
			InvalidRequest, InvalidToken, InsufficientResources,
1481
			UnsupportedType {
1482

    
1483
		return replicate(null, sysmeta, sourceNode);
1484
	}
1485

    
1486
	@Override
1487
	public Identifier create(Identifier pid, InputStream object,
1488
			SystemMetadata sysmeta) throws IdentifierNotUnique,
1489
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1490
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1491
			UnsupportedType {
1492

    
1493
		return create(null, pid, object, sysmeta);
1494
	}
1495

    
1496
	@Override
1497
	public Identifier delete(Identifier pid) throws InvalidToken,
1498
			ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1499

    
1500
		return delete(null, pid);
1501
	}
1502

    
1503
	@Override
1504
	public Identifier generateIdentifier(String scheme, String fragment)
1505
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1506
			InvalidRequest {
1507

    
1508
		return generateIdentifier(null, scheme, fragment);
1509
	}
1510

    
1511
	@Override
1512
	public Identifier update(Identifier pid, InputStream object,
1513
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
1514
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1515
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1516
			UnsupportedType, NotFound {
1517

    
1518
		return update(null, pid, object, newPid, sysmeta);
1519
	}
1520

    
1521
	@Override
1522
	public QueryEngineDescription getQueryEngineDescription(String engine)
1523
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1524
			NotFound {
1525
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1526
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1527
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1528
            }
1529
	        QueryEngineDescription qed = new QueryEngineDescription();
1530
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1531
	        qed.setQueryEngineVersion("1.0");
1532
	        qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1533
	        Vector<String> pathsForIndexing = null;
1534
	        try {
1535
	            pathsForIndexing = SystemUtil.getPathsForIndexing();
1536
	        } catch (MetacatUtilException e) {
1537
	            logMetacat.warn("Could not get index paths", e);
1538
	        }
1539
	        for (String fieldName: pathsForIndexing) {
1540
	            QueryField field = new QueryField();
1541
	            field.addDescription("Indexed field for path '" + fieldName + "'");
1542
	            field.setName(fieldName);
1543
	            field.setReturnable(true);
1544
	            field.setSearchable(true);
1545
	            field.setSortable(false);
1546
	            // TODO: determine type and multivaluedness
1547
	            field.setType(String.class.getName());
1548
	            //field.setMultivalued(true);
1549
	            qed.addQueryField(field);
1550
	        }
1551
	        return qed;
1552
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1553
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1554
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1555
            }
1556
	        try {
1557
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1558
	            return qed;
1559
	        } catch (Exception e) {
1560
	            e.printStackTrace();
1561
	            throw new ServiceFailure("Solr server error", e.getMessage());
1562
	        }
1563
	    } else {
1564
	        throw new NotFound("404", "The Metacat member node can't find the query engine - "+engine);
1565
	    }
1566
		
1567
	}
1568

    
1569
	@Override
1570
	public QueryEngineList listQueryEngines() throws InvalidToken,
1571
			ServiceFailure, NotAuthorized, NotImplemented {
1572
		QueryEngineList qel = new QueryEngineList();
1573
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1574
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1575
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1576
		for(String name : enables) {
1577
		    qel.addQueryEngine(name);
1578
		}
1579
		return qel;
1580
	}
1581

    
1582
	@Override
1583
	public InputStream query(String engine, String query) throws InvalidToken,
1584
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1585
			NotFound {
1586
	    String user = Constants.SUBJECT_PUBLIC;
1587
        String[] groups= null;
1588
        Set<Subject> subjects = null;
1589
        if (session != null) {
1590
            user = session.getSubject().getValue();
1591
            subjects = AuthUtils.authorizedClientSubjects(session);
1592
            if (subjects != null) {
1593
                List<String> groupList = new ArrayList<String>();
1594
                for (Subject subject: subjects) {
1595
                    groupList.add(subject.getValue());
1596
                }
1597
                groups = groupList.toArray(new String[0]);
1598
            }
1599
        } else {
1600
            //add the public user subject to the set 
1601
            Subject subject = new Subject();
1602
            subject.setValue(Constants.SUBJECT_PUBLIC);
1603
            subjects = new HashSet<Subject>();
1604
            subjects.add(subject);
1605
        }
1606
        //System.out.println("====== user is "+user);
1607
        //System.out.println("====== groups are "+groups);
1608
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1609
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1610
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1611
            }
1612
			try {
1613
				DBQuery queryobj = new DBQuery();
1614
				
1615
				String results = queryobj.performPathquery(query, user, groups);
1616
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1617
				ctbais.setContentType("text/xml");
1618
				return ctbais;
1619

    
1620
			} catch (Exception e) {
1621
				throw new ServiceFailure("Pathquery error", e.getMessage());
1622
			}
1623
			
1624
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1625
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1626
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1627
		    }
1628
		    logMetacat.info("The query is ==================================== \n"+query);
1629
		    try {
1630
		        
1631
                return MetacatSolrIndex.getInstance().query(query, subjects);
1632
            } catch (Exception e) {
1633
                // TODO Auto-generated catch block
1634
                throw new ServiceFailure("Solr server error", e.getMessage());
1635
            } 
1636
		}
1637
		return null;
1638
	}
1639
	
1640
	/**
1641
	 * Given an existing Science Metadata PID, this method mints a DOI
1642
	 * and updates the original object "publishing" the update with the DOI.
1643
	 * This includes updating the ORE map that describes the Science Metadata+data.
1644
	 * TODO: ensure all referenced objects allow public read
1645
	 * 
1646
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1647
	 * 
1648
	 * @param originalIdentifier
1649
	 * @param request
1650
	 * @throws InvalidRequest 
1651
	 * @throws NotImplemented 
1652
	 * @throws NotAuthorized 
1653
	 * @throws ServiceFailure 
1654
	 * @throws InvalidToken 
1655
	 * @throws NotFound
1656
	 * @throws InvalidSystemMetadata 
1657
	 * @throws InsufficientResources 
1658
	 * @throws UnsupportedType 
1659
	 * @throws IdentifierNotUnique 
1660
	 */
1661
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata {
1662
		
1663
		
1664
		// get the original SM
1665
		SystemMetadata originalSystemMetadata = this.getSystemMetadata(session, originalIdentifier);
1666

    
1667
		// make copy of it using the marshaller to ensure DEEP copy
1668
		SystemMetadata sysmeta = null;
1669
		try {
1670
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
1671
			TypeMarshaller.marshalTypeToOutputStream(originalSystemMetadata, baos);
1672
			sysmeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1673
		} catch (Exception e) {
1674
			// report as service failure
1675
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1676
			sf.initCause(e);
1677
			throw sf;
1678
		}
1679

    
1680
		// mint a DOI for the new revision
1681
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1682
				
1683
		// set new metadata values
1684
		sysmeta.setIdentifier(newIdentifier);
1685
		sysmeta.setObsoletes(originalIdentifier);
1686
		sysmeta.setObsoletedBy(null);
1687
		
1688
		// ensure it is publicly readable
1689
		sysmeta = makePublicIfNot(sysmeta, originalIdentifier);
1690
		
1691
		// get the bytes
1692
		InputStream inputStream = this.get(session, originalIdentifier);
1693
		
1694
		// update the object
1695
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
1696
		
1697
		// update ORE that references the scimeta
1698
		// first try the naive method, then check the SOLR index
1699
		try {
1700
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1701
			
1702
			Identifier potentialOreIdentifier = new Identifier();
1703
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1704
			
1705
			InputStream oreInputStream = null;
1706
			try {
1707
				oreInputStream = this.get(session, potentialOreIdentifier);
1708
			} catch (NotFound nf) {
1709
				// this is probably okay for many sci meta data docs
1710
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1711
				// try the SOLR index
1712
				List<Identifier> potentialOreIdentifiers = this.lookupOreFor(originalIdentifier, false);
1713
				if (potentialOreIdentifiers != null) {
1714
					potentialOreIdentifier = potentialOreIdentifiers.get(0);
1715
					try {
1716
						oreInputStream = this.get(session, potentialOreIdentifier);
1717
					} catch (NotFound nf2) {
1718
						// this is probably okay for many sci meta data docs
1719
						logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1720
					}
1721
				}
1722
			}
1723
			if (oreInputStream != null) {
1724
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1725
	
1726
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1727
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1728
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1729
					
1730
				// reconstruct the ORE with the new identifiers
1731
				sciMetaMap.remove(originalIdentifier);
1732
				sciMetaMap.put(newIdentifier, dataIdentifiers);
1733
				
1734
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1735
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1736
				
1737
				// get the original ORE SM and update the values
1738
				SystemMetadata originalOreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1739
				SystemMetadata oreSysMeta = new SystemMetadata();
1740
				try {
1741
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
1742
					TypeMarshaller.marshalTypeToOutputStream(originalOreSysMeta, baos);
1743
					oreSysMeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1744
				} catch (Exception e) {
1745
					// report as service failure
1746
					ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1747
					sf.initCause(e);
1748
					throw sf;
1749
				}
1750

    
1751
				oreSysMeta.setIdentifier(newOreIdentifier);
1752
				oreSysMeta.setObsoletes(potentialOreIdentifier);
1753
				oreSysMeta.setObsoletedBy(null);
1754
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
1755
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
1756
				
1757
				// ensure ORE is publicly readable
1758
				oreSysMeta = makePublicIfNot(oreSysMeta, potentialOreIdentifier);
1759
				
1760
				// ensure all data objects allow public read
1761
				List<String> pidsToSync = new ArrayList<String>();
1762
				for (Identifier dataId: dataIdentifiers) {
1763
					SystemMetadata dataSysMeta = this.getSystemMetadata(session, dataId);
1764
					dataSysMeta = makePublicIfNot(dataSysMeta, dataId);
1765
					this.updateSystemMetadata(dataSysMeta);
1766
					pidsToSync.add(dataId.getValue());
1767
				}
1768
				SyncAccessPolicy sap = new SyncAccessPolicy();
1769
				try {
1770
					sap.sync(pidsToSync);
1771
				} catch (Exception e) {
1772
					// ignore
1773
					logMetacat.warn("Error attempting to sync access for data objects when publishing package");
1774
				}
1775
				
1776
				// save the updated ORE
1777
				this.update(
1778
						session, 
1779
						potentialOreIdentifier, 
1780
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")), 
1781
						newOreIdentifier, 
1782
						oreSysMeta);
1783
				
1784
			} else {
1785
				// create a new ORE for them
1786
				// https://projects.ecoinformatics.org/ecoinfo/issues/6194
1787
				try {
1788
					// find the local id for the NEW package.
1789
					String newLocalId = IdentifierManager.getInstance().getLocalId(newIdentifier.getValue());
1790
	
1791
					@SuppressWarnings("unused")
1792
					SystemMetadata extraSysMeta = SystemMetadataFactory.createSystemMetadata(newLocalId, true, false);
1793
					// should be done generating the ORE here, and the same permissions were used from the metadata object
1794
					
1795
				} catch (Exception e) {
1796
					// oops, guess there was a problem - no package for you
1797
					logMetacat.error("Could not generate new ORE for published object: " + newIdentifier.getValue(), e);
1798
				}
1799
			}
1800
		} catch (McdbDocNotFoundException e) {
1801
			// report as service failure
1802
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1803
			sf.initCause(e);
1804
			throw sf;
1805
		} catch (UnsupportedEncodingException e) {
1806
			// report as service failure
1807
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1808
			sf.initCause(e);
1809
			throw sf;
1810
		} catch (OREException e) {
1811
			// report as service failure
1812
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1813
			sf.initCause(e);
1814
			throw sf;
1815
		} catch (URISyntaxException e) {
1816
			// report as service failure
1817
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1818
			sf.initCause(e);
1819
			throw sf;
1820
		} catch (OREParserException e) {
1821
			// report as service failure
1822
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1823
			sf.initCause(e);
1824
			throw sf;
1825
		} catch (ORESerialiserException e) {
1826
			// report as service failure
1827
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1828
			sf.initCause(e);
1829
			throw sf;
1830
		} catch (NoSuchAlgorithmException e) {
1831
			// report as service failure
1832
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1833
			sf.initCause(e);
1834
			throw sf;
1835
		}
1836
		
1837
		return newIdentifier;
1838
	}
1839
	
1840
	/**
1841
	 * Determines if we already have registered an ORE map for this package
1842
	 * NOTE: uses a solr query to locate OREs for the object
1843
	 * @param guid of the EML/packaging object
1844
	 * @return list of resource map identifiers for the given pid
1845
	 */
1846
	public List<Identifier> lookupOreFor(Identifier guid, boolean includeObsolete) {
1847
		// Search for the ORE if we can find it
1848
		String pid = guid.getValue();
1849
		List<Identifier> retList = null;
1850
		try {
1851
			String query = "fl=id,resourceMap&wt=xml&q=-obsoletedBy:[* TO *]+resourceMap:[* TO *]+id:\"" + pid + "\"";
1852
			if (includeObsolete) {
1853
				query = "fl=id,resourceMap&wt=xml&q=resourceMap:[* TO *]+id:\"" + pid + "\"";
1854
			}
1855
			
1856
			InputStream results = this.query("solr", query);
1857
			org.w3c.dom.Node rootNode = XMLUtilities.getXMLReaderAsDOMTreeRootNode(new InputStreamReader(results, "UTF-8"));
1858
			//String resultString = XMLUtilities.getDOMTreeAsString(rootNode);
1859
			org.w3c.dom.NodeList nodeList = XMLUtilities.getNodeListWithXPath(rootNode, "//arr[@name=\"resourceMap\"]/str");
1860
			if (nodeList != null && nodeList.getLength() > 0) {
1861
				retList = new ArrayList<Identifier>();
1862
				for (int i = 0; i < nodeList.getLength(); i++) {
1863
					String found = nodeList.item(i).getFirstChild().getNodeValue();
1864
					Identifier oreId = new Identifier();
1865
					oreId.setValue(found);
1866
					retList.add(oreId);
1867
				}
1868
			}
1869
		} catch (Exception e) {
1870
			logMetacat.error("Error checking for resourceMap[s] on pid " + pid + ". " + e.getMessage(), e);
1871
		}
1872
		
1873
		return retList;
1874
	}
1875
	
1876
	/**
1877
	 * Packages the given package in a Bagit collection for download
1878
	 * @param pid
1879
	 * @throws NotImplemented 
1880
	 * @throws NotFound 
1881
	 * @throws NotAuthorized 
1882
	 * @throws ServiceFailure 
1883
	 * @throws InvalidToken 
1884
	 */
1885
	public InputStream getPackage(Session session, Identifier pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1886
		
1887
		InputStream bagInputStream = null;
1888
		BagFactory bagFactory = new BagFactory();
1889
		Bag bag = bagFactory.createBag();
1890
		
1891
		// track the temp files we use so we can delete them when finished
1892
		List<File> tempFiles = new ArrayList<File>();
1893
		
1894
		// the pids to include in the package
1895
		List<Identifier> packagePids = new ArrayList<Identifier>();
1896
		
1897
		// catch non-D1 service errors and throw as ServiceFailures
1898
		try {
1899
			//Create a map of dataone ids and file names
1900
			Map<Identifier, String> fileNames = new HashMap<Identifier, String>();
1901
			
1902
			// find the package contents
1903
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1904
			if (ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
1905
				//Get the resource map as a map of Identifiers
1906
				InputStream oreInputStream = this.get(session, pid);
1907
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1908
				packagePids.addAll(resourceMapStructure.keySet());
1909
				//Loop through each object in this resource map
1910
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
1911
					//Loop through each metadata object in this entry
1912
					Set<Identifier> metadataIdentifiers = entries.keySet();
1913
					for(Identifier metadataID: metadataIdentifiers){
1914
						try{
1915
							//Get the system metadata for this metadata object
1916
							SystemMetadata metadataSysMeta = this.getSystemMetadata(session, metadataID);
1917
							
1918
							//If this is in eml format, extract the filename and GUID from each entity in its package
1919
							if (metadataSysMeta.getFormatId().getValue().startsWith("eml://")) {
1920
								//Get the package
1921
								DataPackageParserInterface parser = new Eml200DataPackageParser();
1922
								InputStream emlStream = this.get(session, metadataID);
1923
								parser.parse(emlStream);
1924
								DataPackage dataPackage = parser.getDataPackage();
1925
								
1926
								//Get all the entities in this package and loop through each to extract its ID and file name
1927
								Entity[] entities = dataPackage.getEntityList();
1928
								for(Entity entity: entities){
1929
									try{
1930
										//Get the file name from the metadata
1931
										String fileNameFromMetadata = entity.getName();
1932
										
1933
										//Get the ecogrid URL from the metadata
1934
										String ecogridIdentifier = entity.getEntityIdentifier();
1935
										//Parse the ecogrid URL to get the local id
1936
										String idFromMetadata = DocumentUtil.getAccessionNumberFromEcogridIdentifier(ecogridIdentifier);
1937
										
1938
										//Get the docid and rev pair
1939
										String docid = DocumentUtil.getDocIdFromString(idFromMetadata);
1940
										String rev = DocumentUtil.getRevisionStringFromString(idFromMetadata);
1941
										
1942
										//Get the GUID
1943
										String guid = IdentifierManager.getInstance().getGUID(docid, Integer.valueOf(rev));
1944
										Identifier dataIdentifier = new Identifier();
1945
										dataIdentifier.setValue(guid);
1946
										
1947
										//Add the GUID to our GUID & file name map
1948
										fileNames.put(dataIdentifier, fileNameFromMetadata);
1949
									}
1950
									catch(Exception e){
1951
										//Prevent just one entity error
1952
										e.printStackTrace();
1953
										logMetacat.debug(e.getMessage(), e);
1954
									}
1955
								}
1956
							}
1957
						}
1958
						catch(Exception e){
1959
							//Catch errors that would prevent package download
1960
							logMetacat.debug(e.toString());
1961
						}
1962
					}
1963
					packagePids.addAll(entries.keySet());
1964
					for (List<Identifier> dataPids: entries.values()) {
1965
						packagePids.addAll(dataPids);
1966
					}
1967
				}
1968
			} else {
1969
				// just the lone pid in this package
1970
				packagePids.add(pid);
1971
			}
1972
			
1973
			//Create a temp file, then delete it and make a directory with that name
1974
			File tempDir = File.createTempFile("temp", Long.toString(System.nanoTime()));
1975
			tempDir.delete();
1976
			tempDir = new File(tempDir.getPath() + "_dir");
1977
			tempDir.mkdir();			
1978
			tempFiles.add(tempDir);
1979
			
1980
			// track the pid-to-file mapping
1981
			StringBuffer pidMapping = new StringBuffer();
1982
			
1983
			// loop through the package contents
1984
			for (Identifier entryPid: packagePids) {
1985
				//Get the system metadata for each item
1986
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);					
1987
				
1988
				String objectFormatType = ObjectFormatCache.getInstance().getFormat(entrySysMeta.getFormatId()).getFormatType();
1989
				String fileName = null;
1990
				
1991
				//TODO: Be more specific of what characters to replace. Make sure periods arent replaced for the filename from metadata
1992
				//Our default file name is just the ID + format type (e.g. walker.1.1-DATA)
1993
				fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + objectFormatType;
1994

    
1995
				if(fileNames.containsKey(entryPid)){
1996
					//Let's use the file name and extension from the metadata is we have it
1997
					fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + fileNames.get(entryPid).replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
1998
				}
1999
				else{
2000
					//If we couldn't find a given file name, use the system metadata extension
2001
					String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
2002
					fileName += extension;
2003
				}
2004
				
2005
		        //Create a new file for this item and add to the list
2006
				File tempFile = new File(tempDir, fileName);
2007
				tempFiles.add(tempFile);
2008
				
2009
				InputStream entryInputStream = this.get(session, entryPid);			
2010
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
2011
				bag.addFileToPayload(tempFile);
2012
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
2013
			}
2014
			
2015
			//add the the pid to data file map
2016
			File pidMappingFile = new File(tempDir, "pid-mapping.txt");
2017
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
2018
			bag.addFileAsTag(pidMappingFile);
2019
			tempFiles.add(pidMappingFile);
2020
			
2021
			bag = bag.makeComplete();
2022
			
2023
			///Now create the zip file
2024
			//Use the pid as the file name prefix, replacing all non-word characters
2025
			String zipName = pid.getValue().replaceAll("\\W", "_");
2026
			
2027
			File bagFile = new File(tempDir, zipName+".zip");
2028
			
2029
			bag.setFile(bagFile);
2030
			ZipWriter zipWriter = new ZipWriter(bagFactory);
2031
			bag.write(zipWriter, bagFile);
2032
			bagFile = bag.getFile();
2033
			// use custom FIS that will delete the file when closed
2034
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
2035
			// also mark for deletion on shutdown in case the stream is never closed
2036
			bagFile.deleteOnExit();
2037
			tempFiles.add(bagFile);
2038
			
2039
			// clean up other temp files
2040
			for(int i=tempFiles.size()-1; i>=0; i--){
2041
				File tf = new File(tempFiles.get(i).getPath());
2042
				tf.delete();
2043
			}
2044
			
2045
		} catch (IOException e) {
2046
			// report as service failure
2047
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2048
			sf.initCause(e);
2049
			throw sf;
2050
		} catch (OREException e) {
2051
			// report as service failure
2052
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2053
			sf.initCause(e);
2054
			throw sf;
2055
		} catch (URISyntaxException e) {
2056
			// report as service failure
2057
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2058
			sf.initCause(e);
2059
			throw sf;
2060
		} catch (OREParserException e) {
2061
			// report as service failure
2062
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2063
			sf.initCause(e);
2064
			throw sf;
2065
		}
2066
		
2067
		return bagInputStream;
2068

    
2069
	}
2070
	
2071
	/**
2072
	 * Get a rendered view of the object identified by pid.
2073
	 * Uses the registered format given by the format parameter.
2074
	 * Typically, this is structured HTML that can be styled with CSS.
2075
	 * @param session
2076
	 * @param pid
2077
	 * @param format
2078
	 * @return
2079
	 * @throws InvalidToken
2080
	 * @throws ServiceFailure
2081
	 * @throws NotAuthorized
2082
	 * @throws NotFound
2083
	 * @throws NotImplemented
2084
	 */
2085
	public InputStream getView(Session session, Identifier pid, String format) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2086
		InputStream resultInputStream = null;
2087
		
2088
		SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2089
		InputStream object = this.get(session, pid);
2090

    
2091
		try {
2092
			// can only transform metadata, really
2093
			ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2094
			if (objectFormat.getFormatType().equals("METADATA")) {
2095
				// transform
2096
				DBTransform transformer = new DBTransform();
2097
	            String documentContent = IOUtils.toString(object, "UTF-8");
2098
	            String sourceType = objectFormat.getFormatId().getValue();
2099
	            String targetType = "-//W3C//HTML//EN";
2100
	            ByteArrayOutputStream baos = new ByteArrayOutputStream();
2101
	            Writer writer = new OutputStreamWriter(baos , "UTF-8");
2102
	            // TODO: include more params?
2103
	            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2104
	            String localId = null;
2105
				try {
2106
					localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2107
				} catch (McdbDocNotFoundException e) {
2108
					throw new NotFound("1020", e.getMessage());
2109
				}
2110
	            params.put("qformat", new String[] {format});	            
2111
	            params.put("docid", new String[] {localId});
2112
	            params.put("pid", new String[] {pid.getValue()});
2113
	            transformer.transformXMLDocument(
2114
	                    documentContent , 
2115
	                    sourceType, 
2116
	                    targetType , 
2117
	                    format, 
2118
	                    writer, 
2119
	                    params, 
2120
	                    null //sessionid
2121
	                    );
2122
	            
2123
	            // finally, get the HTML back
2124
	            resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2125
	            ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2126
	
2127
			} else {
2128
				// just return the raw bytes
2129
				resultInputStream = object;
2130
			}
2131
		} catch (IOException e) {
2132
			// report as service failure
2133
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2134
			sf.initCause(e);
2135
			throw sf;
2136
		} catch (PropertyNotFoundException e) {
2137
			// report as service failure
2138
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2139
			sf.initCause(e);
2140
			throw sf;
2141
		} catch (SQLException e) {
2142
			// report as service failure
2143
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2144
			sf.initCause(e);
2145
			throw sf;
2146
		} catch (ClassNotFoundException e) {
2147
			// report as service failure
2148
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2149
			sf.initCause(e);
2150
			throw sf;
2151
		}
2152
		
2153
		return resultInputStream;
2154
		
2155
	}	
2156
    
2157
}
(4-4/7)