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
		Identifier identifier = new Identifier();
1282
		
1283
		// handle different schemes
1284
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1285
			// UUID
1286
			UUID uuid = UUID.randomUUID();
1287
            identifier.setValue(UUID_PREFIX + uuid.toString());
1288
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1289
			// generate a DOI
1290
			try {
1291
				identifier = DOIService.getInstance().generateDOI();
1292
			} catch (EZIDException e) {
1293
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1294
				sf.initCause(e);
1295
				throw sf;
1296
			}
1297
		} else {
1298
			// default if we don't know the scheme
1299
			if (fragment != null) {
1300
				// for now, just autogen with fragment
1301
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1302
				identifier.setValue(autogenId);			
1303
			} else {
1304
				// autogen with no fragment
1305
				String autogenId = DocumentUtil.generateDocumentId(0);
1306
				identifier.setValue(autogenId);
1307
			}
1308
		}
1309
		
1310
		// TODO: reserve the identifier with the CN. We can only do this when
1311
		// 1) the MN is part of a CN cluster
1312
		// 2) the request is from an authenticated user
1313
		
1314
		return identifier;
1315
	}
1316

    
1317
	@Override
1318
	public boolean isAuthorized(Identifier pid, Permission permission)
1319
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1320
			NotAuthorized, NotImplemented {
1321

    
1322
		return isAuthorized(null, pid, permission);
1323
	}
1324

    
1325
	@Override
1326
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1327
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1328
			InvalidRequest {
1329

    
1330
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1331
	}
1332

    
1333
	@Override
1334
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1335
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1336
			NotAuthorized, NotImplemented, ServiceFailure {
1337

    
1338
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1339
	}
1340

    
1341
	@Override
1342
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1343
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1344

    
1345
		return describe(null, pid);
1346
	}
1347

    
1348
	@Override
1349
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
1350
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
1351

    
1352
		return get(null, pid);
1353
	}
1354

    
1355
	@Override
1356
	public Checksum getChecksum(Identifier pid, String algorithm)
1357
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1358
			ServiceFailure, NotFound {
1359

    
1360
		return getChecksum(null, pid, algorithm);
1361
	}
1362

    
1363
	@Override
1364
	public SystemMetadata getSystemMetadata(Identifier pid)
1365
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1366
			NotFound {
1367

    
1368
		return getSystemMetadata(null, pid);
1369
	}
1370

    
1371
	@Override
1372
	public ObjectList listObjects(Date startTime, Date endTime,
1373
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1374
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1375
			NotImplemented, ServiceFailure {
1376

    
1377
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1378
	}
1379

    
1380
	@Override
1381
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1382
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1383

    
1384
		return synchronizationFailed(null, syncFailed);
1385
	}
1386

    
1387
	@Override
1388
	public InputStream getReplica(Identifier pid) throws InvalidToken,
1389
			NotAuthorized, NotImplemented, ServiceFailure, NotFound,
1390
			InsufficientResources {
1391

    
1392
		return getReplica(null, pid);
1393
	}
1394

    
1395
	@Override
1396
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1397
			throws NotImplemented, ServiceFailure, NotAuthorized,
1398
			InvalidRequest, InvalidToken, InsufficientResources,
1399
			UnsupportedType {
1400

    
1401
		return replicate(null, sysmeta, sourceNode);
1402
	}
1403

    
1404
	@Override
1405
	public Identifier create(Identifier pid, InputStream object,
1406
			SystemMetadata sysmeta) throws IdentifierNotUnique,
1407
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1408
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1409
			UnsupportedType {
1410

    
1411
		return create(null, pid, object, sysmeta);
1412
	}
1413

    
1414
	@Override
1415
	public Identifier delete(Identifier pid) throws InvalidToken,
1416
			ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1417

    
1418
		return delete(null, pid);
1419
	}
1420

    
1421
	@Override
1422
	public Identifier generateIdentifier(String scheme, String fragment)
1423
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1424
			InvalidRequest {
1425

    
1426
		return generateIdentifier(null, scheme, fragment);
1427
	}
1428

    
1429
	@Override
1430
	public Identifier update(Identifier pid, InputStream object,
1431
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
1432
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1433
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1434
			UnsupportedType, NotFound {
1435

    
1436
		return update(null, pid, object, newPid, sysmeta);
1437
	}
1438

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

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

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

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

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

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

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

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

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