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
        
236
        //check if it is the authoritative member node
237
        if(!allowed) {
238
            allowed = isAuthoritativeMNodeAdmin(session, pid);
239
        }
240
        
241
        if (!allowed) { 
242
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
243
        }
244
    	
245
    	// defer to superclass implementation
246
        return super.delete(session, pid);
247
    }
248

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

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

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

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

    
321
        // does the subject have WRITE ( == update) priveleges on the pid?
322
        allowed = isAuthorized(session, pid, Permission.WRITE);
323

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

    
334
            // get the existing system metadata for the object
335
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
336

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

    
345
            isScienceMetadata = isScienceMetadata(sysmeta);
346

    
347
            // do we have XML metadata or a data object?
348
            if (isScienceMetadata) {
349

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

    
362
                    }
363

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

    
369
                }
370

    
371
            } else {
372

    
373
                // update the data object
374
                localId = insertDataObject(object, newPid, session);
375

    
376
            }
377
            
378
            // add the newPid to the obsoletedBy list for the existing sysmeta
379
            existingSysMeta.setObsoletedBy(newPid);
380

    
381
            // then update the existing system metadata
382
            updateSystemMetadata(existingSysMeta);
383

    
384
            // prep the new system metadata, add pid to the affected lists
385
            sysmeta.setObsoletes(pid);
386
            //sysmeta.addDerivedFrom(pid);
387

    
388
            // and insert the new system metadata
389
            insertSystemMetadata(sysmeta);
390

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

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

    
406
        return newPid;
407
    }
408

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

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

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

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

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

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

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

    
500
        // get the referenced object
501
        Identifier pid = sysmeta.getIdentifier();
502

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

    
523

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

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

    
537
        }
538
        
539

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

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

    
575
            }
576

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

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

    
591
        }
592

    
593
        // verify checksum on the object, if supported
594
        if (object.markSupported()) {
595
            Checksum givenChecksum = sysmeta.getChecksum();
596
            Checksum computedChecksum = null;
597
            try {
598
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
599
                object.reset();
600

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

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

    
638
        // finish by setting the replication status
639
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
640
        return result;
641

    
642
    }
643

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

    
662
        return super.get(session, pid);
663

    
664
    }
665

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

    
688
        Checksum checksum = null;
689

    
690
        InputStream inputStream = get(session, pid);
691

    
692
        try {
693
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
694

    
695
        } catch (NoSuchAlgorithmException e) {
696
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
697
                    + e.getMessage());
698
        } catch (IOException e) {
699
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
700
                    + e.getMessage());
701
        }
702

    
703
        if (checksum == null) {
704
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
705
        }
706

    
707
        return checksum;
708
    }
709

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

    
730
        return super.getSystemMetadata(session, pid);
731
    }
732

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

    
761
        ObjectList objectList = null;
762

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

    
773
        return objectList;
774
    }
775

    
776
    /**
777
     * Return a description of the node's capabilities and services.
778
     * 
779
     * @return node - the technical capabilities of the Member Node
780
     * 
781
     * @throws ServiceFailure
782
     * @throws NotAuthorized
783
     * @throws InvalidRequest
784
     * @throws NotImplemented
785
     */
786
    @Override
787
    public Node getCapabilities() 
