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.FileInputStream;
30
import java.io.FileOutputStream;
31
import java.io.IOException;
32
import java.io.InputStream;
33
import java.io.InputStreamReader;
34
import java.io.OutputStreamWriter;
35
import java.io.UnsupportedEncodingException;
36
import java.io.Writer;
37
import java.math.BigInteger;
38
import java.net.URISyntaxException;
39
import java.security.NoSuchAlgorithmException;
40
import java.sql.SQLException;
41
import java.util.ArrayList;
42
import java.util.Calendar;
43
import java.util.Date;
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.beanutils.BeanUtils;
56
import org.apache.commons.io.IOUtils;
57
import org.apache.log4j.Logger;
58
import org.apache.wicket.protocol.http.mock.MockHttpServletRequest;
59
import org.dataone.client.CNode;
60
import org.dataone.client.D1Client;
61
import org.dataone.client.MNode;
62
import org.dataone.client.ObjectFormatCache;
63
import org.dataone.client.auth.CertificateManager;
64
import org.dataone.client.formats.ObjectFormatInfo;
65
import org.dataone.configuration.Settings;
66
import org.dataone.ore.ResourceMapFactory;
67
import org.dataone.service.exceptions.BaseException;
68
import org.dataone.service.exceptions.IdentifierNotUnique;
69
import org.dataone.service.exceptions.InsufficientResources;
70
import org.dataone.service.exceptions.InvalidRequest;
71
import org.dataone.service.exceptions.InvalidSystemMetadata;
72
import org.dataone.service.exceptions.InvalidToken;
73
import org.dataone.service.exceptions.NotAuthorized;
74
import org.dataone.service.exceptions.NotFound;
75
import org.dataone.service.exceptions.NotImplemented;
76
import org.dataone.service.exceptions.ServiceFailure;
77
import org.dataone.service.exceptions.SynchronizationFailed;
78
import org.dataone.service.exceptions.UnsupportedType;
79
import org.dataone.service.mn.tier1.v1.MNCore;
80
import org.dataone.service.mn.tier1.v1.MNRead;
81
import org.dataone.service.mn.tier2.v1.MNAuthorization;
82
import org.dataone.service.mn.tier3.v1.MNStorage;
83
import org.dataone.service.mn.tier4.v1.MNReplication;
84
import org.dataone.service.mn.v1.MNQuery;
85
import org.dataone.service.types.v1.Checksum;
86
import org.dataone.service.types.v1.DescribeResponse;
87
import org.dataone.service.types.v1.Event;
88
import org.dataone.service.types.v1.Identifier;
89
import org.dataone.service.types.v1.Log;
90
import org.dataone.service.types.v1.LogEntry;
91
import org.dataone.service.types.v1.MonitorInfo;
92
import org.dataone.service.types.v1.MonitorList;
93
import org.dataone.service.types.v1.Node;
94
import org.dataone.service.types.v1.NodeList;
95
import org.dataone.service.types.v1.NodeReference;
96
import org.dataone.service.types.v1.NodeState;
97
import org.dataone.service.types.v1.NodeType;
98
import org.dataone.service.types.v1.ObjectFormat;
99
import org.dataone.service.types.v1.ObjectFormatIdentifier;
100
import org.dataone.service.types.v1.ObjectList;
101
import org.dataone.service.types.v1.Permission;
102
import org.dataone.service.types.v1.Ping;
103
import org.dataone.service.types.v1.ReplicationStatus;
104
import org.dataone.service.types.v1.Schedule;
105
import org.dataone.service.types.v1.Service;
106
import org.dataone.service.types.v1.Services;
107
import org.dataone.service.types.v1.Session;
108
import org.dataone.service.types.v1.Subject;
109
import org.dataone.service.types.v1.Synchronization;
110
import org.dataone.service.types.v1.SystemMetadata;
111
import org.dataone.service.types.v1.util.AuthUtils;
112
import org.dataone.service.types.v1.util.ChecksumUtil;
113
import org.dataone.service.types.v1_1.QueryEngineDescription;
114
import org.dataone.service.types.v1_1.QueryEngineList;
115
import org.dataone.service.types.v1_1.QueryField;
116
import org.dataone.service.util.Constants;
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

    
122
import edu.ucsb.nceas.ezid.EZIDException;
123
import edu.ucsb.nceas.metacat.DBQuery;
124
import edu.ucsb.nceas.metacat.DBTransform;
125
import edu.ucsb.nceas.metacat.EventLog;
126
import edu.ucsb.nceas.metacat.IdentifierManager;
127
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
128
import edu.ucsb.nceas.metacat.MetaCatServlet;
129
import edu.ucsb.nceas.metacat.MetacatHandler;
130

    
131
import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines;
132
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
133
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
134
import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler;
135
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
136
import edu.ucsb.nceas.metacat.properties.PropertyService;
137
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
138
import edu.ucsb.nceas.metacat.util.DeleteOnCloseFileInputStream;
139
import edu.ucsb.nceas.metacat.util.DocumentUtil;
140
import edu.ucsb.nceas.metacat.util.SystemUtil;
141
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
142
import edu.ucsb.nceas.utilities.XMLUtilities;
143
import gov.loc.repository.bagit.Bag;
144
import gov.loc.repository.bagit.BagFactory;
145
import gov.loc.repository.bagit.writer.impl.ZipWriter;
146

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

    
177
    //private static final String PATHQUERY = "pathquery";
178
	public static final String UUID_SCHEME = "UUID";
179
	public static final String DOI_SCHEME = "DOI";
180
	private static final String UUID_PREFIX = "urn:uuid:";
181

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

    
191

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

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

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

    
232
    	// only admin of  the MN or the CN is allowed a full delete
233
        boolean allowed = false;
234
        allowed = isAdminAuthorized(session);
235
        if (!allowed) { 
236
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
237
        }
238
    	
239
    	// defer to superclass implementation
240
        return super.delete(session, pid);
241
    }
242

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

    
274
        String localId = null;
275
        boolean allowed = false;
276
        boolean isScienceMetadata = false;
277
        
278
        if (session == null) {
279
        	throw new InvalidToken("1210", "No session has been provided");
280
        }
281
        Subject subject = session.getSubject();
282

    
283
        // verify the pid is valid format
284
        if (!isValidIdentifier(pid)) {
285
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
286
        }
287

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

    
315
        // does the subject have WRITE ( == update) priveleges on the pid?
316
        allowed = isAuthorized(session, pid, Permission.WRITE);
317

    
318
        if (allowed) {
319
        	
320
        	// check quality of SM
321
        	if (sysmeta.getObsoletedBy() != null) {
322
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
323
        	}
324
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
325
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
326
        	}
327

    
328
            // get the existing system metadata for the object
329
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
330

    
331
            // check for previous update
332
            // see: https://redmine.dataone.org/issues/3336
333
            Identifier existingObsoletedBy = existingSysMeta.getObsoletedBy();
334
            if (existingObsoletedBy != null) {
335
            	throw new InvalidRequest("1202", 
336
            			"The previous identifier has already been made obsolete by: " + existingObsoletedBy.getValue());
337
            }
338
            
