Project

General

Profile

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

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

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

    
54
import javax.servlet.http.HttpServletRequest;
55

    
56
import org.apache.commons.io.IOUtils;
57
import org.apache.log4j.Logger;
58
import org.dataone.client.v2.CNode;
59
import org.dataone.client.v2.itk.D1Client;
60
import org.dataone.client.v2.MNode;
61
import org.dataone.client.v2.formats.ObjectFormatCache;
62
import org.dataone.client.auth.CertificateManager;
63
import org.dataone.client.v2.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.v2.MNCore;
79
import org.dataone.service.mn.tier1.v2.MNRead;
80
import org.dataone.service.mn.tier2.v2.MNAuthorization;
81
import org.dataone.service.mn.tier3.v2.MNStorage;
82
import org.dataone.service.mn.tier4.v2.MNReplication;
83
import org.dataone.service.mn.v2.MNPackage;
84
import org.dataone.service.mn.v2.MNQuery;
85
import org.dataone.service.mn.v2.MNView;
86
import org.dataone.service.types.v1.AccessRule;
87
import org.dataone.service.types.v1.Checksum;
88
import org.dataone.service.types.v1.DescribeResponse;
89
import org.dataone.service.types.v1.Event;
90
import org.dataone.service.types.v1.Identifier;
91
import org.dataone.service.types.v2.Log;
92
import org.dataone.service.types.v2.LogEntry;
93
import org.dataone.service.types.v2.OptionList;
94
import org.dataone.service.types.v1.MonitorInfo;
95
import org.dataone.service.types.v1.MonitorList;
96
import org.dataone.service.types.v2.Node;
97
import org.dataone.service.types.v2.NodeList;
98
import org.dataone.service.types.v1.NodeReference;
99
import org.dataone.service.types.v1.NodeState;
100
import org.dataone.service.types.v1.NodeType;
101
import org.dataone.service.types.v2.ObjectFormat;
102
import org.dataone.service.types.v1.ObjectFormatIdentifier;
103
import org.dataone.service.types.v1.ObjectList;
104
import org.dataone.service.types.v1.Permission;
105
import org.dataone.service.types.v1.Ping;
106
import org.dataone.service.types.v1.ReplicationStatus;
107
import org.dataone.service.types.v1.Schedule;
108
import org.dataone.service.types.v1.Service;
109
import org.dataone.service.types.v1.Services;
110
import org.dataone.service.types.v1.Session;
111
import org.dataone.service.types.v1.Subject;
112
import org.dataone.service.types.v1.Synchronization;
113
import org.dataone.service.types.v2.SystemMetadata;
114
import org.dataone.service.types.v1.util.AuthUtils;
115
import org.dataone.service.types.v1.util.ChecksumUtil;
116
import org.dataone.service.types.v1_1.QueryEngineDescription;
117
import org.dataone.service.types.v1_1.QueryEngineList;
118
import org.dataone.service.types.v1_1.QueryField;
119
import org.dataone.service.util.Constants;
120
import org.dataone.service.util.TypeMarshaller;
121
import org.dspace.foresite.OREException;
122
import org.dspace.foresite.OREParserException;
123
import org.dspace.foresite.ORESerialiserException;
124
import org.dspace.foresite.ResourceMap;
125
import org.ecoinformatics.datamanager.parser.DataPackage;
126
import org.ecoinformatics.datamanager.parser.Entity;
127
import org.ecoinformatics.datamanager.parser.generic.DataPackageParserInterface;
128
import org.ecoinformatics.datamanager.parser.generic.Eml200DataPackageParser;
129

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

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

    
186
    //private static final String PATHQUERY = "pathquery";
187
	public static final String UUID_SCHEME = "UUID";
188
	public static final String DOI_SCHEME = "DOI";
189
	private static final String UUID_PREFIX = "urn:uuid:";
190

    
191
	/* the logger instance */
192
    private Logger logMetacat = null;
193
    
194
    /* A reference to a remote Memeber Node */
195
    private MNode mn;
196
    
197
    /* A reference to a Coordinating Node */
198
    private CNode cn;
199

    
200

    
201
    /**
202
     * Singleton accessor to get an instance of MNodeService.
203
     * 
204
     * @return instance - the instance of MNodeService
205
     */
206
    public static MNodeService getInstance(HttpServletRequest request) {
207
        return new MNodeService(request);
208
    }
209

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

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

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

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

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

    
298
        // verify the pid is valid format
299
        if (!isValidIdentifier(pid)) {
300
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
301
        }
302

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

    
330
        // does the subject have WRITE ( == update) priveleges on the pid?
331
        allowed = isAuthorized(session, pid, Permission.WRITE);
332

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

    
343
            // get the existing system metadata for the object
344
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
345

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

    
354
            isScienceMetadata = isScienceMetadata(sysmeta);
355

    
356
            // do we have XML metadata or a data object?
357
            if (isScienceMetadata) {
358

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

    
371
                    }
372

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

    
378
                }
379

    
380
            } else {
381

    
382
                // update the data object
383
                localId = insertDataObject(object, newPid, session);
384

    
385
            }
386
            
387
            // add the newPid to the obsoletedBy list for the existing sysmeta
388
            existingSysMeta.setObsoletedBy(newPid);
389

    
390
            // then update the existing system metadata
391
            updateSystemMetadata(existingSysMeta);
392

    
393
            // prep the new system metadata, add pid to the affected lists
394
            sysmeta.setObsoletes(pid);
395
            //sysmeta.addDerivedFrom(pid);
396

    
397
            // and insert the new system metadata
398
            insertSystemMetadata(sysmeta);
399

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

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

    
415
        return newPid;
416
    }
417

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

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

    
432
        // set the dates
433
        Date now = Calendar.getInstance().getTime();
434
        sysmeta.setDateSysMetadataModified(now);
435
        sysmeta.setDateUploaded(now);
436
        
437
        // set the serial version
438
        sysmeta.setSerialVersion(BigInteger.ZERO);
439

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

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

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

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

    
509
        // get the referenced object
510
        Identifier pid = sysmeta.getIdentifier();
511

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

    
532

    
533
        // get the local node id
