Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000-2011 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author:  $'
7
 *     '$Date:  $'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23

    
24
package edu.ucsb.nceas.metacat.dataone;
25

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

    
52
import javax.servlet.http.HttpServletRequest;
53

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

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

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

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

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

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

    
189

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

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

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

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

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

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

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

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

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

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

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

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

    
343
            isScienceMetadata = isScienceMetadata(sysmeta);
344

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

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

    
360
                    }
361

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

    
367
                }
368

    
369
            } else {
370

    
371
                // update the data object
372
                localId = insertDataObject(object, newPid, session);
373

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

    
379
            // then update the existing system metadata
380
            updateSystemMetadata(existingSysMeta);
381

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

    
386
            // and insert the new system metadata
387
            insertSystemMetadata(sysmeta);
388

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

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

    
404
        return newPid;
405
    }
406

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

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

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

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

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

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

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

    
498
        // get the referenced object
499
        Identifier pid = sysmeta.getIdentifier();
500

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

    
521

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

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

    
536
        }
537
        
538

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

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

    
574
            }
575

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

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

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

    
616
        // verify checksum on the object, if supported
617
        if (object.markSupported()) {
618
            Checksum givenChecksum = sysmeta.getChecksum();
619
            Checksum computedChecksum = null;
620
            try {
621
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
622
                object.reset();
623

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

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

    
664
        // finish by setting the replication status
665
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
666
        return result;
667

    
668
    }
669

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

    
688
        return super.get(session, pid);
689

    
690
    }
691

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

    
714
        Checksum checksum = null;
715

    
716
        InputStream inputStream = get(session, pid);
717

    
718
        try {
719
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
720

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

    
729
        if (checksum == null) {
730
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
731
        }
732

    
733
        return checksum;
734
    }
735

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

    
756
        return super.getSystemMetadata(session, pid);
757
    }
758

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

    
787
        ObjectList objectList = null;
788

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

    
799
        return objectList;
800
    }
801

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

    
816
        String nodeName = null;
817
        String nodeId = null;
818
        String subject = null;
819
        String contactSubject = null;
820
        String nodeDesc = null;
821
        String nodeTypeString = null;
822
        NodeType nodeType = null;
823
        String mnCoreServiceVersion = null;
824
        String mnReadServiceVersion = null;
825
        String mnAuthorizationServiceVersion = null;
826
        String mnStorageServiceVersion = null;
827
        String mnReplicationServiceVersion = null;
828

    
829
        boolean nodeSynchronize = false;
830
        boolean nodeReplicate = false;
831
        boolean mnCoreServiceAvailable = false;
832
        boolean mnReadServiceAvailable = false;
833
        boolean mnAuthorizationServiceAvailable = false;
834
        boolean mnStorageServiceAvailable = false;
835
        boolean mnReplicationServiceAvailable = false;
836

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

    
849
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
850
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
851
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
852
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
853
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
854

    
855
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
856
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
857
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
858
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
859
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
860

    
861
            // Set the properties of the node based on configuration information and
862
            // calls to current status methods
863
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
864
            Node node = new Node();
865
            node.setBaseURL(serviceName + "/" + nodeTypeString);
866
            node.setDescription(nodeDesc);
867

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

    
884
            NodeReference identifier = new NodeReference();
885
            identifier.setValue(nodeId);
886
            node.setIdentifier(identifier);
887
            Subject s = new Subject();
888
            s.setValue(subject);
889
            node.addSubject(s);
890
            Subject contact = new Subject();
891
            contact.setValue(contactSubject);
892
            node.addContactSubject(contact);
893
            node.setName(nodeName);
894
            node.setReplicate(nodeReplicate);
895
            node.setSynchronize(nodeSynchronize);
896

    
897
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
898
            Services services = new Services();
899

    
900
            Service sMNCore = new Service();
901
            sMNCore.setName("MNCore");
902
            sMNCore.setVersion(mnCoreServiceVersion);