339
            // add the newPid to the obsoletedBy list for the existing sysmeta
340
            existingSysMeta.setObsoletedBy(newPid);
341

    
342
            // then update the existing system metadata
343
            updateSystemMetadata(existingSysMeta);
344

    
345
            // prep the new system metadata, add pid to the affected lists
346
            sysmeta.setObsoletes(pid);
347
            //sysmeta.addDerivedFrom(pid);
348

    
349
            isScienceMetadata = isScienceMetadata(sysmeta);
350

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

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

    
366
                    }
367

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

    
373
                }
374

    
375
            } else {
376

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

    
380
            }
381

    
382
            // and insert the new system metadata
383
            insertSystemMetadata(sysmeta);
384

    
385
            // log the update event
386
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
387
            
388
            // attempt to register the identifier - it checks if it is a doi
389
            try {
390
    			DOIService.getInstance().registerDOI(sysmeta);
391
    		} catch (EZIDException e) {
392
                throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
393
    		}
394

    
395
        } else {
396
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
397
                    + " on the Member Node.");
398
        }
399

    
400
        return newPid;
401
    }
402

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

    
406
        // check for null session
407
        if (session == null) {
408
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
409
        }
410
        // set the submitter to match the certificate
411
        sysmeta.setSubmitter(session.getSubject());
412
        // set the originating node
413
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
414
        sysmeta.setOriginMemberNode(originMemberNode);
415
        sysmeta.setArchived(false);
416

    
417
        // set the dates
418
        Date now = Calendar.getInstance().getTime();
419
        sysmeta.setDateSysMetadataModified(now);
420
        sysmeta.setDateUploaded(now);
421
        
422
        // set the serial version
423
        sysmeta.setSerialVersion(BigInteger.ZERO);
424

    
425
        // check that we are not attempting to subvert versioning
426
        if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
427
            throw new InvalidSystemMetadata("1180", 
428
              "The supplied system metadata is invalid. " +
429
              "The obsoletes field cannot have a value when creating entries.");
430
        }
431
        
432
        if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
433
            throw new InvalidSystemMetadata("1180", 
434
              "The supplied system metadata is invalid. " +
435
              "The obsoletedBy field cannot have a value when creating entries.");
436
        }
437

    
438
        // call the shared impl
439
        Identifier resultPid = super.create(session, pid, object, sysmeta);
440
        
441
        // attempt to register the identifier - it checks if it is a doi
442
        try {
443
			DOIService.getInstance().registerDOI(sysmeta);
444
		} catch (EZIDException e) {
445
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
446
			sf.initCause(e);
447
            throw sf;
448
		}
449
        
450
        // return 
451
		return resultPid ;
452
    }
453

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

    
481
        if (session != null && sysmeta != null && sourceNode != null) {
482
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
483
                            "\tSession.Subject      = "                           +
484
                            session.getSubject().getValue() + "\n"                +
485
                            "\tidentifier           = "                           + 
486
                            sysmeta.getIdentifier().getValue()                    +
487
                            "\n" + "\tSource NodeReference ="                     +
488
                            sourceNode.getValue());
489
        }
490
        boolean result = false;
491
        String nodeIdStr = null;
492
        NodeReference nodeId = null;
493

    
494
        // get the referenced object
495
        Identifier pid = sysmeta.getIdentifier();
496

    
497
        // get from the membernode
498
        // TODO: switch credentials for the server retrieval?
499
        this.mn = D1Client.getMN(sourceNode);
500
        this.cn = D1Client.getCN();
501
        InputStream object = null;
502
        Session thisNodeSession = null;
503
        SystemMetadata localSystemMetadata = null;
504
        BaseException failure = null;
505
        String localId = null;
506
        
507
        // TODO: check credentials
508
        // cannot be called by public
509
        if (session == null || session.getSubject() == null) {
510
            String msg = "No session was provided to replicate identifier " +
511
            sysmeta.getIdentifier().getValue();
512
            logMetacat.info(msg);
513
            throw new NotAuthorized("2152", msg);
514
            
515
        }
516

    
517

    
518
        // get the local node id
519
        try {
520
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
521
            nodeId = new NodeReference();
522
            nodeId.setValue(nodeIdStr);
523

    
524
        } catch (PropertyNotFoundException e1) {
525
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
526
            failure = new ServiceFailure("2151", msg);
527
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
528
            logMetacat.error(msg);
529
            return true;
530

    
531
        }
532
        
533

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

    
556
            } catch (McdbDocNotFoundException e) {
557
                logMetacat.info("No replica found. Continuing.");
558
                
559
            }
560
            
561
            // no local replica, get a replica
562
            if ( object == null ) {
563
                // session should be null to use the default certificate
564
                // location set in the Certificate manager
565
                object = mn.getReplica(thisNodeSession, pid);
566
                logMetacat.info("MNodeService.getReplica() called for identifier "
567
                                + pid.getValue());
568

    
569
            }
570

    
571
        } catch (InvalidToken e) {            
572
            String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
573
            failure = new ServiceFailure("2151", msg);
574
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
575
            logMetacat.error(msg);
576
            throw new ServiceFailure("2151", msg);
577

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

    
585
        }
586

    
587
        // verify checksum on the object, if supported
588
        if (object.markSupported()) {
589
            Checksum givenChecksum = sysmeta.getChecksum();
590
            Checksum computedChecksum = null;
591
            try {
592
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
593
                object.reset();
594

    
595
            } catch (Exception e) {
596
                String msg = "Error computing checksum on replica: " + e.getMessage();
597
                logMetacat.error(msg);
598
                ServiceFailure sf = new ServiceFailure("2151", msg);
599
                sf.initCause(e);
600
                throw sf;
601
            }
602
            if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
603
                logMetacat.error("Given    checksum for " + pid.getValue() + 
604
                    "is " + givenChecksum.getValue());
605
                logMetacat.error("Computed checksum for " + pid.getValue() + 
606
                    "is " + computedChecksum.getValue());
607
                throw new ServiceFailure("2151",
608
                        "Computed checksum does not match declared checksum");
609
            }
610
        }
611

    
612
        // add it to local store
613
        Identifier retPid;
614
        try {
615
            // skip the MN.create -- this mutates the system metadata and we don't want it to
616
            if ( localId == null ) {
617
                // TODO: this will fail if we already "know" about the identifier
618
            	// FIXME: see https://redmine.dataone.org/issues/2572
619
                retPid = super.create(session, pid, object, sysmeta);
620
                result = (retPid.getValue().equals(pid.getValue()));
621
            }
622
            
623
        } catch (Exception e) {
624
            String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
625
            failure = new ServiceFailure("2151", msg);
626
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
627
            logMetacat.error(msg);
628
            throw new ServiceFailure("2151", msg);
629
            
630
        }
631

    
632
        // finish by setting the replication status
633
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
634
        return result;
635

    
636
    }
637

    
638
    /**
639
     * Return the object identified by the given object identifier
640
     * 
641
     * @param session - the Session object containing the credentials for the Subject
642
     * @param pid - the object identifier for the given object
643
     * 
644
     * @return inputStream - the input stream of the given object
645
     * 
646
     * @throws InvalidToken
647
     * @throws ServiceFailure
648
     * @throws NotAuthorized
649
     * @throws InvalidRequest
650
     * @throws NotImplemented
651
     */
