Project

General

Profile

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

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

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

    
53
import javax.servlet.http.HttpServletRequest;
54

    
55
import org.apache.commons.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
import org.ecoinformatics.datamanager.parser.DataPackage;
122
import org.ecoinformatics.datamanager.parser.Entity;
123
import org.ecoinformatics.datamanager.parser.generic.DataPackageParserInterface;
124
import org.ecoinformatics.datamanager.parser.generic.Eml200DataPackageParser;
125

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

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

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

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

    
196

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

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

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

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

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

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

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

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

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

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

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

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

    
350
            isScienceMetadata = isScienceMetadata(sysmeta);
351

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

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

    
367
                    }
368

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

    
374
                }
375

    
376
            } else {
377

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

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

    
386
            // then update the existing system metadata
387
            updateSystemMetadata(existingSysMeta);
388

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

    
393
            // and insert the new system metadata
394
            insertSystemMetadata(sysmeta);
395

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

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

    
411
        return newPid;
412
    }
413

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

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

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

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

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

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

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

    
505
        // get the referenced object
506
        Identifier pid = sysmeta.getIdentifier();
507

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

    
528

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

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

    
543
        }
544
        
545

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

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

    
581
            }
582

    
583
        } catch (InvalidToken e) {            
584
            String msg = "Could not retrieve object to replicate (InvalidToken): "+ 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 (NotFound e) {
591
            String msg = "Could not retrieve object to replicate (NotFound): "+ 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

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

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

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

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

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

    
675
    }
676

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

    
695
        return super.get(session, pid);
696

    
697
    }
698

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

    
721
        Checksum checksum = null;
722

    
723
        InputStream inputStream = get(session, pid);
724

    
725
        try {
726
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
727

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

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

    
740
        return checksum;
741
    }
742

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

    
763
        return super.getSystemMetadata(session, pid);
764
    }
765

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

    
794
        ObjectList objectList = null;
795

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

    
806
        return objectList;
807
    }
808

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
955
            node.setType(nodeType);
956
            return node;
957

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

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

    
988
        MonitorList monitorList = new MonitorList();
989

    
990
        try {
991

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

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

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

    
1019
        return monitorList;
1020

    
1021
    }
1022

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

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

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

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

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

    
1090
    }
1091

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

    
1099
        logMetacat.info("MNodeService.getReplica() called.");
1100

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

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

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

    
1124
        Subject targetNodeSubject = session.getSubject();
1125

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

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

    
1141
        }
1142

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

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

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

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

    
1170
        return inputStream;
1171
    }
1172

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

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

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

    
1361
	@Override
1362
	public boolean isAuthorized(Identifier pid, Permission permission)
1363
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1364
			NotAuthorized, NotImplemented {
1365

    
1366
		return isAuthorized(null, pid, permission);
1367
	}
1368

    
1369
	@Override
1370
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1371
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1372
			InvalidRequest {
1373

    
1374
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1375
	}
1376

    
1377
	@Override
1378
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1379
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1380
			NotAuthorized, NotImplemented, ServiceFailure {
1381

    
1382
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1383
	}
1384

    
1385
	@Override
1386
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1387
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1388

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

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

    
1396
		return get(null, pid);
1397
	}
1398

    
1399
	@Override
1400
	public Checksum getChecksum(Identifier pid, String algorithm)
1401
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1402
			ServiceFailure, NotFound {
1403

    
1404
		return getChecksum(null, pid, algorithm);
1405
	}
1406

    
1407
	@Override
1408
	public SystemMetadata getSystemMetadata(Identifier pid)
1409
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1410
			NotFound {
1411

    
1412
		return getSystemMetadata(null, pid);
1413
	}
1414

    
1415
	@Override
1416
	public ObjectList listObjects(Date startTime, Date endTime,
1417
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1418
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1419
			NotImplemented, ServiceFailure {
1420

    
1421
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1422
	}
1423

    
1424
	@Override
1425
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1426
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1427

    
1428
		return synchronizationFailed(null, syncFailed);
1429
	}