903
            sMNCore.setAvailable(mnCoreServiceAvailable);
904

    
905
            Service sMNRead = new Service();
906
            sMNRead.setName("MNRead");
907
            sMNRead.setVersion(mnReadServiceVersion);
908
            sMNRead.setAvailable(mnReadServiceAvailable);
909

    
910
            Service sMNAuthorization = new Service();
911
            sMNAuthorization.setName("MNAuthorization");
912
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
913
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
914

    
915
            Service sMNStorage = new Service();
916
            sMNStorage.setName("MNStorage");
917
            sMNStorage.setVersion(mnStorageServiceVersion);
918
            sMNStorage.setAvailable(mnStorageServiceAvailable);
919

    
920
            Service sMNReplication = new Service();
921
            sMNReplication.setName("MNReplication");
922
            sMNReplication.setVersion(mnReplicationServiceVersion);
923
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
924

    
925
            services.addService(sMNRead);
926
            services.addService(sMNCore);
927
            services.addService(sMNAuthorization);
928
            services.addService(sMNStorage);
929
            services.addService(sMNReplication);
930
            node.setServices(services);
931

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

    
948
            node.setType(nodeType);
949
            return node;
950

    
951
        } catch (PropertyNotFoundException pnfe) {
952
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
953
            logMetacat.error(msg);
954
            throw new ServiceFailure("2162", msg);
955
        }
956
    }
957

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

    
981
        MonitorList monitorList = new MonitorList();
982

    
983
        try {
984

    
985
            // get log records first
986
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
987

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

    
1006
            }
1007
        } catch (Exception e) {
1008
            e.printStackTrace();
1009
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
1010
        }
1011

    
1012
        return monitorList;
1013

    
1014
    }
1015

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

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

    
1050
            }
1051
            
1052
        } else {
1053
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1054

    
1055
        }
1056
        
1057
        try {
1058
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1059
        } catch (McdbDocNotFoundException e) {
1060
            throw new ServiceFailure("2161", "The identifier specified by " + 
1061
                    syncFailed.getPid() + " was not found on this node.");
1062

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

    
1083
    }
1084

    
1085
    /**
1086
     * Essentially a get() but with different logging behavior
1087
     */
1088
    @Override
1089
    public InputStream getReplica(Session session, Identifier pid) 
1090
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1091

    
1092
        logMetacat.info("MNodeService.getReplica() called.");
1093

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

    
1103
        InputStream inputStream = null; // bytes to be returned
1104
        handler = new MetacatHandler(new Timer());
1105
        boolean allowed = false;
1106
        String localId; // the metacat docid for the pid
1107

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

    
1117
        Subject targetNodeSubject = session.getSubject();
1118

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

    
1130
        } catch (InvalidRequest e1) {
1131
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1132
                    + e1.getMessage());
1133

    
1134
        }
1135

    
1136
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1137
            " for identifier " + pid.getValue());
1138

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

    
1149
        // if we fail to set the input stream
1150
        if (inputStream == null) {
1151
            throw new ServiceFailure("2181", "The object specified by " + 
1152
                pid.getValue() + "does not exist at this node.");
1153
        }
1154

    
1155
        // log the replica event
1156
        String principal = null;
1157
        if (session.getSubject() != null) {
1158
            principal = session.getSubject().getValue();
1159
        }
1160
        EventLog.getInstance().log(request.getRemoteAddr(), 
1161
            request.getHeader("User-Agent"), principal, localId, "replicate");
1162

    
1163
        return inputStream;
1164
    }
1165

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

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

    
1308
	@Override