534
        try {
535
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
536
            nodeId = new NodeReference();
537
            nodeId.setValue(nodeIdStr);
538

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

    
547
        }
548
        
549

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

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

    
585
            }
586

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

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

    
627
        // verify checksum on the object, if supported
628
        if (object.markSupported()) {
629
            Checksum givenChecksum = sysmeta.getChecksum();
630
            Checksum computedChecksum = null;
631
            try {
632
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
633
                object.reset();
634

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

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

    
675
        // finish by setting the replication status
676
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
677
        return result;
678

    
679
    }
680

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

    
699
        return super.get(session, pid);
700

    
701
    }
702

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

    
725
        Checksum checksum = null;
726

    
727
        InputStream inputStream = get(session, pid);
728

    
729
        try {
730
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
731

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

    
740
        if (checksum == null) {
741
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
742
        }
743

    
744
        return checksum;
745
    }
746

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

    
767
        return super.getSystemMetadata(session, pid);
768
    }
769

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

    
798
        ObjectList objectList = null;
799

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

    
810
        return objectList;
811
    }
812

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

    
827
        String nodeName = null;
828
        String nodeId = null;
829
        String subject = null;
830
        String contactSubject = null;
831
        String nodeDesc = null;
832
        String nodeTypeString = null;
833
        NodeType nodeType = null;
834
        String mnCoreServiceVersion = null;
835
        String mnReadServiceVersion = null;
836
        String mnAuthorizationServiceVersion = null;
837
        String mnStorageServiceVersion = null;
838
        String mnReplicationServiceVersion = null;
839

    
840
        boolean nodeSynchronize = false;
841
        boolean nodeReplicate = false;
842
        boolean mnCoreServiceAvailable = false;
843
        boolean mnReadServiceAvailable = false;
844
        boolean mnAuthorizationServiceAvailable = false;
845
        boolean mnStorageServiceAvailable = false;
846
        boolean mnReplicationServiceAvailable = false;
847

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

    
860
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
861
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
862
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
863
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
864
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
865

    
866
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
867
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
868
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
869
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
870
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
871

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

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

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

    
908
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
909
            Services services = new Services();
910

    
911
            Service sMNCore = new Service();
912
            sMNCore.setName("MNCore");
913
            sMNCore.setVersion(mnCoreServiceVersion);
914
            sMNCore.setAvailable(mnCoreServiceAvailable);
915

    
916
            Service sMNRead = new Service();
917
            sMNRead.setName("MNRead");
918
            sMNRead.setVersion(mnReadServiceVersion);
919
            sMNRead.setAvailable(mnReadServiceAvailable);
920

    
921
            Service sMNAuthorization = new Service();
922
            sMNAuthorization.setName("MNAuthorization");
923
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
924
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
925

    
926
            Service sMNStorage = new Service();
927
            sMNStorage.setName("MNStorage");
928
            sMNStorage.setVersion(mnStorageServiceVersion);
929
            sMNStorage.setAvailable(mnStorageServiceAvailable);
930

    
931
            Service sMNReplication = new Service();
932
            sMNReplication.setName("MNReplication");
933
            sMNReplication.setVersion(mnReplicationServiceVersion);
934
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
935

    
936
            services.addService(sMNRead);
937
            services.addService(sMNCore);
938
            services.addService(sMNAuthorization);
939
            services.addService(sMNStorage);
940
            services.addService(sMNReplication);
941
            node.setServices(services);
942

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

    
959
            node.setType(nodeType);
960
            return node;
961

    
962
        } catch (PropertyNotFoundException pnfe) {
963
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
964
            logMetacat.error(msg);
965
            throw new ServiceFailure("2162", msg);
966
        }
967
    }
968

    
969
    
970

    
971
    /**
972
     * A callback method used by a CN to indicate to a MN that it cannot 
973
     * complete synchronization of the science metadata identified by pid.  Log
974
     * the event in the metacat event log.
975
     * 
976
     * @param session
977
     * @param syncFailed
978
     * 
979
     * @throws ServiceFailure
980
     * @throws NotAuthorized
981
     * @throws NotImplemented
982
     */
983
    @Override
984
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
985
        throws NotImplemented, ServiceFailure, NotAuthorized {
986

    
987
        String localId;
988
        Identifier pid;
989
        if ( syncFailed.getPid() != null ) {
990
            pid = new Identifier();
991
            pid.setValue(syncFailed.getPid());
992
            boolean allowed;
993
            
994
            //are we allowed? only CNs
995
            try {
996
                allowed = isAdminAuthorized(session);
997
                if ( !allowed ){
998
                    throw new NotAuthorized("2162", 
999
                            "Not allowed to call synchronizationFailed() on this node.");
1000
                }
1001
            } catch (InvalidToken e) {
1002
                throw new NotAuthorized("2162", 
1003
                        "Not allowed to call synchronizationFailed() on this node.");
1004

    
1005
            }
1006
            
1007
        } else {
1008
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1009

    
1010
        }
1011
        
1012
        try {
1013
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1014
        } catch (McdbDocNotFoundException e) {
1015
            throw new ServiceFailure("2161", "The identifier specified by " + 
1016
                    syncFailed.getPid() + " was not found on this node.");
1017

    
1018
        }
1019
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1020
        // method is changed to include the URL as a parameter
1021
        logMetacat.debug("Synchronization for the object identified by " + 
1022
                pid.getValue() + " failed from " + syncFailed.getNodeId() + 
1023
                " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
1024
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1025
        String principal = Constants.SUBJECT_PUBLIC;
1026
        if (session != null && session.getSubject() != null) {
1027
          principal = session.getSubject().getValue();
1028
        }
1029
        try {
1030
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1031
        } catch (Exception e) {
1032
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1033
        }
1034
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
1035
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1036
        return true;
1037

    
1038
    }
1039

    
1040
    /**
1041
     * Essentially a get() but with different logging behavior
1042
     */
1043
    @Override
1044
    public InputStream getReplica(Session session, Identifier pid) 
1045
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1046

    
1047
        logMetacat.info("MNodeService.getReplica() called.");
1048

    
1049
        // cannot be called by public
1050
        if (session == null) {
1051
        	throw new InvalidToken("2183", "No session was provided.");
1052
        }
1053
        
1054
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1055
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1056
             "\tIdentifier           = " + pid.getValue());