1430

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

    
1436
		return getReplica(null, pid);
1437
	}
1438

    
1439
	@Override
1440
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1441
			throws NotImplemented, ServiceFailure, NotAuthorized,
1442
			InvalidRequest, InvalidToken, InsufficientResources,
1443
			UnsupportedType {
1444

    
1445
		return replicate(null, sysmeta, sourceNode);
1446
	}
1447

    
1448
	@Override
1449
	public Identifier create(Identifier pid, InputStream object,
1450
			SystemMetadata sysmeta) throws IdentifierNotUnique,
1451
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1452
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1453
			UnsupportedType {
1454

    
1455
		return create(null, pid, object, sysmeta);
1456
	}
1457

    
1458
	@Override
1459
	public Identifier delete(Identifier pid) throws InvalidToken,
1460
			ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1461

    
1462
		return delete(null, pid);
1463
	}
1464

    
1465
	@Override
1466
	public Identifier generateIdentifier(String scheme, String fragment)
1467
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1468
			InvalidRequest {
1469

    
1470
		return generateIdentifier(null, scheme, fragment);
1471
	}
1472

    
1473
	@Override
1474
	public Identifier update(Identifier pid, InputStream object,
1475
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
1476
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1477
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1478
			UnsupportedType, NotFound {
1479

    
1480
		return update(null, pid, object, newPid, sysmeta);
1481
	}
1482

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

    
1531
	@Override
1532
	public QueryEngineList listQueryEngines() throws InvalidToken,
1533
			ServiceFailure, NotAuthorized, NotImplemented {
1534
		QueryEngineList qel = new QueryEngineList();
1535
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1536
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1537
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1538
		for(String name : enables) {
1539
		    qel.addQueryEngine(name);
1540
		}
1541
		return qel;
1542
	}
1543

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

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

    
1629
		// make copy of it
1630
		SystemMetadata sysmeta = new SystemMetadata();
1631
		try {
1632
			BeanUtils.copyProperties(sysmeta, originalSystemMetadata);
1633
		} catch (Exception e) {
1634
			// report as service failure
1635
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1636
			sf.initCause(e);
1637
			throw sf;
1638
		}
1639

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

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

    
1933
				if(fileNames.containsKey(entryPid)){
1934
					//Let's use the file name and extension from the metadata is we have it
1935
					fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + fileNames.get(entryPid).replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
1936
				}
1937
				else{
1938
					//If we couldn't find a given file name, use the system metadata extension
1939
					String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
1940
					fileName += extension;
1941
				}
1942
				
1943
		        //Create a new file for this item and add to the list
1944
				File tempFile = new File(tempDir, fileName);
1945
				tempFiles.add(tempFile);
1946
				
1947
				InputStream entryInputStream = this.get(session, entryPid);			
1948
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
1949
				bag.addFileToPayload(tempFile);
1950
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
1951
			}
1952
			
1953
			//add the the pid to data file map
1954
			File pidMappingFile = new File(tempDir, "pid-mapping.txt");
1955
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
1956
			bag.addFileAsTag(pidMappingFile);
1957
			tempFiles.add(pidMappingFile);
1958
			
1959
			bag = bag.makeComplete();
1960
			
1961
			///Now create the zip file
1962
			//Use the pid as the file name prefix, replacing all non-word characters
1963
			String zipName = pid.getValue().replaceAll("\\W", "_");
1964
			
1965
			File bagFile = new File(tempDir, zipName+".zip");
1966
			
1967
			bag.setFile(bagFile);
1968
			ZipWriter zipWriter = new ZipWriter(bagFactory);
1969
			bag.write(zipWriter, bagFile);
1970
			bagFile = bag.getFile();
1971
			// use custom FIS that will delete the file when closed
1972
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
1973
			// also mark for deletion on shutdown in case the stream is never closed