1309
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1310
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1311
			InvalidRequest {
1312
		
1313
		// check for null session
1314
        if (session == null) {
1315
          throw new InvalidToken("2190", "Session is required to generate an Identifier at this Node.");
1316
        }
1317
		
1318
		Identifier identifier = new Identifier();
1319
		
1320
		// handle different schemes
1321
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1322
			// UUID
1323
			UUID uuid = UUID.randomUUID();
1324
            identifier.setValue(UUID_PREFIX + uuid.toString());
1325
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1326
			// generate a DOI
1327
			try {
1328
				identifier = DOIService.getInstance().generateDOI();
1329
			} catch (EZIDException e) {
1330
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1331
				sf.initCause(e);
1332
				throw sf;
1333
			}
1334
		} else {
1335
			// default if we don't know the scheme
1336
			if (fragment != null) {
1337
				// for now, just autogen with fragment
1338
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1339
				identifier.setValue(autogenId);			
1340
			} else {
1341
				// autogen with no fragment
1342
				String autogenId = DocumentUtil.generateDocumentId(0);
1343
				identifier.setValue(autogenId);
1344
			}
1345
		}
1346
		
1347
		// TODO: reserve the identifier with the CN. We can only do this when
1348
		// 1) the MN is part of a CN cluster
1349
		// 2) the request is from an authenticated user
1350
		
1351
		return identifier;
1352
	}
1353

    
1354
	@Override
1355
	public boolean isAuthorized(Identifier pid, Permission permission)
1356
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1357
			NotAuthorized, NotImplemented {
1358

    
1359
		return isAuthorized(null, pid, permission);
1360
	}
1361

    
1362
	@Override
1363
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1364
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1365
			InvalidRequest {
1366

    
1367
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1368
	}
1369

    
1370
	@Override
1371
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1372
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1373
			NotAuthorized, NotImplemented, ServiceFailure {
1374

    
1375
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1376
	}
1377

    
1378
	@Override
1379
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1380
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1381

    
1382
		return describe(null, pid);
1383
	}
1384

    
1385
	@Override
1386
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
1387
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
1388

    
1389
		return get(null, pid);
1390
	}
1391

    
1392
	@Override
1393
	public Checksum getChecksum(Identifier pid, String algorithm)
1394
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1395
			ServiceFailure, NotFound {
1396

    
1397
		return getChecksum(null, pid, algorithm);
1398
	}
1399

    
1400
	@Override
1401
	public SystemMetadata getSystemMetadata(Identifier pid)
1402
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1403
			NotFound {
1404

    
1405
		return getSystemMetadata(null, pid);
1406
	}
1407

    
1408
	@Override
1409
	public ObjectList listObjects(Date startTime, Date endTime,
1410
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1411
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1412
			NotImplemented, ServiceFailure {
1413

    
1414
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1415
	}
1416

    
1417
	@Override
1418
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1419
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1420

    
1421
		return synchronizationFailed(null, syncFailed);
1422
	}
1423

    
1424
	@Override
1425
	public InputStream getReplica(Identifier pid) throws InvalidToken,
1426
			NotAuthorized, NotImplemented, ServiceFailure, NotFound,
1427
			InsufficientResources {
1428

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

    
1432
	@Override
1433
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1434
			throws NotImplemented, ServiceFailure, NotAuthorized,
1435
			InvalidRequest, InvalidToken, InsufficientResources,
1436
			UnsupportedType {
1437

    
1438
		return replicate(null, sysmeta, sourceNode);
1439
	}
1440

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

    
1448
		return create(null, pid, object, sysmeta);
1449
	}
1450

    
1451
	@Override
1452
	public Identifier delete(Identifier pid) throws InvalidToken,
1453
			ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1454

    
1455
		return delete(null, pid);
1456
	}
1457

    
1458
	@Override
1459
	public Identifier generateIdentifier(String scheme, String fragment)
1460
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1461
			InvalidRequest {
1462

    
1463
		return generateIdentifier(null, scheme, fragment);
1464
	}
1465

    
1466
	@Override