1057

    
1058
        InputStream inputStream = null; // bytes to be returned
1059
        handler = new MetacatHandler(new Timer());
1060
        boolean allowed = false;
1061
        String localId; // the metacat docid for the pid
1062

    
1063
        // get the local docid from Metacat
1064
        try {
1065
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1066
        } catch (McdbDocNotFoundException e) {
1067
            throw new ServiceFailure("2181", "The object specified by " + 
1068
                    pid.getValue() + " does not exist at this node.");
1069
            
1070
        }
1071

    
1072
        Subject targetNodeSubject = session.getSubject();
1073

    
1074
        // check for authorization to replicate, null session to act as this source MN
1075
        try {
1076
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1077
        } catch (InvalidToken e1) {
1078
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1079
                + e1.getMessage());
1080
            
1081
        } catch (NotFound e1) {
1082
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1083
                    + e1.getMessage());
1084

    
1085
        } catch (InvalidRequest e1) {
1086
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1087
                    + e1.getMessage());
1088

    
1089
        }
1090

    
1091
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1092
            " for identifier " + pid.getValue());
1093

    
1094
        // if the person is authorized, perform the read
1095
        if (allowed) {
1096
            try {
1097
                inputStream = MetacatHandler.read(localId);
1098
            } catch (Exception e) {
1099
                throw new ServiceFailure("1020", "The object specified by " + 
1100
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1101
            }
1102
        }
1103

    
1104
        // if we fail to set the input stream
1105
        if (inputStream == null) {
1106
            throw new ServiceFailure("2181", "The object specified by " + 
1107
                pid.getValue() + "does not exist at this node.");
1108
        }
1109

    
1110
        // log the replica event
1111
        String principal = null;
1112
        if (session.getSubject() != null) {
1113
            principal = session.getSubject().getValue();
1114
        }
1115
        EventLog.getInstance().log(request.getRemoteAddr(), 
1116
            request.getHeader("User-Agent"), principal, localId, "replicate");
1117

    
1118
        return inputStream;
1119
    }
1120

    
1121
    /**
1122
     * A method to notify the Member Node that the authoritative copy of 
1123
     * system metadata on the Coordinating Nodes has changed.
1124
     * 
1125
     * @param session   Session information that contains the identity of the 
1126
     *                  calling user as retrieved from the X.509 certificate 
1127
     *                  which must be traceable to the CILogon service.
1128
     * @param serialVersion   The serialVersion of the system metadata
1129
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1130
     * @throws NotImplemented
1131
     * @throws ServiceFailure
1132
     * @throws NotAuthorized
1133
     * @throws InvalidRequest
1134
     * @throws InvalidToken
1135
     */
1136
    public boolean systemMetadataChanged(Session session, Identifier pid,
1137
        long serialVersion, Date dateSysMetaLastModified) 
1138
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1139
        InvalidToken {
1140
        
1141
        // cannot be called by public
1142
        if (session == null) {
1143
        	throw new InvalidToken("2183", "No session was provided.");
1144
        }
1145

    
1146
        SystemMetadata currentLocalSysMeta = null;
1147
        SystemMetadata newSysMeta = null;
1148
        CNode cn = D1Client.getCN();
1149
        NodeList nodeList = null;
1150
        Subject callingSubject = null;
1151
        boolean allowed = false;
1152
        
1153
        // are we allowed to call this?
1154
        callingSubject = session.getSubject();
1155
        nodeList = cn.listNodes();
1156
        
1157
        for(Node node : nodeList.getNodeList()) {
1158
            // must be a CN
1159
            if ( node.getType().equals(NodeType.CN)) {
1160
               List<Subject> subjectList = node.getSubjectList();
1161
               // the calling subject must be in the subject list
1162
               if ( subjectList.contains(callingSubject)) {
1163
                   allowed = true;
1164
                   
1165
               }
1166
               
1167
            }
1168
        }
1169
        
1170
        if (!allowed ) {
1171
            String msg = "The subject identified by " + callingSubject.getValue() +
1172
              " is not authorized to call this service.";
1173
            throw new NotAuthorized("1331", msg);
1174
            
1175
        }
1176
        
1177
        // compare what we have locally to what is sent in the change notification
1178
        try {
1179
            currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1180
             
1181
        } catch (RuntimeException e) {
1182
            String msg = "SystemMetadata for pid " + pid.getValue() +
1183
              " couldn't be updated because it couldn't be found locally: " +
1184
              e.getMessage();
1185
            logMetacat.error(msg);
1186
            ServiceFailure sf = new ServiceFailure("1333", msg);
1187
            sf.initCause(e);
1188
            throw sf; 
1189
        }
1190
        
1191
        if (currentLocalSysMeta.getSerialVersion().longValue() < serialVersion ) {
1192
            try {
1193
                newSysMeta = cn.getSystemMetadata(null, pid);
1194
            } catch (NotFound e) {
1195
                // huh? you just said you had it
1196
            	String msg = "On updating the local copy of system metadata " + 
1197
                "for pid " + pid.getValue() +", the CN reports it is not found." +
1198
                " The error message was: " + e.getMessage();
1199
                logMetacat.error(msg);
1200
                ServiceFailure sf = new ServiceFailure("1333", msg);
1201
                sf.initCause(e);
1202
                throw sf;
1203
            }
1204
            
1205
            // update the local copy of system metadata for the pid
1206
            try {
1207
                HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1208
                logMetacat.info("Updated local copy of system metadata for pid " +
1209
                    pid.getValue() + " after change notification from the CN.");
1210
                
1211
                // TODO: consider inspecting the change for archive
1212
                // see: https://projects.ecoinformatics.org/ecoinfo/issues/6417
1213
//                if (newSysMeta.getArchived() != null && newSysMeta.getArchived().booleanValue()) {
1214
//                	try {
1215
//						this.archive(session, newSysMeta.getIdentifier());
1216
//					} catch (NotFound e) {
1217
//						// do we care? nothing to do about it now
1218
//						logMetacat.error(e.getMessage(), e);
1219
//					}
1220
//                }
1221
                
1222
            } catch (RuntimeException e) {
1223
                String msg = "SystemMetadata for pid " + pid.getValue() +
1224
                  " couldn't be updated: " +
1225
                  e.getMessage();
1226
                logMetacat.error(msg);
1227
                ServiceFailure sf = new ServiceFailure("1333", msg);
1228
                sf.initCause(e);
1229
                throw sf;
1230
            }
1231
            
1232
            // submit for indexing
1233
            try {
1234
				MetacatSolrIndex.getInstance().submit(newSysMeta.getIdentifier(), newSysMeta, null, true);
1235
			} catch (Exception e) {
1236
                logMetacat.error("Could not submit changed systemMetadata for indexing, pid: " + newSysMeta.getIdentifier().getValue(), e);
1237
			}
1238
        }
1239
        
1240
        return true;
1241
        
1242
    }