652
    @Override
653
    public InputStream get(Session session, Identifier pid) 
654
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
655

    
656
        return super.get(session, pid);
657

    
658
    }
659

    
660
    /**
661
     * Returns a Checksum for the specified object using an accepted hashing algorithm
662
     * 
663
     * @param session - the Session object containing the credentials for the Subject
664
     * @param pid - the object identifier for the given object
665
     * @param algorithm -  the name of an algorithm that will be used to compute 
666
     *                     a checksum of the bytes of the object
667
     * 
668
     * @return checksum - the checksum of the given object
669
     * 
670
     * @throws InvalidToken
671
     * @throws ServiceFailure
672
     * @throws NotAuthorized
673
     * @throws NotFound
674
     * @throws InvalidRequest
675
     * @throws NotImplemented
676
     */
677
    @Override
678
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
679
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
680
        InvalidRequest, NotImplemented {
681

    
682
        Checksum checksum = null;
683

    
684
        InputStream inputStream = get(session, pid);
685

    
686
        try {
687
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
688

    
689
        } catch (NoSuchAlgorithmException e) {
690
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
691
                    + e.getMessage());
692
        } catch (IOException e) {
693
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
694
                    + e.getMessage());
695
        }
696

    
697
        if (checksum == null) {
698
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
699
        }
700

    
701
        return checksum;
702
    }
703

    
704
    /**
705
     * Return the system metadata for a given object
706
     * 
707
     * @param session - the Session object containing the credentials for the Subject
708
     * @param pid - the object identifier for the given object
709
     * 
710
     * @return inputStream - the input stream of the given system metadata object
711
     * 
712
     * @throws InvalidToken
713
     * @throws ServiceFailure
714
     * @throws NotAuthorized
715
     * @throws NotFound
716
     * @throws InvalidRequest
717
     * @throws NotImplemented
718
     */
719
    @Override
720
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) 
721
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
722
        NotImplemented {
723

    
724
        return super.getSystemMetadata(session, pid);
725
    }
726

    
727
    /**
728
     * Retrieve the list of objects present on the MN that match the calling parameters
729
     * 
730
     * @param session - the Session object containing the credentials for the Subject
731
     * @param startTime - Specifies the beginning of the time range from which 
732
     *                    to return object (>=)
733
     * @param endTime - Specifies the beginning of the time range from which 
734
     *                  to return object (>=)
735
     * @param objectFormat - Restrict results to the specified object format
736
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
737
     * @param start - The zero-based index of the first value, relative to the 
738
     *                first record of the resultset that matches the parameters.
739
     * @param count - The maximum number of entries that should be returned in 
740
     *                the response. The Member Node may return less entries 
741
     *                than specified in this value.
742
     * 
743
     * @return objectList - the list of objects matching the criteria
744
     * 
745
     * @throws InvalidToken
746
     * @throws ServiceFailure
747
     * @throws NotAuthorized
748
     * @throws InvalidRequest
749
     * @throws NotImplemented
750
     */
751
    @Override
752
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
753
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
754

    
755
        ObjectList objectList = null;
756

    
757
        try {
758
        	// safeguard against large requests
759
            if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
760
            	count = MAXIMUM_DB_RECORD_COUNT;
761
            }
762
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
763
        } catch (Exception e) {
764
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
765
        }
766

    
767
        return objectList;
768
    }
769

    
770
    /**
771
     * Return a description of the node's capabilities and services.
772
     * 
773
     * @return node - the technical capabilities of the Member Node
774
     * 
775
     * @throws ServiceFailure
776
     * @throws NotAuthorized
777
     * @throws InvalidRequest
778
     * @throws NotImplemented
779
     */
780
    @Override
781
    public Node getCapabilities() 
782
        throws NotImplemented, ServiceFailure {
783

    
784
        String nodeName = null;
785
        String nodeId = null;
786
        String subject = null;
787
        String contactSubject = null;
788
        String nodeDesc = null;
789
        String nodeTypeString = null;
790
        NodeType nodeType = null;
791
        String mnCoreServiceVersion = null;
792
        String mnReadServiceVersion = null;
793
        String mnAuthorizationServiceVersion = null;
794
        String mnStorageServiceVersion = null;
795
        String mnReplicationServiceVersion = null;
796

    
797
        boolean nodeSynchronize = false;
798
        boolean nodeReplicate = false;
799
        boolean mnCoreServiceAvailable = false;
800
        boolean mnReadServiceAvailable = false;
801
        boolean mnAuthorizationServiceAvailable = false;
802
        boolean mnStorageServiceAvailable = false;
803
        boolean mnReplicationServiceAvailable = false;
804

    
805
        try {
806
            // get the properties of the node based on configuration information
807
            nodeName = PropertyService.getProperty("dataone.nodeName");
808
            nodeId = PropertyService.getProperty("dataone.nodeId");
809
            subject = PropertyService.getProperty("dataone.subject");
810
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
811
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
812
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
813
            nodeType = NodeType.convert(nodeTypeString);
814
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
815
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
816

    
817
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
818
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
819
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
820
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
821
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
822

    
823
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
824
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
825
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
826
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
827
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
828

    
829
            // Set the properties of the node based on configuration information and
830
            // calls to current status methods
831
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
832
            Node node = new Node();
833
            node.setBaseURL(serviceName + "/" + nodeTypeString);
834
            node.setDescription(nodeDesc);
835

    
836
            // set the node's health information
837
            node.setState(NodeState.UP);
838
            
839
            // set the ping response to the current value
840
            Ping canPing = new Ping();
841
            canPing.setSuccess(false);
842
            try {
843
            	Date pingDate = ping();
844
                canPing.setSuccess(pingDate != null);
845
            } catch (BaseException e) {
846
                e.printStackTrace();
847
                // guess it can't be pinged
848
            }
849
            
850
            node.setPing(canPing);
851

    
852
            NodeReference identifier = new NodeReference();
853
            identifier.setValue(nodeId);
854
            node.setIdentifier(identifier);
855
            Subject s = new Subject();
856
            s.setValue(subject);
857
            node.addSubject(s);
858
            Subject contact = new Subject();
859
            contact.setValue(contactSubject);
860
            node.addContactSubject(contact);
861
            node.setName(nodeName);
862
            node.setReplicate(nodeReplicate);
863
            node.setSynchronize(nodeSynchronize);
864

    
865
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
866
            Services services = new Services();
867

    
868
            Service sMNCore = new Service();
869
            sMNCore.setName("MNCore");
870
            sMNCore.setVersion(mnCoreServiceVersion);
871
            sMNCore.setAvailable(mnCoreServiceAvailable);
872

    
873
            Service sMNRead = new Service();
874
            sMNRead.setName("MNRead");
875
            sMNRead.setVersion(mnReadServiceVersion);
876
            sMNRead.setAvailable(mnReadServiceAvailable);
877

    
878
            Service sMNAuthorization = new Service();
879
            sMNAuthorization.setName("MNAuthorization");
880
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
881
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
882

    
883
            Service sMNStorage = new Service();
884
            sMNStorage.setName("MNStorage");
885
            sMNStorage.setVersion(mnStorageServiceVersion);
886
            sMNStorage.setAvailable(mnStorageServiceAvailable);
887

    
888
            Service sMNReplication = new Service();
889
            sMNReplication.setName("MNReplication");
890
            sMNReplication.setVersion(mnReplicationServiceVersion);
891
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
892

    
893
            services.addService(sMNRead);
894
            services.addService(sMNCore);
895
            services.addService(sMNAuthorization);
896
            services.addService(sMNStorage);
897
            services.addService(sMNReplication);
898
            node.setServices(services);
899

    
900
            // Set the schedule for synchronization
901
            Synchronization synchronization = new Synchronization();
902
            Schedule schedule = new Schedule();
903
            Date now = new Date();
904
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
905
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
906
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
907
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
908
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
909
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
910
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
911
            synchronization.setSchedule(schedule);
912
            synchronization.setLastHarvested(now);
913
            synchronization.setLastCompleteHarvest(now);
914
            node.setSynchronization(synchronization);
915

    
916
            node.setType(nodeType);
917
            return node;
918

    
919
        } catch (PropertyNotFoundException pnfe) {
920
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
921
            logMetacat.error(msg);
922
            throw new ServiceFailure("2162", msg);
923
        }
924
    }