1467
	public Identifier update(Identifier pid, InputStream object,
1468
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
1469
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1470
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1471
			UnsupportedType, NotFound {
1472

    
1473
		return update(null, pid, object, newPid, sysmeta);
1474
	}
1475

    
1476
	@Override
1477
	public QueryEngineDescription getQueryEngineDescription(String engine)
1478
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1479
			NotFound {
1480
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1481
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1482
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1483
            }
1484
	        QueryEngineDescription qed = new QueryEngineDescription();
1485
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1486
	        qed.setQueryEngineVersion("1.0");
1487
	        qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1488
	        Vector<String> pathsForIndexing = null;
1489
	        try {
1490
	            pathsForIndexing = SystemUtil.getPathsForIndexing();
1491
	        } catch (MetacatUtilException e) {
1492
	            logMetacat.warn("Could not get index paths", e);
1493
	        }
1494
	        for (String fieldName: pathsForIndexing) {
1495
	            QueryField field = new QueryField();
1496
	            field.addDescription("Indexed field for path '" + fieldName + "'");
1497
	            field.setName(fieldName);
1498
	            field.setReturnable(true);
1499
	            field.setSearchable(true);
1500
	            field.setSortable(false);
1501
	            // TODO: determine type and multivaluedness
1502
	            field.setType(String.class.getName());
1503
	            //field.setMultivalued(true);
1504
	            qed.addQueryField(field);
1505
	        }
1506
	        return qed;
1507
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1508
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1509
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1510
            }
1511
	        try {
1512
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1513
	            return qed;
1514
	        } catch (Exception e) {
1515
	            e.printStackTrace();
1516
	            throw new ServiceFailure("Solr server error", e.getMessage());
1517
	        }
1518
	    } else {
1519
	        throw new NotFound("404", "The Metacat member node can't find the query engine - "+engine);
1520
	    }
1521
		
1522
	}
1523

    
1524
	@Override
1525
	public QueryEngineList listQueryEngines() throws InvalidToken,
1526
			ServiceFailure, NotAuthorized, NotImplemented {
1527
		QueryEngineList qel = new QueryEngineList();
1528
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1529
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1530
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1531
		for(String name : enables) {
1532
		    qel.addQueryEngine(name);
1533
		}
1534
		return qel;
1535
	}
1536

    
1537
	@Override
1538
	public InputStream query(String engine, String query) throws InvalidToken,
1539
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1540
			NotFound {
1541
	    String user = Constants.SUBJECT_PUBLIC;
1542
        String[] groups= null;
1543
        Set<Subject> subjects = null;
1544
        if (session != null) {
1545
            user = session.getSubject().getValue();
1546
            subjects = AuthUtils.authorizedClientSubjects(session);
1547
            if (subjects != null) {
1548
                List<String> groupList = new ArrayList<String>();
1549
                for (Subject subject: subjects) {
1550
                    groupList.add(subject.getValue());
1551
                }
1552
                groups = groupList.toArray(new String[0]);
1553
            }
1554
        } else {
1555
            //add the public user subject to the set 
1556
            Subject subject = new Subject();
1557
            subject.setValue(Constants.SUBJECT_PUBLIC);
1558
            subjects = new HashSet<Subject>();
1559
            subjects.add(subject);
1560
        }
1561
        //System.out.println("====== user is "+user);
1562
        //System.out.println("====== groups are "+groups);
1563
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1564
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1565
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1566
            }
1567
			try {
1568
				DBQuery queryobj = new DBQuery();
1569
				
1570
				String results = queryobj.performPathquery(query, user, groups);
1571
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1572
				ctbais.setContentType("text/xml");
1573
				return ctbais;
1574

    
1575
			} catch (Exception e) {
1576
				throw new ServiceFailure("Pathquery error", e.getMessage());
1577
			}
1578
			
1579
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1580
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1581
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1582
		    }
1583
		    logMetacat.info("The query is ==================================== \n"+query);
1584
		    try {
1585
		        
1586
                return MetacatSolrIndex.getInstance().query(query, subjects);
1587
            } catch (Exception e) {
1588
                // TODO Auto-generated catch block
1589
                throw new ServiceFailure("Solr server error", e.getMessage());
1590
            } 
1591
		}