1243
    
1244
    /*
1245
     * Set the replication status for the object on the Coordinating Node
1246
     * 
1247
     * @param session - the session for the this target node
1248
     * @param pid - the identifier of the object being updated
1249
     * @param nodeId - the identifier of this target node
1250
     * @param status - the replication status to set
1251
     * @param failure - the exception to include, if any
1252
     */
1253
    private void setReplicationStatus(Session session, Identifier pid, 
1254
        NodeReference nodeId, ReplicationStatus status, BaseException failure) 
1255
        throws ServiceFailure, NotImplemented, NotAuthorized, 
1256
        InvalidRequest {
1257
        
1258
        // call the CN as the MN to set the replication status
1259
        try {
1260
            this.cn = D1Client.getCN();
1261
            this.cn.setReplicationStatus(session, pid, nodeId,
1262
                    status, failure);
1263
            
1264
        } catch (InvalidToken e) {
1265
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (InvalidToken): " + e.getMessage();
1266
            logMetacat.error(msg);
1267
        	throw new ServiceFailure("2151",
1268
                    msg);
1269
            
1270
        } catch (NotFound e) {
1271
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (NotFound): " + e.getMessage();
1272
            logMetacat.error(msg);
1273
        	throw new ServiceFailure("2151",
1274
                    msg);
1275
            
1276
        }
1277
    }
1278
    
1279
    private SystemMetadata makePublicIfNot(SystemMetadata sysmeta, Identifier pid) throws ServiceFailure, InvalidToken, NotFound, NotImplemented, InvalidRequest {
1280
    	// check if it is publicly readable
1281
		boolean isPublic = false;
1282
		Subject publicSubject = new Subject();
1283
		publicSubject.setValue(Constants.SUBJECT_PUBLIC);
1284
		Session publicSession = new Session();
1285
		publicSession.setSubject(publicSubject);
1286
		AccessRule publicRule = new AccessRule();
1287
		publicRule.addPermission(Permission.READ);
1288
		publicRule.addSubject(publicSubject);
1289
		
1290
		// see if we need to add the rule
1291
		try {
1292
			isPublic = this.isAuthorized(publicSession, pid, Permission.READ);
1293
		} catch (NotAuthorized na) {
1294
			// well, certainly not authorized for public read!
1295
		}
1296
		if (!isPublic) {
1297
			sysmeta.getAccessPolicy().addAllow(publicRule);
1298
		}
1299
		
1300
		return sysmeta;
1301
    }
1302

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

    
1349
	
1350

    
1351
	@Override
1352
	public QueryEngineDescription getQueryEngineDescription(Session session, String engine)
1353
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1354
			NotFound {
1355
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1356
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1357
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1358
            }
1359
	        QueryEngineDescription qed = new QueryEngineDescription();
1360
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1361
	        qed.setQueryEngineVersion("1.0");
1362
	        qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1363
	        Vector<String> pathsForIndexing = null;
1364
	        try {
1365
	            pathsForIndexing = SystemUtil.getPathsForIndexing();
1366
	        } catch (MetacatUtilException e) {
1367
	            logMetacat.warn("Could not get index paths", e);
1368
	        }
1369
	        for (String fieldName: pathsForIndexing) {
1370
	            QueryField field = new QueryField();
1371
	            field.addDescription("Indexed field for path '" + fieldName + "'");
1372
	            field.setName(fieldName);
1373
	            field.setReturnable(true);
1374
	            field.setSearchable(true);
1375
	            field.setSortable(false);
1376
	            // TODO: determine type and multivaluedness
1377
	            field.setType(String.class.getName());
1378
	            //field.setMultivalued(true);
1379
	            qed.addQueryField(field);
1380
	        }
1381
	        return qed;
1382
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1383
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1384
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1385
            }
1386
	        try {
1387
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1388
	            return qed;
1389
	        } catch (Exception e) {
1390
	            e.printStackTrace();
1391
	            throw new ServiceFailure("Solr server error", e.getMessage());
1392
	        }
1393
	    } else {
1394
	        throw new NotFound("404", "The Metacat member node can't find the query engine - "+engine);
1395
	    }
1396
		
1397
	}
1398

    
1399
	@Override
1400
	public QueryEngineList listQueryEngines(Session session) throws InvalidToken,
1401
			ServiceFailure, NotAuthorized, NotImplemented {
1402
		QueryEngineList qel = new QueryEngineList();
1403
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1404
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1405
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1406
		for(String name : enables) {
1407
		    qel.addQueryEngine(name);
1408
		}
1409
		return qel;
1410
	}
1411

    
1412
	@Override
1413
	public InputStream query(Session session, String engine, String query) throws InvalidToken,
1414
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1415
			NotFound {
1416
	    String user = Constants.SUBJECT_PUBLIC;
1417
        String[] groups= null;
1418
        Set<Subject> subjects = null;
1419
        if (session != null) {
1420
            user = session.getSubject().getValue();
1421
            subjects = AuthUtils.authorizedClientSubjects(session);
1422
            if (subjects != null) {
1423
                List<String> groupList = new ArrayList<String>();
1424
                for (Subject subject: subjects) {
1425
                    groupList.add(subject.getValue());
1426
                }
1427
                groups = groupList.toArray(new String[0]);
1428
            }
1429
        } else {
1430
            //add the public user subject to the set 
1431
            Subject subject = new Subject();
1432
            subject.setValue(Constants.SUBJECT_PUBLIC);
1433
            subjects = new HashSet<Subject>();
1434
            subjects.add(subject);
1435
        }
1436
        //System.out.println("====== user is "+user);
1437
        //System.out.println("====== groups are "+groups);
1438
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1439
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1440
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1441
            }