1974
			bagFile.deleteOnExit();
1975
			tempFiles.add(bagFile);
1976
			
1977
			// clean up other temp files
1978
			for(int i=tempFiles.size()-1; i>=0; i--){
1979
				File tf = new File(tempFiles.get(i).getPath());
1980
				tf.delete();
1981
			}
1982
			
1983
		} catch (IOException e) {
1984
			// report as service failure
1985
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1986
			sf.initCause(e);
1987
			throw sf;
1988
		} catch (OREException e) {
1989
			// report as service failure
1990
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1991
			sf.initCause(e);
1992
			throw sf;
1993
		} catch (URISyntaxException e) {
1994
			// report as service failure
1995
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1996
			sf.initCause(e);
1997
			throw sf;
1998
		} catch (OREParserException e) {
1999
			// report as service failure
2000
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2001
			sf.initCause(e);
2002
			throw sf;
2003
		}
2004
		
2005
		return bagInputStream;
2006

    
2007
	}
2008
	
2009
	/**
2010
	 * Get a rendered view of the object identified by pid.
2011
	 * Uses the registered format given by the format parameter.
2012
	 * Typically, this is structured HTML that can be styled with CSS.
2013
	 * @param session
2014
	 * @param pid
2015
	 * @param format
2016
	 * @return
2017
	 * @throws InvalidToken
2018
	 * @throws ServiceFailure
2019
	 * @throws NotAuthorized
2020
	 * @throws NotFound
2021
	 * @throws NotImplemented
2022
	 */
2023
	public InputStream getView(Session session, Identifier pid, String format) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2024
		InputStream resultInputStream = null;
2025
		
2026
		SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2027
		InputStream object = this.get(session, pid);
2028

    
2029
		try {
2030
			// can only transform metadata, really
2031
			ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2032
			if (objectFormat.getFormatType().equals("METADATA")) {
2033
				// transform
2034
				DBTransform transformer = new DBTransform();
2035
	            String documentContent = IOUtils.toString(object, "UTF-8");
2036
	            String sourceType = objectFormat.getFormatId().getValue();
2037
	            String targetType = "-//W3C//HTML//EN";
2038
	            ByteArrayOutputStream baos = new ByteArrayOutputStream();
2039
	            Writer writer = new OutputStreamWriter(baos , "UTF-8");
2040
	            // TODO: include more params?
2041
	            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2042
	            String localId = null;
2043
				try {
2044
					localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2045
				} catch (McdbDocNotFoundException e) {
2046
					throw new NotFound("1020", e.getMessage());
2047
				}
2048
	            params.put("qformat", new String[] {format});	            
2049
	            params.put("docid", new String[] {localId});
2050
	            params.put("pid", new String[] {pid.getValue()});
2051
	            transformer.transformXMLDocument(
2052
	                    documentContent , 
2053
	                    sourceType, 
2054
	                    targetType , 
2055
	                    format, 
2056
	                    writer, 
2057
	                    params, 
2058
	                    null //sessionid
2059
	                    );
2060
	            
2061
	            // finally, get the HTML back
2062
	            resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2063
	            ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2064
	
2065
			} else {
2066
				// just return the raw bytes
2067
				resultInputStream = object;
2068
			}
2069
		} catch (IOException e) {
2070
			// report as service failure
2071
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2072
			sf.initCause(e);
2073
			throw sf;
2074
		} catch (PropertyNotFoundException e) {
2075
			// report as service failure
2076
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2077
			sf.initCause(e);
2078
			throw sf;
2079
		} catch (SQLException e) {
2080
			// report as service failure
2081
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2082
			sf.initCause(e);
2083
			throw sf;
2084
		} catch (ClassNotFoundException e) {
2085
			// report as service failure
2086
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2087
			sf.initCause(e);
2088
			throw sf;
2089
		}
2090
		
2091
		return resultInputStream;
2092
		
2093
	}	
2094
    
2095
}
(4-4/6)