1592
		return null;
1593
	}
1594
	
1595
	/**
1596
	 * Given an existing Science Metadata PID, this method mints a DOI
1597
	 * and updates the original object "publishing" the update with the DOI.
1598
	 * This includes updating the ORE map that describes the Science Metadata+data.
1599
	 * TODO: ensure all referenced objects allow public read
1600
	 * 
1601
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1602
	 * 
1603
	 * @param originalIdentifier
1604
	 * @param request
1605
	 * @throws InvalidRequest 
1606
	 * @throws NotImplemented 
1607
	 * @throws NotAuthorized 
1608
	 * @throws ServiceFailure 
1609
	 * @throws InvalidToken 
1610
	 * @throws NotFound
1611
	 * @throws InvalidSystemMetadata 
1612
	 * @throws InsufficientResources 
1613
	 * @throws UnsupportedType 
1614
	 * @throws IdentifierNotUnique 
1615
	 */
1616
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata {
1617
		
1618
		
1619
		// get the original SM
1620
		SystemMetadata originalSystemMetadata = this.getSystemMetadata(session, originalIdentifier);
1621

    
1622
		// make copy of it
1623
		SystemMetadata sysmeta = new SystemMetadata();
1624
		try {
1625
			BeanUtils.copyProperties(sysmeta, originalSystemMetadata);
1626
		} catch (Exception e) {
1627
			// report as service failure
1628
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1629
			sf.initCause(e);
1630
			throw sf;
1631
		}
1632

    
1633
		// mint a DOI for the new revision
1634
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1635
				
1636
		// set new metadata values
1637
		sysmeta.setIdentifier(newIdentifier);
1638
		sysmeta.setObsoletes(originalIdentifier);
1639
		sysmeta.setObsoletedBy(null);
1640
		
1641
		// get the bytes
1642
		InputStream inputStream = this.get(session, originalIdentifier);
1643
		
1644
		// update the object
1645
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
1646
		
1647
		// update ORE that references the scimeta
1648
		// first try the naive method, then check the SOLR index
1649
		try {
1650
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1651
			
1652
			Identifier potentialOreIdentifier = new Identifier();
1653
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1654
			
1655
			InputStream oreInputStream = null;
1656
			try {
1657
				oreInputStream = this.get(session, potentialOreIdentifier);
1658
			} catch (NotFound nf) {
1659
				// this is probably okay for many sci meta data docs
1660
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1661
				// try the SOLR index
1662
				List<Identifier> potentialOreIdentifiers = this.lookupOreFor(originalIdentifier, false);
1663
				if (potentialOreIdentifiers != null) {
1664
					potentialOreIdentifier = potentialOreIdentifiers.get(0);
1665
					try {
1666
						oreInputStream = this.get(session, potentialOreIdentifier);
1667
					} catch (NotFound nf2) {
1668
						// this is probably okay for many sci meta data docs
1669
						logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1670
					}
1671
				}
1672
			}
1673
			if (oreInputStream != null) {
1674
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1675
	
1676
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1677
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1678
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1679
				
1680
				// TODO: ensure all data package objects allow public read
1681
	
1682
				// reconstruct the ORE with the new identifiers
1683
				sciMetaMap.remove(originalIdentifier);
1684
				sciMetaMap.put(newIdentifier, dataIdentifiers);
1685
				
1686
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1687
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1688
				
1689
				// get the original ORE SM and update the values
1690
				SystemMetadata originalOreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1691
				SystemMetadata oreSysMeta = new SystemMetadata();
1692
				try {
1693
					BeanUtils.copyProperties(oreSysMeta, originalOreSysMeta);
1694
				} catch (Exception e) {
1695
					// report as service failure
1696
					ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1697
					sf.initCause(e);
1698
					throw sf;
1699
				}
1700

    
1701
				oreSysMeta.setIdentifier(newOreIdentifier);
1702
				oreSysMeta.setObsoletes(potentialOreIdentifier);
1703
				oreSysMeta.setObsoletedBy(null);
1704
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
1705
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
1706
				
1707
				// save the updated ORE
1708
				this.update(
1709
						session, 
1710
						potentialOreIdentifier, 
1711
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")), 
1712
						newOreIdentifier, 
1713
						oreSysMeta);
1714
				
1715
			} else {
1716
				// create a new ORE for them
1717
				// https://projects.ecoinformatics.org/ecoinfo/issues/6194
1718
				try {
1719
					// find the local id for the NEW package.
1720
					String newLocalId = IdentifierManager.getInstance().getLocalId(newIdentifier.getValue());
1721
	
1722
					@SuppressWarnings("unused")
1723
					SystemMetadata extraSysMeta = SystemMetadataFactory.createSystemMetadata(newLocalId, true, false);
1724
					// should be done generating the ORE here
1725
					
1726
				} catch (Exception e) {
1727
					// oops, guess there was a problem - no package for you
1728
					logMetacat.error("Could not generate new ORE for published object: " + newIdentifier.getValue(), e);
1729
				}
1730
			}
1731
		} catch (McdbDocNotFoundException e) {
1732
			// report as service failure
1733
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1734
			sf.initCause(e);
1735
			throw sf;
1736
		} catch (UnsupportedEncodingException e) {
1737
			// report as service failure
1738
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1739
			sf.initCause(e);
1740
			throw sf;
1741
		} catch (OREException e) {
1742
			// report as service failure
1743
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1744
			sf.initCause(e);
1745
			throw sf;
1746
		} catch (URISyntaxException e) {
1747
			// report as service failure
1748
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1749
			sf.initCause(e);
1750
			throw sf;
1751
		} catch (OREParserException e) {
1752
			// report as service failure
1753
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1754
			sf.initCause(e);
1755
			throw sf;
1756
		} catch (ORESerialiserException e) {
1757
			// report as service failure
1758
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1759
			sf.initCause(e);
1760
			throw sf;
1761
		} catch (NoSuchAlgorithmException e) {
1762
			// report as service failure
1763
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1764
			sf.initCause(e);
1765
			throw sf;
1766
		}
1767
		
1768
		return newIdentifier;
1769
	}