1442
			try {
1443
				DBQuery queryobj = new DBQuery();
1444
				
1445
				String results = queryobj.performPathquery(query, user, groups);
1446
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1447
				ctbais.setContentType("text/xml");
1448
				return ctbais;
1449

    
1450
			} catch (Exception e) {
1451
				throw new ServiceFailure("Pathquery error", e.getMessage());
1452
			}
1453
			
1454
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1455
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1456
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1457
		    }
1458
		    logMetacat.info("The query is ==================================== \n"+query);
1459
		    try {
1460
		        
1461
                return MetacatSolrIndex.getInstance().query(query, subjects);
1462
            } catch (Exception e) {
1463
                // TODO Auto-generated catch block
1464
                throw new ServiceFailure("Solr server error", e.getMessage());
1465
            } 
1466
		}
1467
		return null;
1468
	}
1469
	
1470
	/**
1471
	 * Given an existing Science Metadata PID, this method mints a DOI
1472
	 * and updates the original object "publishing" the update with the DOI.
1473
	 * This includes updating the ORE map that describes the Science Metadata+data.
1474
	 * TODO: ensure all referenced objects allow public read
1475
	 * 
1476
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1477
	 * 
1478
	 * @param originalIdentifier
1479
	 * @param request
1480
	 * @throws InvalidRequest 
1481
	 * @throws NotImplemented 
1482
	 * @throws NotAuthorized 
1483
	 * @throws ServiceFailure 
1484
	 * @throws InvalidToken 
1485
	 * @throws NotFound
1486
	 * @throws InvalidSystemMetadata 
1487
	 * @throws InsufficientResources 
1488
	 * @throws UnsupportedType 
1489
	 * @throws IdentifierNotUnique 
1490
	 */
1491
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata {
1492
		
1493
		
1494
		// get the original SM
1495
		SystemMetadata originalSystemMetadata = this.getSystemMetadata(session, originalIdentifier);
1496

    
1497
		// make copy of it using the marshaller to ensure DEEP copy
1498
		SystemMetadata sysmeta = null;
1499
		try {
1500
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
1501
			TypeMarshaller.marshalTypeToOutputStream(originalSystemMetadata, baos);
1502
			sysmeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1503
		} catch (Exception e) {
1504
			// report as service failure
1505
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1506
			sf.initCause(e);
1507
			throw sf;
1508
		}
1509

    
1510
		// mint a DOI for the new revision
1511
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1512
				
1513
		// set new metadata values
1514
		sysmeta.setIdentifier(newIdentifier);
1515
		sysmeta.setObsoletes(originalIdentifier);
1516
		sysmeta.setObsoletedBy(null);
1517
		
1518
		// ensure it is publicly readable
1519
		sysmeta = makePublicIfNot(sysmeta, originalIdentifier);
1520
		
1521
		// get the bytes
1522
		InputStream inputStream = this.get(session, originalIdentifier);
1523
		
1524
		// update the object
1525
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
1526
		
1527
		// update ORE that references the scimeta
1528
		// first try the naive method, then check the SOLR index
1529
		try {
1530
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1531
			
1532
			Identifier potentialOreIdentifier = new Identifier();
1533
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1534
			
1535
			InputStream oreInputStream = null;
1536
			try {
1537
				oreInputStream = this.get(session, potentialOreIdentifier);
1538
			} catch (NotFound nf) {
1539
				// this is probably okay for many sci meta data docs
1540
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1541
				// try the SOLR index
1542
				List<Identifier> potentialOreIdentifiers = this.lookupOreFor(originalIdentifier, false);
1543
				if (potentialOreIdentifiers != null) {
1544
					potentialOreIdentifier = potentialOreIdentifiers.get(0);
1545
					try {
1546
						oreInputStream = this.get(session, potentialOreIdentifier);
1547
					} catch (NotFound nf2) {
1548
						// this is probably okay for many sci meta data docs
1549
						logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1550
					}
1551
				}
1552
			}
1553
			if (oreInputStream != null) {
1554
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1555
	
1556
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1557
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1558
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1559
					
1560
				// reconstruct the ORE with the new identifiers
1561
				sciMetaMap.remove(originalIdentifier);
1562
				sciMetaMap.put(newIdentifier, dataIdentifiers);
1563
				
1564
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1565
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1566
				
1567
				// get the original ORE SM and update the values
1568
				SystemMetadata originalOreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1569
				SystemMetadata oreSysMeta = new SystemMetadata();
1570
				try {
1571
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
1572
					TypeMarshaller.marshalTypeToOutputStream(originalOreSysMeta, baos);
1573
					oreSysMeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1574
				} catch (Exception e) {
1575
					// report as service failure
1576
					ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1577
					sf.initCause(e);
1578
					throw sf;
1579
				}
1580

    
1581
				oreSysMeta.setIdentifier(newOreIdentifier);
1582
				oreSysMeta.setObsoletes(potentialOreIdentifier);
1583
				oreSysMeta.setObsoletedBy(null);
1584
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
1585
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
1586
				
1587
				// ensure ORE is publicly readable
1588
				oreSysMeta = makePublicIfNot(oreSysMeta, potentialOreIdentifier);
1589
				
1590
				// ensure all data objects allow public read
1591
				List<String> pidsToSync = new ArrayList<String>();
1592
				for (Identifier dataId: dataIdentifiers) {
1593
					SystemMetadata dataSysMeta = this.getSystemMetadata(session, dataId);
1594
					dataSysMeta = makePublicIfNot(dataSysMeta, dataId);
1595
					this.updateSystemMetadata(dataSysMeta);
1596
					pidsToSync.add(dataId.getValue());
1597
				}
1598
				SyncAccessPolicy sap = new SyncAccessPolicy();
1599
				try {
1600
					sap.sync(pidsToSync);
1601
				} catch (Exception e) {
1602
					// ignore
1603
					logMetacat.warn("Error attempting to sync access for data objects when publishing package");
1604
				}
1605
				
1606
				// save the updated ORE
1607
				this.update(
1608
						session, 
1609
						potentialOreIdentifier, 
1610
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")), 
1611
						newOreIdentifier, 
1612
						oreSysMeta);
1613
				
1614
			} else {
1615
				// create a new ORE for them
1616
				// https://projects.ecoinformatics.org/ecoinfo/issues/6194
1617
				try {
1618
					// find the local id for the NEW package.
1619
					String newLocalId = IdentifierManager.getInstance().getLocalId(newIdentifier.getValue());
1620
	
1621
					@SuppressWarnings("unused")
1622
					SystemMetadata extraSysMeta = SystemMetadataFactory.createSystemMetadata(newLocalId, true, false);
1623
					// should be done generating the ORE here, and the same permissions were used from the metadata object
1624
					
1625
				} catch (Exception e) {
1626
					// oops, guess there was a problem - no package for you
1627
					logMetacat.error("Could not generate new ORE for published object: " + newIdentifier.getValue(), e);
1628
				}
1629
			}
1630
		} catch (McdbDocNotFoundException e) {
1631
			// report as service failure
1632
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1633
			sf.initCause(e);
1634
			throw sf;
1635
		} catch (UnsupportedEncodingException e) {
1636
			// report as service failure
1637
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1638
			sf.initCause(e);
1639
			throw sf;
1640
		} catch (OREException e) {
1641
			// report as service failure
1642
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1643
			sf.initCause(e);
1644
			throw sf;
1645
		} catch (URISyntaxException e) {
1646
			// report as service failure
1647
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1648
			sf.initCause(e);
1649
			throw sf;
1650
		} catch (OREParserException e) {
1651
			// report as service failure
1652
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1653
			sf.initCause(e);
1654
			throw sf;
1655
		} catch (ORESerialiserException e) {
1656
			// report as service failure
1657
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1658
			sf.initCause(e);
1659
			throw sf;
1660
		} catch (NoSuchAlgorithmException e) {
1661
			// report as service failure
1662
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1663
			sf.initCause(e);
1664
			throw sf;
1665
		}
1666
		
1667
		return newIdentifier;
1668
	}