788
        throws NotImplemented, ServiceFailure {
789

    
790
        String nodeName = null;
791
        String nodeId = null;
792
        String subject = null;
793
        String contactSubject = null;
794
        String nodeDesc = null;
795
        String nodeTypeString = null;
796
        NodeType nodeType = null;
797
        String mnCoreServiceVersion = null;
798
        String mnReadServiceVersion = null;
799
        String mnAuthorizationServiceVersion = null;
800
        String mnStorageServiceVersion = null;
801
        String mnReplicationServiceVersion = null;
802

    
803
        boolean nodeSynchronize = false;
804
        boolean nodeReplicate = false;
805
        boolean mnCoreServiceAvailable = false;
806
        boolean mnReadServiceAvailable = false;
807
        boolean mnAuthorizationServiceAvailable = false;
808
        boolean mnStorageServiceAvailable = false;
809
        boolean mnReplicationServiceAvailable = false;
810

    
811
        try {
812
            // get the properties of the node based on configuration information
813
            nodeName = PropertyService.getProperty("dataone.nodeName");
814
            nodeId = PropertyService.getProperty("dataone.nodeId");
815
            subject = PropertyService.getProperty("dataone.subject");
816
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
817
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
818
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
819
            nodeType = NodeType.convert(nodeTypeString);
820
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
821
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
822

    
823
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
824
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
825
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
826
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
827
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
828

    
829
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
830
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
831
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
832
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
833
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
834

    
835
            // Set the properties of the node based on configuration information and
836
            // calls to current status methods
837
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
838
            Node node = new Node();
839
            node.setBaseURL(serviceName + "/" + nodeTypeString);
840
            node.setDescription(nodeDesc);
841

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

    
858
            NodeReference identifier = new NodeReference();
859
            identifier.setValue(nodeId);
860
            node.setIdentifier(identifier);
861
            Subject s = new Subject();
862
            s.setValue(subject);
863
            node.addSubject(s);
864
            Subject contact = new Subject();
865
            contact.setValue(contactSubject);
866
            node.addContactSubject(contact);
867
            node.setName(nodeName);
868
            node.setReplicate(nodeReplicate);
869
            node.setSynchronize(nodeSynchronize);
870

    
871
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
872
            Services services = new Services();
873

    
874
            Service sMNCore = new Service();
875
            sMNCore.setName("MNCore");
876
            sMNCore.setVersion(mnCoreServiceVersion);
877
            sMNCore.setAvailable(mnCoreServiceAvailable);
878

    
879
            Service sMNRead = new Service();
880
            sMNRead.setName("MNRead");
881
            sMNRead.setVersion(mnReadServiceVersion);
882
            sMNRead.setAvailable(mnReadServiceAvailable);
883

    
884
            Service sMNAuthorization = new Service();
885
            sMNAuthorization.setName("MNAuthorization");
886
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
887
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
888

    
889
            Service sMNStorage = new Service();
890
            sMNStorage.setName("MNStorage");
891
            sMNStorage.setVersion(mnStorageServiceVersion);
892
            sMNStorage.setAvailable(mnStorageServiceAvailable);
893

    
894
            Service sMNReplication = new Service();
895
            sMNReplication.setName("MNReplication");
896
            sMNReplication.setVersion(mnReplicationServiceVersion);
897
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
898

    
899
            services.addService(sMNRead);
900
            services.addService(sMNCore);
901
            services.addService(sMNAuthorization);
902
            services.addService(sMNStorage);
903
            services.addService(sMNReplication);
904
            node.setServices(services);
905

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

    
922
            node.setType(nodeType);
923
            return node;
924

    
925
        } catch (PropertyNotFoundException pnfe) {
926
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
927
            logMetacat.error(msg);
928
            throw new ServiceFailure("2162", msg);
929
        }
930
    }
931

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

    
955
        MonitorList monitorList = new MonitorList();
956

    
957
        try {
958

    
959
            // get log records first
960
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
961

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

    
980
            }
981
        } catch (Exception e) {
982
            e.printStackTrace();
983
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
984
        }
985

    
986
        return monitorList;
987

    
988
    }
989

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

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

    
1024
            }
1025
            
1026
        } else {
1027
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1028

    
1029
        }
1030
        
1031
        try {
1032
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1033
        } catch (McdbDocNotFoundException e) {
1034
            throw new ServiceFailure("2161", "The identifier specified by " + 
1035
                    syncFailed.getPid() + " was not found on this node.");
1036

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

    
1057
    }
1058

    
1059
    /**
1060
     * Essentially a get() but with different logging behavior
1061
     */
1062
    @Override
1063
    public InputStream getReplica(Session session, Identifier pid) 
1064
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1065

    
1066
        logMetacat.info("MNodeService.getReplica() called.");
1067

    
1068
        // cannot be called by public
1069
        if (session == null) {
1070
        	throw new InvalidToken("2183", "No session was provided.");
1071
        }
1072
        
1073
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1074
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1075
             "\tIdentifier           = " + pid.getValue());
1076

    
1077
        InputStream inputStream = null; // bytes to be returned
1078
        handler = new MetacatHandler(new Timer());
1079
        boolean allowed = false;
1080
        String localId; // the metacat docid for the pid
1081

    
1082
        // get the local docid from Metacat
1083
        try {
1084
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1085
        } catch (McdbDocNotFoundException e) {
1086
            throw new ServiceFailure("2181", "The object specified by " + 
1087
                    pid.getValue() + " does not exist at this node.");
1088
            
1089
        }
1090

    
1091
        Subject targetNodeSubject = session.getSubject();
1092

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

    
1104
        } catch (InvalidRequest e1) {
1105
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1106
                    + e1.getMessage());
1107

    
1108
        }
1109

    
1110
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1111
            " for identifier " + pid.getValue());
1112

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

    
1123
        // if we fail to set the input stream