1770
	
1771
	/**
1772
	 * Determines if we already have registered an ORE map for this package
1773
	 * NOTE: uses a solr query to locate OREs for the object
1774
	 * @param guid of the EML/packaging object
1775
	 * @return list of resource map identifiers for the given pid
1776
	 */
1777
	public List<Identifier> lookupOreFor(Identifier guid, boolean includeObsolete) {
1778
		// Search for the ORE if we can find it
1779
		String pid = guid.getValue();
1780
		List<Identifier> retList = null;
1781
		try {
1782
			String query = "fl=id,resourceMap&wt=xml&q=-obsoletedBy:*+resourceMap:*+id:\"" + pid + "\"";;
1783
			if (includeObsolete) {
1784
				query = "fl=id,resourceMap&wt=xml&q=resourceMap:*+id:\"" + pid + "\"";
1785
			}
1786
			
1787
			InputStream results = this.query("solr", query);
1788
			org.w3c.dom.Node rootNode = XMLUtilities.getXMLReaderAsDOMTreeRootNode(new InputStreamReader(results, "UTF-8"));
1789
			//String resultString = XMLUtilities.getDOMTreeAsString(rootNode);
1790
			org.w3c.dom.NodeList nodeList = XMLUtilities.getNodeListWithXPath(rootNode, "//arr[@name=\"resourceMap\"]/str");
1791
			if (nodeList != null && nodeList.getLength() > 0) {
1792
				retList = new ArrayList<Identifier>();
1793
				for (int i = 0; i < nodeList.getLength(); i++) {
1794
					String found = nodeList.item(i).getFirstChild().getNodeValue();
1795
					Identifier oreId = new Identifier();
1796
					oreId.setValue(found);
1797
					retList.add(oreId);
1798
				}
1799
			}
1800
		} catch (Exception e) {
1801
			logMetacat.error("Error checking for resourceMap[s] on pid " + pid + ". " + e.getMessage(), e);
1802
		}
1803
		
1804
		return retList;
1805
	}