925

    
926
    /**
927
     * Returns the number of operations that have been serviced by the node 
928
     * over time periods of one and 24 hours.
929
     * 
930
     * @param session - the Session object containing the credentials for the Subject
931
     * @param period - An ISO8601 compatible DateTime range specifying the time 
932
     *                 range for which to return operation statistics.
933
     * @param requestor - Limit to operations performed by given requestor identity.
934
     * @param event -  Enumerated value indicating the type of event being examined
935
     * @param format - Limit to events involving objects of the specified format
936
     * 
937
     * @return the desired log records
938
     * 
939
     * @throws InvalidToken
940
     * @throws ServiceFailure
941
     * @throws NotAuthorized
942
     * @throws InvalidRequest
943
     * @throws NotImplemented
944
     */
945
    public MonitorList getOperationStatistics(Session session, Date startTime, 
946
        Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
947
        throws NotImplemented, ServiceFailure, NotAuthorized, InsufficientResources, UnsupportedType {
948

    
949
        MonitorList monitorList = new MonitorList();
950

    
951
        try {
952

    
953
            // get log records first
954
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
955

    
956
            // TODO: aggregate by day or hour -- needs clarification
957
            int count = 1;
958
            for (LogEntry logEntry : logs.getLogEntryList()) {
959
                Identifier pid = logEntry.getIdentifier();
960
                Date logDate = logEntry.getDateLogged();
961
                // if we are filtering by format
962
                if (formatId != null) {
963
                    SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
964
                    if (!sysmeta.getFormatId().getValue().equals(formatId.getValue())) {
965
                        // does not match
966
                        continue;
967
                    }
968
                }
969
                MonitorInfo item = new MonitorInfo();
970
                item.setCount(count);
971
                item.setDate(new java.sql.Date(logDate.getTime()));
972
                monitorList.addMonitorInfo(item);
973

    
974
            }
975
        } catch (Exception e) {
976
            e.printStackTrace();
977
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
978
        }
979

    
980
        return monitorList;
981

    
982
    }
983

    
984
    /**
985
     * A callback method used by a CN to indicate to a MN that it cannot 
986
     * complete synchronization of the science metadata identified by pid.  Log
987
     * the event in the metacat event log.
988
     * 
989
     * @param session
990
     * @param syncFailed
991
     * 
992
     * @throws ServiceFailure
993
     * @throws NotAuthorized
994
     * @throws NotImplemented
995
     */
996
    @Override
997
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
998
        throws NotImplemented, ServiceFailure, NotAuthorized {
999

    
1000
        String localId;
1001
        Identifier pid;
1002
        if ( syncFailed.getPid() != null ) {
1003
            pid = new Identifier();
1004
            pid.setValue(syncFailed.getPid());
1005
            boolean allowed;
1006
            
1007
            //are we allowed? only CNs
1008
            try {
1009
                allowed = isAdminAuthorized(session);
1010
                if ( !allowed ){
1011
                    throw new NotAuthorized("2162", 
1012
                            "Not allowed to call synchronizationFailed() on this node.");
1013
                }
1014
            } catch (InvalidToken e) {
1015
                throw new NotAuthorized("2162", 
1016
                        "Not allowed to call synchronizationFailed() on this node.");
1017

    
1018
            }
1019
            
1020
        } else {
1021
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1022

    
1023
        }
1024
        
1025
        try {
1026
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1027
        } catch (McdbDocNotFoundException e) {
1028
            throw new ServiceFailure("2161", "The identifier specified by " + 
1029
                    syncFailed.getPid() + " was not found on this node.");
1030

    
1031
        }
1032
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1033
        // method is changed to include the URL as a parameter
1034
        logMetacat.debug("Synchronization for the object identified by " + 
1035
                pid.getValue() + " failed from " + syncFailed.getNodeId() + 
1036
                " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
1037
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1038
        String principal = Constants.SUBJECT_PUBLIC;
1039
        if (session != null && session.getSubject() != null) {
1040
          principal = session.getSubject().getValue();
1041
        }
1042
        try {
1043
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1044
        } catch (Exception e) {
1045
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1046
        }
1047
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
1048
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1049
        return true;
1050

    
1051
    }
1052

    
1053
    /**
1054
     * Essentially a get() but with different logging behavior
1055
     */
1056
    @Override
1057
    public InputStream getReplica(Session session, Identifier pid) 
1058
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1059

    
1060
        logMetacat.info("MNodeService.getReplica() called.");
1061

    
1062
        // cannot be called by public
1063
        if (session == null) {
1064
        	throw new InvalidToken("2183", "No session was provided.");
1065
        }
1066
        
1067
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1068
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1069
             "\tIdentifier           = " + pid.getValue());
1070

    
1071
        InputStream inputStream = null; // bytes to be returned
1072
        handler = new MetacatHandler(new Timer());
1073
        boolean allowed = false;
1074
        String localId; // the metacat docid for the pid
1075

    
1076
        // get the local docid from Metacat
1077
        try {
1078
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1079
        } catch (McdbDocNotFoundException e) {
1080
            throw new ServiceFailure("2181", "The object specified by " + 
1081
                    pid.getValue() + " does not exist at this node.");
1082
            
1083
        }
1084

    
1085
        Subject targetNodeSubject = session.getSubject();
1086

    
1087
        // check for authorization to replicate, null session to act as this source MN
1088
        try {
1089
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1090
        } catch (InvalidToken e1) {
1091
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1092
                + e1.getMessage());
1093
            
1094
        } catch (NotFound e1) {
1095
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1096
                    + e1.getMessage());
1097

    
1098
        } catch (InvalidRequest e1) {
1099
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1100
                    + e1.getMessage());
1101

    
1102
        }
1103

    
1104
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1105
            " for identifier " + pid.getValue());
1106

    
1107
        // if the person is authorized, perform the read