1669
	
1670
	/**
1671
	 * Determines if we already have registered an ORE map for this package
1672
	 * NOTE: uses a solr query to locate OREs for the object
1673
	 * @param guid of the EML/packaging object
1674
	 * @return list of resource map identifiers for the given pid
1675
	 */
1676
	public List<Identifier> lookupOreFor(Identifier guid, boolean includeObsolete) {
1677
		// Search for the ORE if we can find it
1678
		String pid = guid.getValue();
1679
		List<Identifier> retList = null;
1680
		try {
1681
			String query = "fl=id,resourceMap&wt=xml&q=-obsoletedBy:[* TO *]+resourceMap:[* TO *]+id:\"" + pid + "\"";
1682
			if (includeObsolete) {
1683
				query = "fl=id,resourceMap&wt=xml&q=resourceMap:[* TO *]+id:\"" + pid + "\"";
1684
			}
1685
			
1686
			InputStream results = this.query(null, "solr", query);
1687
			org.w3c.dom.Node rootNode = XMLUtilities.getXMLReaderAsDOMTreeRootNode(new InputStreamReader(results, "UTF-8"));
1688
			//String resultString = XMLUtilities.getDOMTreeAsString(rootNode);
1689
			org.w3c.dom.NodeList nodeList = XMLUtilities.getNodeListWithXPath(rootNode, "//arr[@name=\"resourceMap\"]/str");
1690
			if (nodeList != null && nodeList.getLength() > 0) {
1691
				retList = new ArrayList<Identifier>();
1692
				for (int i = 0; i < nodeList.getLength(); i++) {
1693
					String found = nodeList.item(i).getFirstChild().getNodeValue();
1694
					Identifier oreId = new Identifier();
1695
					oreId.setValue(found);
1696
					retList.add(oreId);
1697
				}
1698
			}
1699
		} catch (Exception e) {
1700
			logMetacat.error("Error checking for resourceMap[s] on pid " + pid + ". " + e.getMessage(), e);
1701
		}
1702
		
1703
		return retList;
1704
	}
1705
	
1706

    
1707
	@Override
1708
	public InputStream getPackage(Session session, ObjectFormatIdentifier formatId,
1709
			Identifier pid) throws InvalidToken, ServiceFailure,