1806
	
1807
	/**
1808
	 * Packages the given package in a Bagit collection for download
1809
	 * @param pid
1810
	 * @throws NotImplemented 
1811
	 * @throws NotFound 
1812
	 * @throws NotAuthorized 
1813
	 * @throws ServiceFailure 
1814
	 * @throws InvalidToken 
1815
	 */
1816
	public InputStream getPackage(Session session, Identifier pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1817
		
1818
		InputStream bagInputStream = null;
1819
		BagFactory bagFactory = new BagFactory();
1820
		Bag bag = bagFactory.createBag();
1821
		
1822
		// track the temp files we use so we can delete them when finished
1823
		List<File> tempFiles = new ArrayList<File>();
1824
		
1825
		// the pids to include in the package
1826
		List<Identifier> packagePids = new ArrayList<Identifier>();
1827
		
1828
		// catch non-D1 service errors and throw as ServiceFailures
1829
		try {
1830
			
1831
			// find the package contents
1832
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1833
			if (ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
1834
				InputStream oreInputStream = this.get(session, pid);
1835
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1836
				packagePids.addAll(resourceMapStructure.keySet());
1837
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
1838
					packagePids.addAll(entries.keySet());
1839
					for (List<Identifier> dataPids: entries.values()) {
1840
						packagePids.addAll(dataPids);
1841
					}
1842
				}
1843
			} else {
1844
				// just the lone pid in this package
1845
				packagePids.add(pid);
1846
			}
1847
			
1848
			//Create a temp directory in the default temp directory
1849
			String defaultTempDir = System.getProperty("java.io.tmpdir");
1850
			File tempDir = new File(defaultTempDir + "/" + System.nanoTime());
1851
			tempFiles.add(tempDir);
1852
			tempDir.mkdir();
1853
			
1854
			// track the pid-to-file mapping
1855
			StringBuffer pidMapping = new StringBuffer();
1856
			
1857
			// loop through the package contents
1858
			for (Identifier entryPid: packagePids) {
1859
				//Get the system metadata for each item
1860
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);
1861
				
1862
				//Create the temp file extension and prefix
1863
				String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
1864
				String objectFormatType = ObjectFormatCache.getInstance().getFormat(entrySysMeta.getFormatId()).getFormatType();
1865
				String fileName = entryPid.getValue().replaceAll("\\W+", "_") + "-" + objectFormatType;			
1866
				
1867
		        //Create a new file for this item and add to the list
1868
				File tempFile = new File(tempDir, fileName+extension);
1869
				tempFiles.add(tempFile);
1870
				
1871
				InputStream entryInputStream = this.get(session, entryPid);			
1872
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
1873
				bag.addFileToPayload(tempFile);
1874
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
1875
			}
1876
			
1877
			//add the the pid to data file map
1878
			File pidMappingFile = new File(tempDir, "pid-mapping.txt");
1879
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
1880
			bag.addFileAsTag(pidMappingFile);
1881
			tempFiles.add(pidMappingFile);
1882
			
1883
			bag = bag.makeComplete();
1884
			
1885
			///Now create the zip file
1886
			//Use the pid as the file name prefix, replacing illegal characters with a hyphen
1887
			String zipName = pid.getValue().replaceAll("\\W+", "_");
1888
			
1889
			File bagFile = new File(tempDir, zipName+".zip");
1890
			
1891
			bag.setFile(bagFile);
1892
			ZipWriter zipWriter = new ZipWriter(bagFactory);
1893
			bag.write(zipWriter, bagFile);
1894
			bagFile = bag.getFile();
1895
			// use custom FIS that will delete the file when closed
1896
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
1897
			// also mark for deletion on shutdown in case the stream is never closed