1124
        if (inputStream == null) {
1125
            throw new ServiceFailure("2181", "The object specified by " + 
1126
                pid.getValue() + "does not exist at this node.");
1127
        }
1128

    
1129
        // log the replica event
1130
        String principal = null;
1131
        if (session.getSubject() != null) {
1132
            principal = session.getSubject().getValue();
1133
        }
1134
        EventLog.getInstance().log(request.getRemoteAddr(), 
1135
            request.getHeader("User-Agent"), principal, localId, "replicate");
1136

    
1137
        return inputStream;
1138
    }
1139

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

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

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

    
1328
	@Override
1329
	public boolean isAuthorized(Identifier pid, Permission permission)
1330
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1331
			NotAuthorized, NotImplemented {
1332

    
1333
		return isAuthorized(null, pid, permission);
1334
	}
1335

    
1336
	@Override
1337
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1338
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1339
			InvalidRequest {
1340

    
1341
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1342
	}
1343

    
1344
	@Override
1345
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1346
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1347
			NotAuthorized, NotImplemented, ServiceFailure {
1348

    
1349
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1350
	}
1351

    
1352
	@Override
1353
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1354
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1355

    
1356
		return describe(null, pid);
1357
	}
1358

    
1359
	@Override
1360
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
1361
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
1362

    
1363
		return get(null, pid);
1364
	}
1365

    
1366
	@Override
1367
	public Checksum getChecksum(Identifier pid, String algorithm)
1368
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1369
			ServiceFailure, NotFound {
1370

    
1371
		return getChecksum(null, pid, algorithm);
1372
	}
1373

    
1374
	@Override
1375
	public SystemMetadata getSystemMetadata(Identifier pid)
1376
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1377
			NotFound {
1378

    
1379
		return getSystemMetadata(null, pid);
1380
	}
1381

    
1382
	@Override
1383
	public ObjectList listObjects(Date startTime, Date endTime,
1384
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1385
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1386
			NotImplemented, ServiceFailure {
1387

    
1388
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1389
	}
1390

    
1391
	@Override
1392
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1393
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1394

    
1395
		return synchronizationFailed(null, syncFailed);
1396
	}
1397

    
1398
	@Override
1399
	public InputStream getReplica(Identifier pid) throws InvalidToken,
1400
			NotAuthorized, NotImplemented, ServiceFailure, NotFound,
1401
			InsufficientResources {
1402

    
1403
		return getReplica(null, pid);
1404
	}
1405

    
1406
	@Override
1407
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1408
			throws NotImplemented, ServiceFailure, NotAuthorized,
1409
			InvalidRequest, InvalidToken, InsufficientResources,
1410
			UnsupportedType {
1411

    
1412
		return replicate(null, sysmeta, sourceNode);
1413
	}
1414

    
1415
	@Override
1416
	public Identifier create(Identifier pid, InputStream object,
1417
			SystemMetadata sysmeta) throws IdentifierNotUnique,
1418
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1419
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1420
			UnsupportedType {
1421

    
1422
		return create(null, pid, object, sysmeta);
1423
	}
1424

    
1425
	@Override
1426
	public Identifier delete(Identifier pid) throws InvalidToken,
1427
			ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1428

    
1429
		return delete(null, pid);
1430
	}
1431

    
1432
	@Override
1433
	public Identifier generateIdentifier(String scheme, String fragment)
1434
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1435
			InvalidRequest {
1436

    
1437
		return generateIdentifier(null, scheme, fragment);
1438
	}
1439

    
1440
	@Override
1441
	public Identifier update(Identifier pid, InputStream object,
1442
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
1443
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1444
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1445
			UnsupportedType, NotFound {
1446

    
1447
		return update(null, pid, object, newPid, sysmeta);
1448
	}
1449

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

    
1498
	@Override
1499
	public QueryEngineList listQueryEngines() throws InvalidToken,
1500
			ServiceFailure, NotAuthorized, NotImplemented {
1501
		QueryEngineList qel = new QueryEngineList();
1502
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1503
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1504
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1505
		for(String name : enables) {
1506
		    qel.addQueryEngine(name);
1507
		}
1508
		return qel;
1509
	}
1510

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

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

    
1596
		// make copy of it
1597
		SystemMetadata sysmeta = new SystemMetadata();
1598
		try {
1599
			BeanUtils.copyProperties(sysmeta, originalSystemMetadata);
1600
		} catch (Exception e) {
1601
			// report as service failure
1602
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1603
			sf.initCause(e);
1604
			throw sf;
1605
		}
1606

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

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

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

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