1710
			NotAuthorized, InvalidRequest, NotImplemented, NotFound {
1711
		InputStream bagInputStream = null;
1712
		BagFactory bagFactory = new BagFactory();
1713
		Bag bag = bagFactory.createBag();
1714
		
1715
		// track the temp files we use so we can delete them when finished
1716
		List<File> tempFiles = new ArrayList<File>();
1717
		
1718
		// the pids to include in the package
1719
		List<Identifier> packagePids = new ArrayList<Identifier>();
1720
		
1721
		// catch non-D1 service errors and throw as ServiceFailures
1722
		try {
1723
			//Create a map of dataone ids and file names
1724
			Map<Identifier, String> fileNames = new HashMap<Identifier, String>();
1725
			
1726
			// track the pid-to-file mapping
1727
			StringBuffer pidMapping = new StringBuffer();
1728
			
1729
			// find the package contents
1730
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1731
			if (ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
1732
				//Get the resource map as a map of Identifiers
1733
				InputStream oreInputStream = this.get(session, pid);
1734
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1735
				packagePids.addAll(resourceMapStructure.keySet());
1736
				//Loop through each object in this resource map
1737
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
1738
					//Loop through each metadata object in this entry
1739
					Set<Identifier> metadataIdentifiers = entries.keySet();
1740
					for(Identifier metadataID: metadataIdentifiers){
1741
						try{
1742
							//Get the system metadata for this metadata object
1743
							SystemMetadata metadataSysMeta = this.getSystemMetadata(session, metadataID);
1744
							
1745
							// include user-friendly metadata
1746
							if (ObjectFormatCache.getInstance().getFormat(metadataSysMeta.getFormatId()).getFormatType().equals("METADATA")) {
1747
								InputStream metadataStream = this.get(session, metadataID);
1748
							
1749
								try {
1750
									// transform
1751
						            String format = "default";
1752

    
1753
									DBTransform transformer = new DBTransform();
1754
						            String documentContent = IOUtils.toString(metadataStream, "UTF-8");
1755
						            String sourceType = metadataSysMeta.getFormatId().getValue();
1756
						            String targetType = "-//W3C//HTML//EN";
1757
						            ByteArrayOutputStream baos = new ByteArrayOutputStream();
1758
						            Writer writer = new OutputStreamWriter(baos , "UTF-8");
1759
						            // TODO: include more params?
1760
						            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
1761
						            String localId = null;
1762
									try {
1763
										localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1764
									} catch (McdbDocNotFoundException e) {
1765
										throw new NotFound("1020", e.getMessage());
1766
									}
1767
									params.put("qformat", new String[] {format});	            
1768
						            params.put("docid", new String[] {localId});
1769
						            params.put("pid", new String[] {pid.getValue()});
1770
						            params.put("displaymodule", new String[] {"printall"});
1771
						            
1772
						            transformer.transformXMLDocument(
1773
						                    documentContent , 
1774
						                    sourceType, 
1775
						                    targetType , 
1776
						                    format, 
1777
						                    writer, 
1778
						                    params, 
1779
						                    null //sessionid
1780
						                    );
1781
						            
1782
						            // finally, get the HTML back
1783
						            ContentTypeByteArrayInputStream resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
1784
						            
1785
						            // write to temp file with correct css path
1786
						            File tmpDir = File.createTempFile("package_", "_dir");
1787
						            tmpDir.delete();
1788
						            tmpDir.mkdir();
1789
						            File htmlFile = File.createTempFile("metadata", ".html", tmpDir);
1790
						            File cssDir = new File(tmpDir, format);
1791
						            cssDir.mkdir();
1792
						            File cssFile = new File(tmpDir, format + "/" + format + ".css");
1793
						            String pdfFileName = metadataID.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-METADATA.pdf";
1794
						            File pdfFile = new File(tmpDir, pdfFileName);
1795
						            //File pdfFile = File.createTempFile("metadata", ".pdf", tmpDir);
1796
						            
1797
						            // put the CSS file in place for the html to find it
1798
						            String originalCssPath = SystemUtil.getContextDir() + "/style/skins/" + format + "/" + format + ".css";
1799
						            IOUtils.copy(new FileInputStream(originalCssPath), new FileOutputStream(cssFile));
1800
						            
1801
						            // write the HTML file
1802
						            IOUtils.copy(resultInputStream, new FileOutputStream(htmlFile));
1803
						            
1804
						            // convert to PDF
1805
						            HtmlToPdf.export(htmlFile.getAbsolutePath(), pdfFile.getAbsolutePath());
1806
						            
1807
						            //add to the package
1808
						            bag.addFileToPayload(pdfFile);
1809
									pidMapping.append(metadataID.getValue() + " (pdf)" +  "\t" + "data/" + pdfFile.getName() + "\n");
1810
						            
1811
						            // mark for clean up after we are done
1812
									htmlFile.delete();
1813
									cssFile.delete();
1814
									cssDir.delete();
1815
						            tempFiles.add(tmpDir);
1816
									tempFiles.add(pdfFile); // delete this first later on
1817
						            
1818
								} catch (Exception e) {
1819
									logMetacat.warn("Could not transform metadata", e);
1820
								}
1821
							}
1822

    
1823
							
1824
							//If this is in eml format, extract the filename and GUID from each entity in its package
1825
							if (metadataSysMeta.getFormatId().getValue().startsWith("eml://")) {
1826
								//Get the package
1827
								DataPackageParserInterface parser = new Eml200DataPackageParser();
1828
								InputStream emlStream = this.get(session, metadataID);
1829
								parser.parse(emlStream);
1830
								DataPackage dataPackage = parser.getDataPackage();
1831
								
1832
								//Get all the entities in this package and loop through each to extract its ID and file name
1833
								Entity[] entities = dataPackage.getEntityList();
1834
								for(Entity entity: entities){
1835
									try{
1836
										//Get the file name from the metadata
1837
										String fileNameFromMetadata = entity.getName();
1838
										
1839
										//Get the ecogrid URL from the metadata
1840
										String ecogridIdentifier = entity.getEntityIdentifier();
1841
										//Parse the ecogrid URL to get the local id
1842
										String idFromMetadata = DocumentUtil.getAccessionNumberFromEcogridIdentifier(ecogridIdentifier);
1843
										
1844
										//Get the docid and rev pair
1845
										String docid = DocumentUtil.getDocIdFromString(idFromMetadata);
1846
										String rev = DocumentUtil.getRevisionStringFromString(idFromMetadata);
1847
										
1848
										//Get the GUID
1849
										String guid = IdentifierManager.getInstance().getGUID(docid, Integer.valueOf(rev));
1850
										Identifier dataIdentifier = new Identifier();
1851
										dataIdentifier.setValue(guid);
1852
										
1853
										//Add the GUID to our GUID & file name map
1854
										fileNames.put(dataIdentifier, fileNameFromMetadata);
1855
									}
1856
									catch(Exception e){
1857
										//Prevent just one entity error
1858
										e.printStackTrace();
1859
										logMetacat.debug(e.getMessage(), e);
1860
									}
1861
								}
1862
							}
1863
						}
1864
						catch(Exception e){
1865
							//Catch errors that would prevent package download
1866
							logMetacat.debug(e.toString());
1867
						}
1868
					}
1869
					packagePids.addAll(entries.keySet());
1870
					for (List<Identifier> dataPids: entries.values()) {
1871
						packagePids.addAll(dataPids);
1872
					}
1873
				}
1874
			} else {
1875
				// just the lone pid in this package
1876
				packagePids.add(pid);
1877
			}
1878
			
1879
			//Create a temp file, then delete it and make a directory with that name
1880
			File tempDir = File.createTempFile("temp", Long.toString(System.nanoTime()));
1881
			tempDir.delete();
1882
			tempDir = new File(tempDir.getPath() + "_dir");
1883
			tempDir.mkdir();			
1884
			tempFiles.add(tempDir);
1885
			File pidMappingFile = new File(tempDir, "pid-mapping.txt");
1886
			
1887
			// loop through the package contents
1888
			for (Identifier entryPid: packagePids) {
1889
				//Get the system metadata for each item
1890
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);					
1891
				
1892
				String objectFormatType = ObjectFormatCache.getInstance().getFormat(entrySysMeta.getFormatId()).getFormatType();
1893
				String fileName = null;
1894
				
1895
				//TODO: Be more specific of what characters to replace. Make sure periods arent replaced for the filename from metadata
1896
				//Our default file name is just the ID + format type (e.g. walker.1.1-DATA)
1897
				fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + objectFormatType;
1898

    
1899
				if(fileNames.containsKey(entryPid)){
1900
					//Let's use the file name and extension from the metadata is we have it
1901
					fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + fileNames.get(entryPid).replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
1902
				}
1903
				else{
1904
					//If we couldn't find a given file name, use the system metadata extension
1905
					String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
1906
					fileName += extension;
1907
				}
1908
				
1909
		        //Create a new file for this item and add to the list
1910
				File tempFile = new File(tempDir, fileName);
1911
				tempFiles.add(tempFile);
1912
				
1913
				InputStream entryInputStream = this.get(session, entryPid);			
1914
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
1915
				bag.addFileToPayload(tempFile);
1916
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
1917
			}