1108
        if (allowed) {
1109
            try {
1110
                inputStream = MetacatHandler.read(localId);
1111
            } catch (Exception e) {
1112
                throw new ServiceFailure("1020", "The object specified by " + 
1113
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1114
            }
1115
        }
1116

    
1117
        // if we fail to set the input stream
1118
        if (inputStream == null) {
1119
            throw new ServiceFailure("2181", "The object specified by " + 
1120
                pid.getValue() + "does not exist at this node.");
1121
        }
1122

    
1123
        // log the replica event
1124
        String principal = null;
1125
        if (session.getSubject() != null) {
1126
            principal = session.getSubject().getValue();
1127
        }
1128
        EventLog.getInstance().log(request.getRemoteAddr(), 
1129
            request.getHeader("User-Agent"), principal, localId, "replicate");
1130

    
1131
        return inputStream;
1132
    }
1133

    
1134
    /**
1135
     * A method to notify the Member Node that the authoritative copy of 
1136
     * system metadata on the Coordinating Nodes has changed.
1137
     * 
1138
     * @param session   Session information that contains the identity of the 
1139
     *                  calling user as retrieved from the X.509 certificate 
1140
     *                  which must be traceable to the CILogon service.
1141
     * @param serialVersion   The serialVersion of the system metadata
1142
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1143
     * @throws NotImplemented
1144
     * @throws ServiceFailure
1145
     * @throws NotAuthorized
1146
     * @throws InvalidRequest
1147
     * @throws InvalidToken
1148
     */
1149
    public boolean systemMetadataChanged(Session session, Identifier pid,
1150
        long serialVersion, Date dateSysMetaLastModified) 
1151
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1152
        InvalidToken {
1153
        
1154
        // cannot be called by public
1155
        if (session == null) {
1156
        	throw new InvalidToken("2183", "No session was provided.");
1157
        }
1158

    
1159
        SystemMetadata currentLocalSysMeta = null;
1160
        SystemMetadata newSysMeta = null;
1161
        CNode cn = D1Client.getCN();
1162
        NodeList nodeList = null;
1163
        Subject callingSubject = null;
1164
        boolean allowed = false;
1165
        
1166
        // are we allowed to call this?
1167
        callingSubject = session.getSubject();
1168
        nodeList = cn.listNodes();
1169
        
1170
        for(Node node : nodeList.getNodeList()) {
1171
            // must be a CN
1172
            if ( node.getType().equals(NodeType.CN)) {
1173
               List<Subject> subjectList = node.getSubjectList();
1174
               // the calling subject must be in the subject list
1175
               if ( subjectList.contains(callingSubject)) {
1176
                   allowed = true;
1177
                   
1178
               }
1179
               
1180
            }
1181
        }
1182
        
1183
        if (!allowed ) {
1184
            String msg = "The subject identified by " + callingSubject.getValue() +
1185
              " is not authorized to call this service.";
1186
            throw new NotAuthorized("1331", msg);
1187
            
1188
        }
1189
        
1190
        // compare what we have locally to what is sent in the change notification
1191
        try {
1192
            currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1193
             
1194
        } catch (RuntimeException e) {
1195
            String msg = "SystemMetadata for pid " + pid.getValue() +
1196
              " couldn't be updated because it couldn't be found locally: " +
1197
              e.getMessage();
1198
            logMetacat.error(msg);
1199
            ServiceFailure sf = new ServiceFailure("1333", msg);
1200
            sf.initCause(e);
1201
            throw sf; 
1202
        }
1203
        
1204
        if (currentLocalSysMeta.getSerialVersion().longValue() < serialVersion ) {
1205
            try {
1206
                newSysMeta = cn.getSystemMetadata(null, pid);
1207
            } catch (NotFound e) {
1208
                // huh? you just said you had it
1209
            	String msg = "On updating the local copy of system metadata " + 
1210
                "for pid " + pid.getValue() +", the CN reports it is not found." +
1211
                " The error message was: " + e.getMessage();
1212
                logMetacat.error(msg);
1213
                ServiceFailure sf = new ServiceFailure("1333", msg);
1214
                sf.initCause(e);
1215
                throw sf;
1216
            }
1217
            
1218
            // update the local copy of system metadata for the pid
1219
            try {
1220
                HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1221
                // submit for indexing
1222
                HazelcastService.getInstance().getIndexQueue().add(newSysMeta);
1223
                logMetacat.info("Updated local copy of system metadata for pid " +
1224
                    pid.getValue() + " after change notification from the CN.");
1225
                
1226
            } catch (RuntimeException e) {
1227
                String msg = "SystemMetadata for pid " + pid.getValue() +
1228
                  " couldn't be updated: " +
1229
                  e.getMessage();
1230
                logMetacat.error(msg);
1231
                ServiceFailure sf = new ServiceFailure("1333", msg);
1232
                sf.initCause(e);
1233
                throw sf;
1234
            }
1235
        }
1236
        
1237
        return true;
1238
        
1239
    }
1240
    
1241
    /*
1242
     * Set the replication status for the object on the Coordinating Node
1243
     * 
1244
     * @param session - the session for the this target node
1245
     * @param pid - the identifier of the object being updated
1246
     * @param nodeId - the identifier of this target node
1247
     * @param status - the replication status to set
1248
     * @param failure - the exception to include, if any
1249
     */
1250
    private void setReplicationStatus(Session session, Identifier pid, 
1251
        NodeReference nodeId, ReplicationStatus status, BaseException failure) 
1252
        throws ServiceFailure, NotImplemented, NotAuthorized, 
1253
        InvalidRequest {
1254
        
1255
        // call the CN as the MN to set the replication status
1256
        try {
1257
            this.cn = D1Client.getCN();
1258
            this.cn.setReplicationStatus(session, pid, nodeId,
1259
                    status, failure);
1260
            
1261
        } catch (InvalidToken e) {
1262
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (InvalidToken): " + e.getMessage();
1263
            logMetacat.error(msg);
1264
        	throw new ServiceFailure("2151",
1265
                    msg);
1266
            
1267
        } catch (NotFound e) {
1268
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (NotFound): " + e.getMessage();
1269
            logMetacat.error(msg);
1270
        	throw new ServiceFailure("2151",
1271
                    msg);
1272
            
1273
        }
1274
    }
1275

    
1276
	@Override
1277
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1278
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1279
			InvalidRequest {
1280
		
1281
		// check for null session
1282
        if (session == null) {
1283
          throw new InvalidToken("2190", "Session is required to generate an Identifier at this Node.");
1284
        }
1285
		
1286
		Identifier identifier = new Identifier();
1287
		
1288
		// handle different schemes
1289
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1290
			// UUID
1291
			UUID uuid = UUID.randomUUID();
1292
            identifier.setValue(UUID_PREFIX + uuid.toString());
1293
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1294
			// generate a DOI
1295
			try {
1296
				identifier = DOIService.getInstance().generateDOI();
1297
			} catch (EZIDException e) {
1298
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1299
				sf.initCause(e);
1300
				throw sf;
1301
			}
1302
		} else {
1303
			// default if we don't know the scheme
1304
			if (fragment != null) {
1305
				// for now, just autogen with fragment
1306
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1307
				identifier.setValue(autogenId);			
1308
			} else {
1309
				// autogen with no fragment
1310
				String autogenId = DocumentUtil.generateDocumentId(0);
1311
				identifier.setValue(autogenId);
1312
			}
1313
		}