1898
			bagFile.deleteOnExit();
1899
			tempFiles.add(bagFile);
1900
			
1901
			// clean up other temp files
1902
			for(int i=tempFiles.size()-1; i>=0; i--){
1903
				File tf = new File(tempFiles.get(i).getPath());
1904
				tf.delete();
1905
			}
1906
			
1907
		} catch (IOException e) {
1908
			// report as service failure
1909
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1910
			sf.initCause(e);
1911
			throw sf;
1912
		} catch (OREException e) {
1913
			// report as service failure
1914
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1915
			sf.initCause(e);
1916
			throw sf;
1917
		} catch (URISyntaxException e) {
1918
			// report as service failure
1919
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1920
			sf.initCause(e);
1921
			throw sf;
1922
		} catch (OREParserException e) {
1923
			// report as service failure
1924
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1925
			sf.initCause(e);
1926
			throw sf;
1927
		}
1928
		
1929
		return bagInputStream;
1930

    
1931
	}
1932
	
1933
	/**
1934
	 * Get a rendered view of the object identified by pid.
1935
	 * Uses the registered format given by the format parameter.
1936
	 * Typically, this is structured HTML that can be styled with CSS.
1937
	 * @param session
1938
	 * @param pid
1939
	 * @param format
1940
	 * @return
1941
	 * @throws InvalidToken
1942
	 * @throws ServiceFailure
1943
	 * @throws NotAuthorized
1944
	 * @throws NotFound
1945
	 * @throws NotImplemented
1946
	 */
1947
	public InputStream getView(Session session, Identifier pid, String format) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1948
		InputStream resultInputStream = null;
1949
		
1950
		SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1951
		InputStream object = this.get(session, pid);
1952

    
1953
		try {
1954
			// can only transform metadata, really
1955
			ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
1956
			if (objectFormat.getFormatType().equals("METADATA")) {
1957
				// transform
1958
				DBTransform transformer = new DBTransform();
1959
	            String documentContent = IOUtils.toString(object, "UTF-8");
1960
	            String sourceType = objectFormat.getFormatId().getValue();
1961
	            String targetType = "-//W3C//HTML//EN";
1962
	            ByteArrayOutputStream baos = new ByteArrayOutputStream();
1963
	            Writer writer = new OutputStreamWriter(baos , "UTF-8");
1964
	            // TODO: include more params?
1965
	            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
1966
	            String localId = null;
1967
				try {
1968
					localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1969
				} catch (McdbDocNotFoundException e) {
1970
					throw new NotFound("1020", e.getMessage());
1971
				}
1972
	            params.put("qformat", new String[] {format});	            
1973
	            params.put("docid", new String[] {localId});
1974
	            params.put("pid", new String[] {pid.getValue()});
1975
	            transformer.transformXMLDocument(
1976
	                    documentContent , 
1977
	                    sourceType, 
1978
	                    targetType , 
1979
	                    format, 
1980
	                    writer, 
1981
	                    params, 
1982
	                    null //sessionid
1983
	                    );
1984
	            
1985
	            // finally, get the HTML back
1986
	            resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
1987
	            ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
1988
	
1989
			} else {
1990
				// just return the raw bytes
1991
				resultInputStream = object;
1992
			}
1993
		} catch (IOException e) {
1994
			// report as service failure
1995
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1996
			sf.initCause(e);
1997
			throw sf;
1998
		} catch (PropertyNotFoundException e) {
1999
			// report as service failure
2000
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2001
			sf.initCause(e);
2002
			throw sf;
2003
		} catch (SQLException e) {
2004
			// report as service failure
2005
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2006
			sf.initCause(e);
2007
			throw sf;
2008
		} catch (ClassNotFoundException e) {
2009
			// report as service failure
2010
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2011
			sf.initCause(e);
2012
			throw sf;
2013
		}
2014
		
2015
		return resultInputStream;
2016
		
2017
	}	
2018
    
2019
}
(4-4/6)