1918
			
1919
			//add the the pid to data file map
1920
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
1921
			bag.addFileAsTag(pidMappingFile);
1922
			tempFiles.add(pidMappingFile);
1923
			
1924
			bag = bag.makeComplete();
1925
			
1926
			///Now create the zip file
1927
			//Use the pid as the file name prefix, replacing all non-word characters
1928
			String zipName = pid.getValue().replaceAll("\\W", "_");
1929
			
1930
			File bagFile = new File(tempDir, zipName+".zip");
1931
			
1932
			bag.setFile(bagFile);
1933
			ZipWriter zipWriter = new ZipWriter(bagFactory);
1934
			bag.write(zipWriter, bagFile);
1935
			bagFile = bag.getFile();
1936
			// use custom FIS that will delete the file when closed
1937
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
1938
			// also mark for deletion on shutdown in case the stream is never closed
1939
			bagFile.deleteOnExit();
1940
			tempFiles.add(bagFile);
1941
			
1942
			// clean up other temp files
1943
			for (int i=tempFiles.size()-1; i>=0; i--){
1944
				tempFiles.get(i).delete();
1945
			}
1946
			
1947
		} catch (IOException e) {
1948
			// report as service failure
1949
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1950
			sf.initCause(e);
1951
			throw sf;
1952
		} catch (OREException e) {
1953
			// report as service failure
1954
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1955
			sf.initCause(e);
1956
			throw sf;
1957
		} catch (URISyntaxException e) {
1958
			// report as service failure
1959
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1960
			sf.initCause(e);
1961
			throw sf;
1962
		} catch (OREParserException e) {
1963
			// report as service failure
1964
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1965
			sf.initCause(e);
1966
			throw sf;
1967
		}
1968
		
1969
		return bagInputStream;
1970
	}
1971

    
1972
	@Override
1973
	public OptionList listViews(Session arg0) throws InvalidToken,
1974
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented {
1975
		OptionList views = new OptionList();
1976
		Vector<String> skinNames = null;
1977
		try {
1978
			skinNames = SkinUtil.getSkinNames();
1979
		} catch (PropertyNotFoundException e) {
1980
			throw new ServiceFailure("2841", e.getMessage());
1981
		}
1982
		for (String skinName: skinNames) {
1983
			views.addOption(skinName);
1984
		}
1985
		return views;
1986
	}
1987

    
1988
	@Override
1989
	public InputStream view(Session session, String format, Identifier pid)
1990
			throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
1991
			NotImplemented, NotFound {
1992
		InputStream resultInputStream = null;
1993
		
1994
		SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1995
		InputStream object = this.get(session, pid);
1996

    
1997
		try {
1998
			// can only transform metadata, really
1999
			ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2000
			if (objectFormat.getFormatType().equals("METADATA")) {
2001
				// transform
2002
				DBTransform transformer = new DBTransform();
2003
	            String documentContent = IOUtils.toString(object, "UTF-8");
2004
	            String sourceType = objectFormat.getFormatId().getValue();
2005
	            String targetType = "-//W3C//HTML//EN";
2006
	            ByteArrayOutputStream baos = new ByteArrayOutputStream();
2007
	            Writer writer = new OutputStreamWriter(baos , "UTF-8");
2008
	            // TODO: include more params?
2009
	            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2010
	            String localId = null;
2011
				try {
2012
					localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2013
				} catch (McdbDocNotFoundException e) {
2014
					throw new NotFound("1020", e.getMessage());
2015
				}
2016
	            params.put("qformat", new String[] {format});	            
2017
	            params.put("docid", new String[] {localId});
2018
	            params.put("pid", new String[] {pid.getValue()});
2019
	            transformer.transformXMLDocument(
2020
	                    documentContent , 
2021
	                    sourceType, 
2022
	                    targetType , 
2023
	                    format, 
2024
	                    writer, 
2025
	                    params, 
2026
	                    null //sessionid
2027
	                    );
2028
	            
2029
	            // finally, get the HTML back
2030
	            resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2031
	            ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2032
	
2033
			} else {
2034
				// just return the raw bytes
2035
				resultInputStream = object;
2036
			}
2037
		} catch (IOException e) {
2038
			// report as service failure
2039
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2040
			sf.initCause(e);
2041
			throw sf;
2042
		} catch (PropertyNotFoundException e) {
2043
			// report as service failure
2044
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2045
			sf.initCause(e);
2046
			throw sf;
2047
		} catch (SQLException e) {
2048
			// report as service failure
2049
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2050
			sf.initCause(e);
2051
			throw sf;
2052
		} catch (ClassNotFoundException e) {
2053
			// report as service failure
2054
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2055
			sf.initCause(e);
2056
			throw sf;
2057
		}
2058
		
2059
		return resultInputStream;
2060
	}	
2061
    
2062
}
(4-4/7)