1314
		
1315
		// TODO: reserve the identifier with the CN. We can only do this when
1316
		// 1) the MN is part of a CN cluster
1317
		// 2) the request is from an authenticated user
1318
		
1319
		return identifier;
1320
	}
1321

    
1322
	@Override
1323
	public boolean isAuthorized(Identifier pid, Permission permission)
1324
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1325
			NotAuthorized, NotImplemented {
1326

    
1327
		return isAuthorized(null, pid, permission);
1328
	}
1329

    
1330
	@Override
1331
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1332
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1333
			InvalidRequest {
1334

    
1335
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1336
	}
1337

    
1338
	@Override
1339
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1340
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1341
			NotAuthorized, NotImplemented, ServiceFailure {
1342

    
1343
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1344
	}
1345

    
1346
	@Override
1347
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1348
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1349

    
1350
		return describe(null, pid);
1351
	}
1352

    
1353
	@Override
1354
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
1355
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
1356

    
1357
		return get(null, pid);
1358
	}
1359

    
1360
	@Override
1361
	public Checksum getChecksum(Identifier pid, String algorithm)
1362
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1363
			ServiceFailure, NotFound {
1364

    
1365
		return getChecksum(null, pid, algorithm);
1366
	}
1367

    
1368
	@Override
1369
	public SystemMetadata getSystemMetadata(Identifier pid)
1370
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1371
			NotFound {
1372

    
1373
		return getSystemMetadata(null, pid);
1374
	}
1375

    
1376
	@Override
1377
	public ObjectList listObjects(Date startTime, Date endTime,
1378
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1379
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1380
			NotImplemented, ServiceFailure {
1381

    
1382
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1383
	}
1384

    
1385
	@Override
1386
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1387
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1388

    
1389
		return synchronizationFailed(null, syncFailed);
1390
	}
1391

    
1392
	@Override
1393
	public InputStream getReplica(Identifier pid) throws InvalidToken,
1394
			NotAuthorized, NotImplemented, ServiceFailure, NotFound,
1395
			InsufficientResources {
1396

    
1397
		return getReplica(null, pid);
1398
	}
1399

    
1400
	@Override
1401
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1402
			throws NotImplemented, ServiceFailure, NotAuthorized,
1403
			InvalidRequest, InvalidToken, InsufficientResources,
1404
			UnsupportedType {
1405

    
1406
		return replicate(null, sysmeta, sourceNode);
1407
	}
1408

    
1409
	@Override
1410
	public Identifier create(Identifier pid, InputStream object,
1411
			SystemMetadata sysmeta) throws IdentifierNotUnique,
1412
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1413
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1414
			UnsupportedType {
1415

    
1416
		return create(null, pid, object, sysmeta);
1417
	}
1418

    
1419
	@Override
1420
	public Identifier delete(Identifier pid) throws InvalidToken,
1421
			ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1422

    
1423
		return delete(null, pid);
1424
	}
1425

    
1426
	@Override
1427
	public Identifier generateIdentifier(String scheme, String fragment)
1428
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1429
			InvalidRequest {
1430

    
1431
		return generateIdentifier(null, scheme, fragment);
1432
	}
1433

    
1434
	@Override
1435
	public Identifier update(Identifier pid, InputStream object,
1436
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
1437
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1438
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1439
			UnsupportedType, NotFound {
1440

    
1441
		return update(null, pid, object, newPid, sysmeta);
1442
	}
1443

    
1444
	@Override
1445
	public QueryEngineDescription getQueryEngineDescription(String engine)
1446
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1447
			NotFound {
1448
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1449
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1450
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1451
            }
1452
	        QueryEngineDescription qed = new QueryEngineDescription();
1453
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1454
	        qed.setQueryEngineVersion("1.0");
1455
	        qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1456
	        Vector<String> pathsForIndexing = null;
1457
	        try {
1458
	            pathsForIndexing = SystemUtil.getPathsForIndexing();
1459
	        } catch (MetacatUtilException e) {
1460
	            logMetacat.warn("Could not get index paths", e);
1461
	        }
1462
	        for (String fieldName: pathsForIndexing) {
1463
	            QueryField field = new QueryField();
1464
	            field.addDescription("Indexed field for path '" + fieldName + "'");
1465
	            field.setName(fieldName);
1466
	            field.setReturnable(true);
1467
	            field.setSearchable(true);
1468
	            field.setSortable(false);
1469
	            // TODO: determine type and multivaluedness
1470
	            field.setType(String.class.getName());
1471
	            //field.setMultivalued(true);
1472
	            qed.addQueryField(field);
1473
	        }
1474
	        return qed;
1475
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1476
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1477
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1478
            }
1479
	        try {
1480
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1481
	            return qed;
1482
	        } catch (Exception e) {
1483
	            e.printStackTrace();
1484
	            throw new ServiceFailure("Solr server error", e.getMessage());
1485
	        }
1486
	    } else {
1487
	        throw new NotFound("404", "The Metacat member node can't find the query engine - "+engine);
1488
	    }
1489
		
1490
	}
1491

    
1492
	@Override
1493
	public QueryEngineList listQueryEngines() throws InvalidToken,
1494
			ServiceFailure, NotAuthorized, NotImplemented {
1495
		QueryEngineList qel = new QueryEngineList();
1496
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1497
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1498
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1499
		for(String name : enables) {
1500
		    qel.addQueryEngine(name);
1501
		}
1502
		return qel;
1503
	}
1504

    
1505
	@Override
1506
	public InputStream query(String engine, String query) throws InvalidToken,
1507
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1508
			NotFound {
1509
	    String user = Constants.SUBJECT_PUBLIC;
1510
        String[] groups= null;
1511
        Set<Subject> subjects = null;
1512
        if (session != null) {
1513
            user = session.getSubject().getValue();
1514
            subjects = AuthUtils.authorizedClientSubjects(session);
1515
            if (subjects != null) {
1516
                List<String> groupList = new ArrayList<String>();
1517
                for (Subject subject: subjects) {
1518
                    groupList.add(subject.getValue());
1519
                }
1520
                groups = groupList.toArray(new String[0]);
1521
            }
1522
        } else {
1523
            //add the public user subject to the set 
1524
            Subject subject = new Subject();
1525
            subject.setValue(Constants.SUBJECT_PUBLIC);
1526
            subjects = new HashSet<Subject>();
1527
            subjects.add(subject);
1528
        }
1529
        //System.out.println("====== user is "+user);
1530
        //System.out.println("====== groups are "+groups);
1531
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1532
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1533
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1534
            }
1535
			try {
1536
				DBQuery queryobj = new DBQuery();
1537
				
1538
				String results = queryobj.performPathquery(query, user, groups);
1539
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1540
				ctbais.setContentType("text/xml");
1541
				return ctbais;
1542

    
1543
			} catch (Exception e) {
1544
				throw new ServiceFailure("Pathquery error", e.getMessage());
1545
			}
1546
			
