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

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

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

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

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

    
193

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

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

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

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

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

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

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

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

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

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

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

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

    
347
            isScienceMetadata = isScienceMetadata(sysmeta);
348

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

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

    
364
                    }
365

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

    
371
                }
372

    
373
            } else {
374

    
375
                // update the data object
376
                localId = insertDataObject(object, newPid, session);
377

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

    
383
            // then update the existing system metadata
384
            updateSystemMetadata(existingSysMeta);
385

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

    
390
            // and insert the new system metadata
391
            insertSystemMetadata(sysmeta);
392

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

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

    
408
        return newPid;
409
    }
410

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

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

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

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

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

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

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

    
502
        // get the referenced object
503
        Identifier pid = sysmeta.getIdentifier();
504

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

    
525

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

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

    
540
        }
541
        
542

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

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

    
578
            }
579

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

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

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

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

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

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

    
668
        // finish by setting the replication status
669
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
670
        return result;
671

    
672
    }
673

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

    
692
        return super.get(session, pid);
693

    
694
    }
695

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

    
718
        Checksum checksum = null;
719

    
720
        InputStream inputStream = get(session, pid);
721

    
722
        try {
723
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
724

    
725
        } catch (NoSuchAlgorithmException e) {
726
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
727
                    + e.getMessage());
728
        } catch (IOException 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
        }
732

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

    
737
        return checksum;
738
    }
739

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

    
760
        return super.getSystemMetadata(session, pid);
761
    }
762

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

    
791
        ObjectList objectList = null;
792

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

    
803
        return objectList;
804
    }
805

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

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

    
833
        boolean nodeSynchronize = false;
834
        boolean nodeReplicate = false;
835
        boolean mnCoreServiceAvailable = false;
836
        boolean mnReadServiceAvailable = false;
837
        boolean mnAuthorizationServiceAvailable = false;
838
        boolean mnStorageServiceAvailable = false;
839
        boolean mnReplicationServiceAvailable = false;
840

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

    
853
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
854
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
855
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
856
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
857
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
858

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

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

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

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

    
901
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
902
            Services services = new Services();
903

    
904
            Service sMNCore = new Service();
905
            sMNCore.setName("MNCore");
906
            sMNCore.setVersion(mnCoreServiceVersion);
907
            sMNCore.setAvailable(mnCoreServiceAvailable);
908

    
909
            Service sMNRead = new Service();
910
            sMNRead.setName("MNRead");
911
            sMNRead.setVersion(mnReadServiceVersion);
912
            sMNRead.setAvailable(mnReadServiceAvailable);
913

    
914
            Service sMNAuthorization = new Service();
915
            sMNAuthorization.setName("MNAuthorization");
916
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
917
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
918

    
919
            Service sMNStorage = new Service();
920
            sMNStorage.setName("MNStorage");
921
            sMNStorage.setVersion(mnStorageServiceVersion);
922
            sMNStorage.setAvailable(mnStorageServiceAvailable);
923

    
924
            Service sMNReplication = new Service();
925
            sMNReplication.setName("MNReplication");
926
            sMNReplication.setVersion(mnReplicationServiceVersion);
927
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
928

    
929
            services.addService(sMNRead);
930
            services.addService(sMNCore);
931
            services.addService(sMNAuthorization);
932
            services.addService(sMNStorage);
933
            services.addService(sMNReplication);
934
            node.setServices(services);
935

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

    
952
            node.setType(nodeType);
953
            return node;
954

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

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

    
985
        MonitorList monitorList = new MonitorList();
986

    
987
        try {
988

    
989
            // get log records first
990
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
991

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

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

    
1016
        return monitorList;
1017

    
1018
    }
1019

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

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

    
1054
            }
1055
            
1056
        } else {
1057
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1058

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

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

    
1087
    }
1088

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

    
1096
        logMetacat.info("MNodeService.getReplica() called.");
1097

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

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

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

    
1121
        Subject targetNodeSubject = session.getSubject();
1122

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

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

    
1138
        }
1139

    
1140
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1141
            " for identifier " + pid.getValue());
1142

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

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

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

    
1167
        return inputStream;
1168
    }
1169

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

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

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

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

    
1368
		return isAuthorized(null, pid, permission);
1369
	}
1370

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

    
1376
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1377
	}
1378

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

    
1384
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1385
	}
1386

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

    
1391
		return describe(null, pid);
1392
	}
1393

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

    
1398
		return get(null, pid);
1399
	}
1400

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

    
1406
		return getChecksum(null, pid, algorithm);
1407
	}
1408

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

    
1414
		return getSystemMetadata(null, pid);
1415
	}
1416

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

    
1423
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1424
	}
1425

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

    
1430
		return synchronizationFailed(null, syncFailed);
1431
	}
1432

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

    
1438
		return getReplica(null, pid);
1439
	}
1440

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

    
1447
		return replicate(null, sysmeta, sourceNode);
1448
	}
1449

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

    
1457
		return create(null, pid, object, sysmeta);
1458
	}
1459

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

    
1464
		return delete(null, pid);
1465
	}
1466

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

    
1472
		return generateIdentifier(null, scheme, fragment);
1473
	}
1474

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

    
1482
		return update(null, pid, object, newPid, sysmeta);
1483
	}
1484

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

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

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

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

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

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

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

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

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

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