1547
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1548
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1549
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1550
		    }
1551
		    logMetacat.info("The query is ==================================== \n"+query);
1552
		    try {
1553
		        
1554
                return MetacatSolrIndex.getInstance().query(query, subjects);
1555
            } catch (Exception e) {
1556
                // TODO Auto-generated catch block
1557
                throw new ServiceFailure("Solr server error", e.getMessage());
1558
            } 
1559
		}
1560
		return null;
1561
	}
1562
	
1563
	/**
1564
	 * Given an existing Science Metadata PID, this method mints a DOI
1565
	 * and updates the original object "publishing" the update with the DOI.
1566
	 * This includes updating the ORE map that describes the Science Metadata+data.
1567
	 * TODO: ensure all referenced objects allow public read
1568
	 * 
1569
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1570
	 * 
1571
	 * @param originalIdentifier
1572
	 * @param request
1573
	 * @throws InvalidRequest 
1574
	 * @throws NotImplemented 
1575
	 * @throws NotAuthorized 
1576
	 * @throws ServiceFailure 
1577
	 * @throws InvalidToken 
1578
	 * @throws NotFound
1579
	 * @throws InvalidSystemMetadata 
1580
	 * @throws InsufficientResources 
1581
	 * @throws UnsupportedType 
1582
	 * @throws IdentifierNotUnique 
1583
	 */
1584
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata {
1585
		
1586
		
1587
		// get the original SM
1588
		SystemMetadata originalSystemMetadata = this.getSystemMetadata(session, originalIdentifier);
1589

    
1590
		// make copy of it
1591
		SystemMetadata sysmeta = new SystemMetadata();
1592
		try {
1593
			BeanUtils.copyProperties(sysmeta, originalSystemMetadata);
1594
		} catch (Exception e) {
1595
			// report as service failure
1596
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1597
			sf.initCause(e);
1598
			throw sf;
1599
		}
1600

    
1601
		// mint a DOI for the new revision
1602
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1603
				
1604
		// set new metadata values
1605
		sysmeta.setIdentifier(newIdentifier);
1606
		sysmeta.setObsoletes(originalIdentifier);
1607
		sysmeta.setObsoletedBy(null);
1608
		
1609
		// get the bytes
1610
		InputStream inputStream = this.get(session, originalIdentifier);
1611
		
1612
		// update the object
1613
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
1614
		
1615
		// update ORE that references the scimeta
1616
		// first try the naive method, then check the SOLR index
1617
		try {
1618
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1619
			
1620
			Identifier potentialOreIdentifier = new Identifier();
1621
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1622
			
1623
			InputStream oreInputStream = null;
1624
			try {
1625
				oreInputStream = this.get(session, potentialOreIdentifier);
1626
			} catch (NotFound nf) {
1627
				// this is probably okay for many sci meta data docs
1628
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1629
				// try the SOLR index
1630
				List<Identifier> potentialOreIdentifiers = this.lookupOreFor(originalIdentifier, false);
1631
				if (potentialOreIdentifiers != null) {
1632
					potentialOreIdentifier = potentialOreIdentifiers.get(0);
1633
					try {
1634
						oreInputStream = this.get(session, potentialOreIdentifier);
1635
					} catch (NotFound nf2) {
1636
						// this is probably okay for many sci meta data docs
1637
						logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1638
					}
1639
				}
1640
			}
1641
			if (oreInputStream != null) {
1642
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1643
	
1644
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1645
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1646
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1647
				
1648
				// TODO: ensure all data package objects allow public read
1649
	
1650
				// reconstruct the ORE with the new identifiers
1651
				sciMetaMap.remove(originalIdentifier);
1652
				sciMetaMap.put(newIdentifier, dataIdentifiers);
1653
				
1654
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1655
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1656
				
1657
				// get the original ORE SM and update the values
1658
				SystemMetadata originalOreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1659
				SystemMetadata oreSysMeta = new SystemMetadata();
1660
				try {
1661
					BeanUtils.copyProperties(oreSysMeta, originalOreSysMeta);
1662
				} catch (Exception e) {
1663
					// report as service failure
1664
					ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1665
					sf.initCause(e);
1666
					throw sf;
1667
				}
1668

    
1669
				oreSysMeta.setIdentifier(newOreIdentifier);
1670
				oreSysMeta.setObsoletes(potentialOreIdentifier);
1671
				oreSysMeta.setObsoletedBy(null);
1672
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
1673
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
1674
				
1675
				// save the updated ORE
1676
				this.update(
1677
						session, 
1678
						potentialOreIdentifier, 
1679
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")), 
1680
						newOreIdentifier, 
1681
						oreSysMeta);
1682
				
1683
			}
1684
		} catch (McdbDocNotFoundException e) {
1685
			// report as service failure
1686
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1687
			sf.initCause(e);
1688
			throw sf;
1689
		} catch (UnsupportedEncodingException e) {
1690
			// report as service failure
1691
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1692
			sf.initCause(e);
1693
			throw sf;
1694
		} catch (OREException e) {
1695
			// report as service failure
1696
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1697
			sf.initCause(e);
1698
			throw sf;
1699
		} catch (URISyntaxException e) {
1700
			// report as service failure
1701
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1702
			sf.initCause(e);
1703
			throw sf;
1704
		} catch (OREParserException e) {
1705
			// report as service failure
1706
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1707
			sf.initCause(e);
1708
			throw sf;
1709
		} catch (ORESerialiserException e) {
1710
			// report as service failure
1711
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1712
			sf.initCause(e);
1713
			throw sf;
1714
		} catch (NoSuchAlgorithmException e) {
1715
			// report as service failure
1716
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1717
			sf.initCause(e);
1718
			throw sf;
1719
		}
1720
		
1721
		return newIdentifier;
1722
	}
1723
	
1724
	/**
1725
	 * Determines if we already have registered an ORE map for this package
1726
	 * NOTE: uses a solr query to locate OREs for the object
1727
	 * @param guid of the EML/packaging object
1728
	 * @return list of resource map identifiers for the given pid
1729
	 */
1730
	public List<Identifier> lookupOreFor(Identifier guid, boolean includeObsolete) {
1731
		// Search for the ORE if we can find it
1732
		String pid = guid.getValue();
1733
		List<Identifier> retList = null;
1734
		try {
1735
			String query = "fl=id,resourceMap&wt=xml&q=-obsoletedBy:*+resourceMap:*+id:\"" + pid + "\"";;
1736
			if (includeObsolete) {
1737
				query = "fl=id,resourceMap&wt=xml&q=resourceMap:*+id:\"" + pid + "\"";
1738
			}
1739
			
1740
			InputStream results = this.query("solr", query);
1741
			org.w3c.dom.Node rootNode = XMLUtilities.getXMLReaderAsDOMTreeRootNode(new InputStreamReader(results, "UTF-8"));
1742
			//String resultString = XMLUtilities.getDOMTreeAsString(rootNode);
1743
			org.w3c.dom.NodeList nodeList = XMLUtilities.getNodeListWithXPath(rootNode, "//arr[@name=\"resourceMap\"]/str");
1744
			if (nodeList != null && nodeList.getLength() > 0) {
1745
				retList = new ArrayList<Identifier>();
1746
				for (int i = 0; i < nodeList.getLength(); i++) {
1747
					String found = nodeList.item(i).getFirstChild().getNodeValue();
1748
					Identifier oreId = new Identifier();
1749
					oreId.setValue(found);
1750
					retList.add(oreId);
1751
				}
1752
			}
1753
		} catch (Exception e) {
1754
			logMetacat.error("Error checking for resourceMap[s] on pid " + pid + ". " + e.getMessage(), e);
1755
		}
1756
		
1757
		return retList;
1758
	}
1759
	
1760
	/**
1761
	 * Packages the given package in a Bagit collection for download
1762
	 * @param pid
1763
	 * @throws NotImplemented 
1764
	 * @throws NotFound 
1765
	 * @throws NotAuthorized 
1766
	 * @throws ServiceFailure 
1767
	 * @throws InvalidToken 
1768
	 */
1769
	public InputStream getPackage(Session session, Identifier pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1770
		
1771
		InputStream bagInputStream = null;
1772
		BagFactory bagFactory = new BagFactory();
1773
		Bag bag = bagFactory.createBag();
1774
		
1775
		// track the temp files we use so we can delete them when finished
1776
		List<File> tempFiles = new ArrayList<File>();
1777
		
1778
		// the pids to include in the package
1779
		List<Identifier> packagePids = new ArrayList<Identifier>();
1780
		
1781
		// catch non-D1 service errors and throw as ServiceFailures
1782
		try {
1783
			
1784
			// find the package contents
1785
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1786
			if (ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
1787
				InputStream oreInputStream = this.get(session, pid);
1788
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1789
				packagePids.addAll(resourceMapStructure.keySet());
1790
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
1791
					packagePids.addAll(entries.keySet());
1792
					for (List<Identifier> dataPids: entries.values()) {
1793
						packagePids.addAll(dataPids);
1794
					}
1795
				}
1796
			} else {
1797
				// just the lone pid in this package
1798
				packagePids.add(pid);
1799
			}
1800
	
1801
			// track the pid-to-file mapping
1802
			StringBuffer pidMapping = new StringBuffer();
1803
			// loop through the package contents
1804
			for (Identifier entryPid: packagePids) {
1805
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);
1806
				String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
1807
		        String prefix = entryPid.getValue();
1808
		        prefix = "entry";
1809
				File tempFile = File.createTempFile(prefix + "-", extension);
1810
				tempFiles.add(tempFile);
1811
				InputStream entryInputStream = this.get(session, entryPid);
1812
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
1813
				bag.addFileToPayload(tempFile);
1814
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
1815
			}
1816
			
1817
			//add the the pid to data file map
1818
			File pidMappingFile = File.createTempFile("pid-mapping-", ".txt");
1819
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
1820
			bag.addFileAsTag(pidMappingFile);
1821
			tempFiles.add(pidMappingFile);
1822
			
1823
			bag = bag.makeComplete();
1824
			
1825
			// TODO: consider using mangled-PID for filename
1826
			File bagFile = File.createTempFile("dataPackage-", ".zip");
1827
			
1828
			bag.setFile(bagFile);
1829
			ZipWriter zipWriter = new ZipWriter(bagFactory);
1830
			bag.write(zipWriter, bagFile);
1831
			bagFile = bag.getFile();
1832
			// use custom FIS that will delete the file when closed
1833
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
1834
			// also mark for deletion on shutdown in case the stream is never closed
1835
			bagFile.deleteOnExit();
1836
			
1837
			// clean up other temp files
1838
			for (File tf: tempFiles) {
1839
				tf.delete();
1840
			}
1841
		} catch (IOException e) {
1842
			// report as service failure
1843
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1844
			sf.initCause(e);
1845
			throw sf;
1846
		} catch (OREException e) {
1847
			// report as service failure
1848
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1849
			sf.initCause(e);
1850
			throw sf;
1851
		} catch (URISyntaxException e) {
1852
			// report as service failure
1853
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1854
			sf.initCause(e);
1855
			throw sf;
1856
		} catch (OREParserException e) {
1857
			// report as service failure
1858
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1859
			sf.initCause(e);
1860
			throw sf;
1861
		}
1862
		
1863
		return bagInputStream;
1864

    
1865
	}
1866
	
1867
	/**
1868
	 * Get a rendered view of the object identified by pid.
1869
	 * Uses the registered format given by the format parameter.
1870
	 * Typically, this is structured HTML that can be styled with CSS.
1871
	 * @param session
1872
	 * @param pid
1873
	 * @param format
1874
	 * @return
1875
	 * @throws InvalidToken
1876
	 * @throws ServiceFailure
1877
	 * @throws NotAuthorized
1878
	 * @throws NotFound
1879
	 * @throws NotImplemented
1880
	 */
1881
	public InputStream getView(Session session, Identifier pid, String format) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1882
		InputStream resultInputStream = null;
1883
		
1884
		SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1885
		InputStream object = this.get(session, pid);
1886

    
1887
		try {
1888
			// can only transform metadata, really
1889
			ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
1890
			if (objectFormat.getFormatType().equals("METADATA")) {
1891
				// transform
1892
				DBTransform transformer = new DBTransform();
1893
	            String documentContent = IOUtils.toString(object, "UTF-8");
1894
	            String sourceType = objectFormat.getFormatId().getValue();
1895
	            String targetType = "-//W3C//HTML//EN";
1896
	            ByteArrayOutputStream baos = new ByteArrayOutputStream();
1897
	            Writer writer = new OutputStreamWriter(baos , "UTF-8");
1898
	            // TODO: include more params?
1899
	            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
1900
	            String localId = null;
1901
				try {
1902
					localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1903
				} catch (McdbDocNotFoundException e) {
1904
					throw new NotFound("1020", e.getMessage());
1905
				}
1906
	            params.put("qformat", new String[] {format});	            
1907
	            params.put("docid", new String[] {localId});
1908
	            params.put("pid", new String[] {pid.getValue()});
1909
	            transformer.transformXMLDocument(
1910
	                    documentContent , 
1911
	                    sourceType, 
1912
	                    targetType , 
1913
	                    format, 
1914
	                    writer, 
1915
	                    params, 
1916
	                    null //sessionid
1917
	                    );
1918
	            
1919
	            // finally, get the HTML back
1920
	            resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
1921
	            ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
1922
	
1923
			} else {
1924
				// just return the raw bytes
1925
				resultInputStream = object;
1926
			}
1927
		} catch (IOException e) {
1928
			// report as service failure
1929
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1930
			sf.initCause(e);
1931
			throw sf;
1932
		} catch (PropertyNotFoundException e) {
1933
			// report as service failure
1934
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1935
			sf.initCause(e);
1936
			throw sf;
1937
		} catch (SQLException e) {
1938
			// report as service failure
1939
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1940
			sf.initCause(e);
1941
			throw sf;
1942
		} catch (ClassNotFoundException e) {
1943
			// report as service failure
1944
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1945
			sf.initCause(e);
1946
			throw sf;
1947
		}
1948
		
1949
		return resultInputStream;
1950
		
1951
	}	
1952
    
1953
}
(4-4/6)