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.OutputStreamWriter;
34
import java.io.UnsupportedEncodingException;
35
import java.io.Writer;
36
import java.math.BigInteger;
37
import java.net.URISyntaxException;
38
import java.security.NoSuchAlgorithmException;
39
import java.sql.SQLException;
40
import java.util.ArrayList;
41
import java.util.Calendar;
42
import java.util.Date;
43
import java.util.HashSet;
44
import java.util.Hashtable;
45
import java.util.List;
46
import java.util.Map;
47
import java.util.Set;
48
import java.util.Timer;
49
import java.util.UUID;
50
import java.util.Vector;
51

    
52
import javax.servlet.http.HttpServletRequest;
53

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

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

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

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

    
172
    //private static final String PATHQUERY = "pathquery";
173
	public static final String UUID_SCHEME = "UUID";
174
	public static final String DOI_SCHEME = "DOI";
175
	private static final String UUID_PREFIX = "urn:uuid:";
176

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

    
186

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

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

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

    
227
    	// only admin of  the MN or the CN is allowed a full delete
228
        boolean allowed = false;
229
        allowed = isAdminAuthorized(session);
230
        if (!allowed) { 
231
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
232
        }
233
    	
234
    	// defer to superclass implementation
235
        return super.delete(session, pid);
236
    }
237

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

    
269
        String localId = null;
270
        boolean allowed = false;
271
        boolean isScienceMetadata = false;
272
        
273
        if (session == null) {
274
        	throw new InvalidToken("1210", "No session has been provided");
275
        }
276
        Subject subject = session.getSubject();
277

    
278
        // verify the pid is valid format
279
        if (!isValidIdentifier(pid)) {
280
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
281
        }
282

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

    
310
        // does the subject have WRITE ( == update) priveleges on the pid?
311
        allowed = isAuthorized(session, pid, Permission.WRITE);
312

    
313
        if (allowed) {
314
        	
315
        	// check quality of SM
316
        	if (sysmeta.getObsoletedBy() != null) {
317
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
318
        	}
319
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
320
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
321
        	}
322

    
323
            // get the existing system metadata for the object
324
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
325

    
326
            // check for previous update
327
            // see: https://redmine.dataone.org/issues/3336
328
            Identifier existingObsoletedBy = existingSysMeta.getObsoletedBy();
329
            if (existingObsoletedBy != null) {
330
            	throw new InvalidRequest("1202", 
331
            			"The previous identifier has already been made obsolete by: " + existingObsoletedBy.getValue());
332
            }
333
            
334
            // add the newPid to the obsoletedBy list for the existing sysmeta
335
            existingSysMeta.setObsoletedBy(newPid);
336

    
337
            // then update the existing system metadata
338
            updateSystemMetadata(existingSysMeta);
339

    
340
            // prep the new system metadata, add pid to the affected lists
341
            sysmeta.setObsoletes(pid);
342
            //sysmeta.addDerivedFrom(pid);
343

    
344
            isScienceMetadata = isScienceMetadata(sysmeta);
345

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

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

    
361
                    }
362

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

    
368
                }
369

    
370
            } else {
371

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

    
375
            }
376

    
377
            // and insert the new system metadata
378
            insertSystemMetadata(sysmeta);
379

    
380
            // log the update event
381
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
382
            
383
            // attempt to register the identifier - it checks if it is a doi
384
            try {
385
    			DOIService.getInstance().registerDOI(sysmeta);
386
    		} catch (EZIDException e) {
387
                throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
388
    		}
389

    
390
        } else {
391
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
392
                    + " on the Member Node.");
393
        }
394

    
395
        return newPid;
396
    }
397

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

    
401
        // check for null session
402
        if (session == null) {
403
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
404
        }
405
        // set the submitter to match the certificate
406
        sysmeta.setSubmitter(session.getSubject());
407
        // set the originating node
408
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
409
        sysmeta.setOriginMemberNode(originMemberNode);
410
        sysmeta.setArchived(false);
411

    
412
        // set the dates
413
        Date now = Calendar.getInstance().getTime();
414
        sysmeta.setDateSysMetadataModified(now);
415
        sysmeta.setDateUploaded(now);
416
        
417
        // set the serial version
418
        sysmeta.setSerialVersion(BigInteger.ZERO);
419

    
420
        // check that we are not attempting to subvert versioning
421
        if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
422
            throw new InvalidSystemMetadata("1180", 
423
              "The supplied system metadata is invalid. " +
424
              "The obsoletes field cannot have a value when creating entries.");
425
        }
426
        
427
        if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
428
            throw new InvalidSystemMetadata("1180", 
429
              "The supplied system metadata is invalid. " +
430
              "The obsoletedBy field cannot have a value when creating entries.");
431
        }
432

    
433
        // call the shared impl
434
        Identifier resultPid = super.create(session, pid, object, sysmeta);
435
        
436
        // attempt to register the identifier - it checks if it is a doi
437
        try {
438
			DOIService.getInstance().registerDOI(sysmeta);
439
		} catch (EZIDException e) {
440
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
441
			sf.initCause(e);
442
            throw sf;
443
		}
444
        
445
        // return 
446
		return resultPid ;
447
    }
448

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

    
476
        if (session != null && sysmeta != null && sourceNode != null) {
477
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
478
                            "\tSession.Subject      = "                           +
479
                            session.getSubject().getValue() + "\n"                +
480
                            "\tidentifier           = "                           + 
481
                            sysmeta.getIdentifier().getValue()                    +
482
                            "\n" + "\tSource NodeReference ="                     +
483
                            sourceNode.getValue());
484
        }
485
        boolean result = false;
486
        String nodeIdStr = null;
487
        NodeReference nodeId = null;
488

    
489
        // get the referenced object
490
        Identifier pid = sysmeta.getIdentifier();
491

    
492
        // get from the membernode
493
        // TODO: switch credentials for the server retrieval?
494
        this.mn = D1Client.getMN(sourceNode);
495
        this.cn = D1Client.getCN();
496
        InputStream object = null;
497
        Session thisNodeSession = null;
498
        SystemMetadata localSystemMetadata = null;
499
        BaseException failure = null;
500
        String localId = null;
501
        
502
        // TODO: check credentials
503
        // cannot be called by public
504
        if (session == null || session.getSubject() == null) {
505
            String msg = "No session was provided to replicate identifier " +
506
            sysmeta.getIdentifier().getValue();
507
            logMetacat.info(msg);
508
            throw new NotAuthorized("2152", msg);
509
            
510
        }
511

    
512

    
513
        // get the local node id
514
        try {
515
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
516
            nodeId = new NodeReference();
517
            nodeId.setValue(nodeIdStr);
518

    
519
        } catch (PropertyNotFoundException e1) {
520
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
521
            failure = new ServiceFailure("2151", msg);
522
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
523
            logMetacat.error(msg);
524
            return true;
525

    
526
        }
527
        
528

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

    
551
            } catch (McdbDocNotFoundException e) {
552
                logMetacat.info("No replica found. Continuing.");
553
                
554
            }
555
            
556
            // no local replica, get a replica
557
            if ( object == null ) {
558
                // session should be null to use the default certificate
559
                // location set in the Certificate manager
560
                object = mn.getReplica(thisNodeSession, pid);
561
                logMetacat.info("MNodeService.getReplica() called for identifier "
562
                                + pid.getValue());
563

    
564
            }
565

    
566
        } catch (InvalidToken e) {            
567
            String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
568
            failure = new ServiceFailure("2151", msg);
569
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
570
            logMetacat.error(msg);
571
            throw new ServiceFailure("2151", msg);
572

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

    
580
        }
581

    
582
        // verify checksum on the object, if supported
583
        if (object.markSupported()) {
584
            Checksum givenChecksum = sysmeta.getChecksum();
585
            Checksum computedChecksum = null;
586
            try {
587
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
588
                object.reset();
589

    
590
            } catch (Exception e) {
591
                String msg = "Error computing checksum on replica: " + e.getMessage();
592
                logMetacat.error(msg);
593
                ServiceFailure sf = new ServiceFailure("2151", msg);
594
                sf.initCause(e);
595
                throw sf;
596
            }
597
            if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
598
                logMetacat.error("Given    checksum for " + pid.getValue() + 
599
                    "is " + givenChecksum.getValue());
600
                logMetacat.error("Computed checksum for " + pid.getValue() + 
601
                    "is " + computedChecksum.getValue());
602
                throw new ServiceFailure("2151",
603
                        "Computed checksum does not match declared checksum");
604
            }
605
        }
606

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

    
627
        // finish by setting the replication status
628
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
629
        return result;
630

    
631
    }
632

    
633
    /**
634
     * Return the object identified by the given object identifier
635
     * 
636
     * @param session - the Session object containing the credentials for the Subject
637
     * @param pid - the object identifier for the given object
638
     * 
639
     * @return inputStream - the input stream of the given object
640
     * 
641
     * @throws InvalidToken
642
     * @throws ServiceFailure
643
     * @throws NotAuthorized
644
     * @throws InvalidRequest
645
     * @throws NotImplemented
646
     */
647
    @Override
648
    public InputStream get(Session session, Identifier pid) 
649
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
650

    
651
        return super.get(session, pid);
652

    
653
    }
654

    
655
    /**
656
     * Returns a Checksum for the specified object using an accepted hashing algorithm
657
     * 
658
     * @param session - the Session object containing the credentials for the Subject
659
     * @param pid - the object identifier for the given object
660
     * @param algorithm -  the name of an algorithm that will be used to compute 
661
     *                     a checksum of the bytes of the object
662
     * 
663
     * @return checksum - the checksum of the given object
664
     * 
665
     * @throws InvalidToken
666
     * @throws ServiceFailure
667
     * @throws NotAuthorized
668
     * @throws NotFound
669
     * @throws InvalidRequest
670
     * @throws NotImplemented
671
     */
672
    @Override
673
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
674
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
675
        InvalidRequest, NotImplemented {
676

    
677
        Checksum checksum = null;
678

    
679
        InputStream inputStream = get(session, pid);
680

    
681
        try {
682
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
683

    
684
        } catch (NoSuchAlgorithmException e) {
685
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
686
                    + e.getMessage());
687
        } catch (IOException e) {
688
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
689
                    + e.getMessage());
690
        }
691

    
692
        if (checksum == null) {
693
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
694
        }
695

    
696
        return checksum;
697
    }
698

    
699
    /**
700
     * Return the system metadata for a given object
701
     * 
702
     * @param session - the Session object containing the credentials for the Subject
703
     * @param pid - the object identifier for the given object
704
     * 
705
     * @return inputStream - the input stream of the given system metadata object
706
     * 
707
     * @throws InvalidToken
708
     * @throws ServiceFailure
709
     * @throws NotAuthorized
710
     * @throws NotFound
711
     * @throws InvalidRequest
712
     * @throws NotImplemented
713
     */
714
    @Override
715
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) 
716
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
717
        NotImplemented {
718

    
719
        return super.getSystemMetadata(session, pid);
720
    }
721

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

    
750
        ObjectList objectList = null;
751

    
752
        try {
753
        	// safeguard against large requests
754
            if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
755
            	count = MAXIMUM_DB_RECORD_COUNT;
756
            }
757
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
758
        } catch (Exception e) {
759
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
760
        }
761

    
762
        return objectList;
763
    }
764

    
765
    /**
766
     * Return a description of the node's capabilities and services.
767
     * 
768
     * @return node - the technical capabilities of the Member Node
769
     * 
770
     * @throws ServiceFailure
771
     * @throws NotAuthorized
772
     * @throws InvalidRequest
773
     * @throws NotImplemented
774
     */
775
    @Override
776
    public Node getCapabilities() 
777
        throws NotImplemented, ServiceFailure {
778

    
779
        String nodeName = null;
780
        String nodeId = null;
781
        String subject = null;
782
        String contactSubject = null;
783
        String nodeDesc = null;
784
        String nodeTypeString = null;
785
        NodeType nodeType = null;
786
        String mnCoreServiceVersion = null;
787
        String mnReadServiceVersion = null;
788
        String mnAuthorizationServiceVersion = null;
789
        String mnStorageServiceVersion = null;
790
        String mnReplicationServiceVersion = null;
791

    
792
        boolean nodeSynchronize = false;
793
        boolean nodeReplicate = false;
794
        boolean mnCoreServiceAvailable = false;
795
        boolean mnReadServiceAvailable = false;
796
        boolean mnAuthorizationServiceAvailable = false;
797
        boolean mnStorageServiceAvailable = false;
798
        boolean mnReplicationServiceAvailable = false;
799

    
800
        try {
801
            // get the properties of the node based on configuration information
802
            nodeName = PropertyService.getProperty("dataone.nodeName");
803
            nodeId = PropertyService.getProperty("dataone.nodeId");
804
            subject = PropertyService.getProperty("dataone.subject");
805
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
806
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
807
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
808
            nodeType = NodeType.convert(nodeTypeString);
809
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
810
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
811

    
812
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
813
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
814
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
815
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
816
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
817

    
818
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
819
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
820
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
821
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
822
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
823

    
824
            // Set the properties of the node based on configuration information and
825
            // calls to current status methods
826
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
827
            Node node = new Node();
828
            node.setBaseURL(serviceName + "/" + nodeTypeString);
829
            node.setDescription(nodeDesc);
830

    
831
            // set the node's health information
832
            node.setState(NodeState.UP);
833
            
834
            // set the ping response to the current value
835
            Ping canPing = new Ping();
836
            canPing.setSuccess(false);
837
            try {
838
            	Date pingDate = ping();
839
                canPing.setSuccess(pingDate != null);
840
            } catch (BaseException e) {
841
                e.printStackTrace();
842
                // guess it can't be pinged
843
            }
844
            
845
            node.setPing(canPing);
846

    
847
            NodeReference identifier = new NodeReference();
848
            identifier.setValue(nodeId);
849
            node.setIdentifier(identifier);
850
            Subject s = new Subject();
851
            s.setValue(subject);
852
            node.addSubject(s);
853
            Subject contact = new Subject();
854
            contact.setValue(contactSubject);
855
            node.addContactSubject(contact);
856
            node.setName(nodeName);
857
            node.setReplicate(nodeReplicate);
858
            node.setSynchronize(nodeSynchronize);
859

    
860
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
861
            Services services = new Services();
862

    
863
            Service sMNCore = new Service();
864
            sMNCore.setName("MNCore");
865
            sMNCore.setVersion(mnCoreServiceVersion);
866
            sMNCore.setAvailable(mnCoreServiceAvailable);
867

    
868
            Service sMNRead = new Service();
869
            sMNRead.setName("MNRead");
870
            sMNRead.setVersion(mnReadServiceVersion);
871
            sMNRead.setAvailable(mnReadServiceAvailable);
872

    
873
            Service sMNAuthorization = new Service();
874
            sMNAuthorization.setName("MNAuthorization");
875
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
876
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
877

    
878
            Service sMNStorage = new Service();
879
            sMNStorage.setName("MNStorage");
880
            sMNStorage.setVersion(mnStorageServiceVersion);
881
            sMNStorage.setAvailable(mnStorageServiceAvailable);
882

    
883
            Service sMNReplication = new Service();
884
            sMNReplication.setName("MNReplication");
885
            sMNReplication.setVersion(mnReplicationServiceVersion);
886
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
887

    
888
            services.addService(sMNRead);
889
            services.addService(sMNCore);
890
            services.addService(sMNAuthorization);
891
            services.addService(sMNStorage);
892
            services.addService(sMNReplication);
893
            node.setServices(services);
894

    
895
            // Set the schedule for synchronization
896
            Synchronization synchronization = new Synchronization();
897
            Schedule schedule = new Schedule();
898
            Date now = new Date();
899
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
900
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
901
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
902
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
903
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
904
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
905
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
906
            synchronization.setSchedule(schedule);
907
            synchronization.setLastHarvested(now);
908
            synchronization.setLastCompleteHarvest(now);
909
            node.setSynchronization(synchronization);
910

    
911
            node.setType(nodeType);
912
            return node;
913

    
914
        } catch (PropertyNotFoundException pnfe) {
915
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
916
            logMetacat.error(msg);
917
            throw new ServiceFailure("2162", msg);
918
        }
919
    }
920

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

    
944
        MonitorList monitorList = new MonitorList();
945

    
946
        try {
947

    
948
            // get log records first
949
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
950

    
951
            // TODO: aggregate by day or hour -- needs clarification
952
            int count = 1;
953
            for (LogEntry logEntry : logs.getLogEntryList()) {
954
                Identifier pid = logEntry.getIdentifier();
955
                Date logDate = logEntry.getDateLogged();
956
                // if we are filtering by format
957
                if (formatId != null) {
958
                    SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
959
                    if (!sysmeta.getFormatId().getValue().equals(formatId.getValue())) {
960
                        // does not match
961
                        continue;
962
                    }
963
                }
964
                MonitorInfo item = new MonitorInfo();
965
                item.setCount(count);
966
                item.setDate(new java.sql.Date(logDate.getTime()));
967
                monitorList.addMonitorInfo(item);
968

    
969
            }
970
        } catch (Exception e) {
971
            e.printStackTrace();
972
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
973
        }
974

    
975
        return monitorList;
976

    
977
    }
978

    
979
    /**
980
     * A callback method used by a CN to indicate to a MN that it cannot 
981
     * complete synchronization of the science metadata identified by pid.  Log
982
     * the event in the metacat event log.
983
     * 
984
     * @param session
985
     * @param syncFailed
986
     * 
987
     * @throws ServiceFailure
988
     * @throws NotAuthorized
989
     * @throws NotImplemented
990
     */
991
    @Override
992
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
993
        throws NotImplemented, ServiceFailure, NotAuthorized {
994

    
995
        String localId;
996
        Identifier pid;
997
        if ( syncFailed.getPid() != null ) {
998
            pid = new Identifier();
999
            pid.setValue(syncFailed.getPid());
1000
            boolean allowed;
1001
            
1002
            //are we allowed? only CNs
1003
            try {
1004
                allowed = isAdminAuthorized(session);
1005
                if ( !allowed ){
1006
                    throw new NotAuthorized("2162", 
1007
                            "Not allowed to call synchronizationFailed() on this node.");
1008
                }
1009
            } catch (InvalidToken e) {
1010
                throw new NotAuthorized("2162", 
1011
                        "Not allowed to call synchronizationFailed() on this node.");
1012

    
1013
            }
1014
            
1015
        } else {
1016
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1017

    
1018
        }
1019
        
1020
        try {
1021
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1022
        } catch (McdbDocNotFoundException e) {
1023
            throw new ServiceFailure("2161", "The identifier specified by " + 
1024
                    syncFailed.getPid() + " was not found on this node.");
1025

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

    
1046
    }
1047

    
1048
    /**
1049
     * Essentially a get() but with different logging behavior
1050
     */
1051
    @Override
1052
    public InputStream getReplica(Session session, Identifier pid) 
1053
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1054

    
1055
        logMetacat.info("MNodeService.getReplica() called.");
1056

    
1057
        // cannot be called by public
1058
        if (session == null) {
1059
        	throw new InvalidToken("2183", "No session was provided.");
1060
        }
1061
        
1062
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1063
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1064
             "\tIdentifier           = " + pid.getValue());
1065

    
1066
        InputStream inputStream = null; // bytes to be returned
1067
        handler = new MetacatHandler(new Timer());
1068
        boolean allowed = false;
1069
        String localId; // the metacat docid for the pid
1070

    
1071
        // get the local docid from Metacat
1072
        try {
1073
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1074
        } catch (McdbDocNotFoundException e) {
1075
            throw new ServiceFailure("2181", "The object specified by " + 
1076
                    pid.getValue() + " does not exist at this node.");
1077
            
1078
        }
1079

    
1080
        Subject targetNodeSubject = session.getSubject();
1081

    
1082
        // check for authorization to replicate, null session to act as this source MN
1083
        try {
1084
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1085
        } catch (InvalidToken e1) {
1086
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1087
                + e1.getMessage());
1088
            
1089
        } catch (NotFound e1) {
1090
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1091
                    + e1.getMessage());
1092

    
1093
        } catch (InvalidRequest e1) {
1094
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1095
                    + e1.getMessage());
1096

    
1097
        }
1098

    
1099
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1100
            " for identifier " + pid.getValue());
1101

    
1102
        // if the person is authorized, perform the read
1103
        if (allowed) {
1104
            try {
1105
                inputStream = MetacatHandler.read(localId);
1106
            } catch (Exception e) {
1107
                throw new ServiceFailure("1020", "The object specified by " + 
1108
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1109
            }
1110
        }
1111

    
1112
        // if we fail to set the input stream
1113
        if (inputStream == null) {
1114
            throw new ServiceFailure("2181", "The object specified by " + 
1115
                pid.getValue() + "does not exist at this node.");
1116
        }
1117

    
1118
        // log the replica event
1119
        String principal = null;
1120
        if (session.getSubject() != null) {
1121
            principal = session.getSubject().getValue();
1122
        }
1123
        EventLog.getInstance().log(request.getRemoteAddr(), 
1124
            request.getHeader("User-Agent"), principal, localId, "replicate");
1125

    
1126
        return inputStream;
1127
    }
1128

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

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

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

    
1312
	@Override
1313
	public boolean isAuthorized(Identifier pid, Permission permission)
1314
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1315
			NotAuthorized, NotImplemented {
1316

    
1317
		return isAuthorized(null, pid, permission);
1318
	}
1319

    
1320
	@Override
1321
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1322
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1323
			InvalidRequest {
1324

    
1325
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1326
	}
1327

    
1328
	@Override
1329
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1330
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1331
			NotAuthorized, NotImplemented, ServiceFailure {
1332

    
1333
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1334
	}
1335

    
1336
	@Override
1337
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1338
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1339

    
1340
		return describe(null, pid);
1341
	}
1342

    
1343
	@Override
1344
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
1345
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
1346

    
1347
		return get(null, pid);
1348
	}
1349

    
1350
	@Override
1351
	public Checksum getChecksum(Identifier pid, String algorithm)
1352
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1353
			ServiceFailure, NotFound {
1354

    
1355
		return getChecksum(null, pid, algorithm);
1356
	}
1357

    
1358
	@Override
1359
	public SystemMetadata getSystemMetadata(Identifier pid)
1360
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1361
			NotFound {
1362

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

    
1366
	@Override
1367
	public ObjectList listObjects(Date startTime, Date endTime,
1368
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1369
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1370
			NotImplemented, ServiceFailure {
1371

    
1372
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1373
	}
1374

    
1375
	@Override
1376
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1377
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1378

    
1379
		return synchronizationFailed(null, syncFailed);
1380
	}
1381

    
1382
	@Override
1383
	public InputStream getReplica(Identifier pid) throws InvalidToken,
1384
			NotAuthorized, NotImplemented, ServiceFailure, NotFound,
1385
			InsufficientResources {
1386

    
1387
		return getReplica(null, pid);
1388
	}
1389

    
1390
	@Override
1391
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1392
			throws NotImplemented, ServiceFailure, NotAuthorized,
1393
			InvalidRequest, InvalidToken, InsufficientResources,
1394
			UnsupportedType {
1395

    
1396
		return replicate(null, sysmeta, sourceNode);
1397
	}
1398

    
1399
	@Override
1400
	public Identifier create(Identifier pid, InputStream object,
1401
			SystemMetadata sysmeta) throws IdentifierNotUnique,
1402
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1403
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1404
			UnsupportedType {
1405

    
1406
		return create(null, pid, object, sysmeta);
1407
	}
1408

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

    
1413
		return delete(null, pid);
1414
	}
1415

    
1416
	@Override
1417
	public Identifier generateIdentifier(String scheme, String fragment)
1418
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1419
			InvalidRequest {
1420

    
1421
		return generateIdentifier(null, scheme, fragment);
1422
	}
1423

    
1424
	@Override
1425
	public Identifier update(Identifier pid, InputStream object,
1426
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
1427
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1428
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1429
			UnsupportedType, NotFound {
1430

    
1431
		return update(null, pid, object, newPid, sysmeta);
1432
	}
1433

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

    
1479
	@Override
1480
	public QueryEngineList listQueryEngines() throws InvalidToken,
1481
			ServiceFailure, NotAuthorized, NotImplemented {
1482
		QueryEngineList qel = new QueryEngineList();
1483
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1484
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1485
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1486
		for(String name : enables) {
1487
		    qel.addQueryEngine(name);
1488
		}
1489
		return qel;
1490
	}
1491

    
1492
	@Override
1493
	public InputStream query(String engine, String query) throws InvalidToken,
1494
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1495
			NotFound {
1496
	    String user = Constants.SUBJECT_PUBLIC;
1497
        String[] groups= null;
1498
        Set<Subject> subjects = null;
1499
        if (session != null) {
1500
            user = session.getSubject().getValue();
1501
            subjects = AuthUtils.authorizedClientSubjects(session);
1502
            if (subjects != null) {
1503
                List<String> groupList = new ArrayList<String>();
1504
                for (Subject subject: subjects) {
1505
                    groupList.add(subject.getValue());
1506
                }
1507
                groups = groupList.toArray(new String[0]);
1508
            }
1509
        } else {
1510
            //add the public user subject to the set 
1511
            Subject subject = new Subject();
1512
            subject.setValue(Constants.SUBJECT_PUBLIC);
1513
            subjects = new HashSet<Subject>();
1514
            subjects.add(subject);
1515
        }
1516
        //System.out.println("====== user is "+user);
1517
        //System.out.println("====== groups are "+groups);
1518
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1519
			try {
1520
				DBQuery queryobj = new DBQuery();
1521
				
1522
				String results = queryobj.performPathquery(query, user, groups);
1523
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1524
				ctbais.setContentType("text/xml");
1525
				return ctbais;
1526

    
1527
			} catch (Exception e) {
1528
				throw new ServiceFailure("Pathquery error", e.getMessage());
1529
			}
1530
			
1531
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1532
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1533
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1534
		    }
1535
		    logMetacat.info("The query is ==================================== \n"+query);
1536
		    try {
1537
		        
1538
                return MetacatSolrIndex.getInstance().query(query, subjects);
1539
            } catch (Exception e) {
1540
                // TODO Auto-generated catch block
1541
                throw new ServiceFailure("Solr server error", e.getMessage());
1542
            } 
1543
		}
1544
		return null;
1545
	}
1546
	
1547
	/**
1548
	 * Given an existing Science Metadata PID, this method mints a DOI
1549
	 * and updates the original object "publishing" the update with the DOI.
1550
	 * This includes updating the ORE map that describes the Science Metadata+data.
1551
	 * TODO: ensure all referenced objects allow public read
1552
	 * 
1553
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1554
	 * 
1555
	 * @param originalIdentifier
1556
	 * @param request
1557
	 * @throws InvalidRequest 
1558
	 * @throws NotImplemented 
1559
	 * @throws NotAuthorized 
1560
	 * @throws ServiceFailure 
1561
	 * @throws InvalidToken 
1562
	 * @throws NotFound
1563
	 * @throws InvalidSystemMetadata 
1564
	 * @throws InsufficientResources 
1565
	 * @throws UnsupportedType 
1566
	 * @throws IdentifierNotUnique 
1567
	 */
1568
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata {
1569
		
1570
		// mint a DOI for the new revision
1571
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1572
		
1573
		// get the original SM and update the values
1574
		SystemMetadata sysmeta = this.getSystemMetadata(session, originalIdentifier);
1575
		sysmeta.setIdentifier(newIdentifier);
1576
		sysmeta.setObsoletes(originalIdentifier);
1577
		sysmeta.setObsoletedBy(null);
1578
		
1579
		// get the bytes
1580
		InputStream inputStream = this.get(session, originalIdentifier);
1581
		
1582
		// update the object
1583
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
1584
		
1585
		// update ORE that references the scimeta
1586
		// TODO: better ORE location algorithm -- this is just convention for generated resource maps and is fragile
1587
		try {
1588
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1589
			
1590
			Identifier potentialOreIdentifier = new Identifier();
1591
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1592
			
1593
			InputStream oreInputStream = null;
1594
			try {
1595
				oreInputStream = this.get(session, potentialOreIdentifier);
1596
			} catch (NotFound nf) {
1597
				// this is probably okay for many sci meta data docs
1598
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1599
			}
1600
			if (oreInputStream != null) {
1601
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1602
	
1603
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1604
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1605
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1606
				
1607
				// TODO: ensure all data package objects allow public read
1608
	
1609
				// reconstruct the ORE with the new identifiers
1610
				sciMetaMap.remove(originalIdentifier);
1611
				sciMetaMap.put(newIdentifier, dataIdentifiers);
1612
				
1613
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1614
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1615
				
1616
				// get the original ORE SM and update the values
1617
				SystemMetadata oreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1618
				oreSysMeta.setIdentifier(newOreIdentifier);
1619
				oreSysMeta.setObsoletes(potentialOreIdentifier);
1620
				oreSysMeta.setObsoletedBy(null);
1621
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
1622
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
1623
				
1624
				// save the updated ORE
1625
				this.update(
1626
						session, 
1627
						potentialOreIdentifier, 
1628
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")), 
1629
						newOreIdentifier, 
1630
						oreSysMeta);
1631
				
1632
			}
1633
		} catch (McdbDocNotFoundException e) {
1634
			// report as service failure
1635
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1636
			sf.initCause(e);
1637
			throw sf;
1638
		} catch (UnsupportedEncodingException e) {
1639
			// report as service failure
1640
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1641
			sf.initCause(e);
1642
			throw sf;
1643
		} catch (OREException e) {
1644
			// report as service failure
1645
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1646
			sf.initCause(e);
1647
			throw sf;
1648
		} catch (URISyntaxException e) {
1649
			// report as service failure
1650
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1651
			sf.initCause(e);
1652
			throw sf;
1653
		} catch (OREParserException e) {
1654
			// report as service failure
1655
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1656
			sf.initCause(e);
1657
			throw sf;
1658
		} catch (ORESerialiserException e) {
1659
			// report as service failure
1660
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1661
			sf.initCause(e);
1662
			throw sf;
1663
		} catch (NoSuchAlgorithmException e) {
1664
			// report as service failure
1665
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1666
			sf.initCause(e);
1667
			throw sf;
1668
		}
1669
		
1670
		return newIdentifier;
1671
	}
1672
	
1673
	/**
1674
	 * Packages the given package in a Bagit collection for download
1675
	 * @param pid
1676
	 * @throws NotImplemented 
1677
	 * @throws NotFound 
1678
	 * @throws NotAuthorized 
1679
	 * @throws ServiceFailure 
1680
	 * @throws InvalidToken 
1681
	 */
1682
	public InputStream getPackage(Session session, Identifier pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1683
		
1684
		InputStream bagInputStream = null;
1685
		BagFactory bagFactory = new BagFactory();
1686
		Bag bag = bagFactory.createBag();
1687
		
1688
		// track the temp files we use so we can delete them when finished
1689
		List<File> tempFiles = new ArrayList<File>();
1690
		
1691
		// the pids to include in the package
1692
		List<Identifier> packagePids = new ArrayList<Identifier>();
1693
		
1694
		// catch non-D1 service errors and throw as ServiceFailures
1695
		try {
1696
			
1697
			// find the package contents
1698
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1699
			if (ObjectFormatService.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
1700
				InputStream oreInputStream = this.get(session, pid);
1701
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1702
				packagePids.addAll(resourceMapStructure.keySet());
1703
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
1704
					packagePids.addAll(entries.keySet());
1705
					for (List<Identifier> dataPids: entries.values()) {
1706
						packagePids.addAll(dataPids);
1707
					}
1708
				}
1709
			} else {
1710
				// just the lone pid in this package
1711
				packagePids.add(pid);
1712
			}
1713
	
1714
			// track the pid-to-file mapping
1715
			StringBuffer pidMapping = new StringBuffer();
1716
			// loop through the package contents
1717
			for (Identifier entryPid: packagePids) {
1718
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);
1719
				String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
1720
		        String prefix = entryPid.getValue();
1721
		        prefix = "entry";
1722
				File tempFile = File.createTempFile(prefix + ".", extension);
1723
				tempFiles.add(tempFile);
1724
				InputStream entryInputStream = this.get(session, entryPid);
1725
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
1726
				bag.addFileToPayload(tempFile);
1727
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
1728
			}
1729
			
1730
			//add the the pid to data file map
1731
			File pidMappingFile = File.createTempFile("pid-mapping.", ".txt");
1732
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
1733
			bag.addFileAsTag(pidMappingFile);
1734
	
1735
			bag = bag.makeComplete();
1736
			File bagFile = File.createTempFile("bag.", ".zip");
1737
			// TODO: delete more confidently
1738
			bagFile.deleteOnExit();
1739
			bag.setFile(bagFile);
1740
			ZipWriter zipWriter = new ZipWriter(bagFactory);
1741
			bag.write(zipWriter, bagFile);
1742
			bagFile = bag.getFile();
1743
			bagInputStream = new FileInputStream(bagFile);
1744
			
1745
			// clean up temp entry files
1746
			for (File tf: tempFiles) {
1747
				tf.delete();
1748
			}
1749
		} catch (IOException e) {
1750
			// report as service failure
1751
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1752
			sf.initCause(e);
1753
			throw sf;
1754
		} catch (OREException e) {
1755
			// report as service failure
1756
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1757
			sf.initCause(e);
1758
			throw sf;
1759
		} catch (URISyntaxException e) {
1760
			// report as service failure
1761
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1762
			sf.initCause(e);
1763
			throw sf;
1764
		} catch (OREParserException e) {
1765
			// report as service failure
1766
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1767
			sf.initCause(e);
1768
			throw sf;
1769
		}
1770
		
1771
		return bagInputStream;
1772

    
1773
	}
1774
	
1775
	/**
1776
	 * Get a rendered view of the object identified by pid.
1777
	 * Uses the registered format given by the format parameter.
1778
	 * Typically, this is structured HTML that can be styled with CSS.
1779
	 * @param session
1780
	 * @param pid
1781
	 * @param format
1782
	 * @return
1783
	 * @throws InvalidToken
1784
	 * @throws ServiceFailure
1785
	 * @throws NotAuthorized
1786
	 * @throws NotFound
1787
	 * @throws NotImplemented
1788
	 */
1789
	public InputStream getView(Session session, Identifier pid, String format) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1790
		InputStream resultInputStream = null;
1791
		
1792
		SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1793
		InputStream object = this.get(session, pid);
1794

    
1795
		try {
1796
			// can only transform metadata, really
1797
			ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
1798
			if (objectFormat.getFormatType().equals("METADATA")) {
1799
				// transform
1800
				DBTransform transformer = new DBTransform();
1801
	            String documentContent = IOUtils.toString(object, "UTF-8");
1802
	            String sourceType = objectFormat.getFormatId().getValue();
1803
	            String targetType = "-//W3C//HTML//EN";
1804
	            ByteArrayOutputStream baos = new ByteArrayOutputStream();
1805
	            Writer writer = new OutputStreamWriter(baos , "UTF-8");
1806
	            // TODO: include more params?
1807
	            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
1808
	            String localId = null;
1809
				try {
1810
					localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1811
				} catch (McdbDocNotFoundException e) {
1812
					throw new NotFound("1020", e.getMessage());
1813
				}
1814
	            params.put("qformat", new String[] {format});	            
1815
	            params.put("docid", new String[] {localId});
1816
	            params.put("pid", new String[] {pid.getValue()});
1817
	            transformer.transformXMLDocument(
1818
	                    documentContent , 
1819
	                    sourceType, 
1820
	                    targetType , 
1821
	                    format, 
1822
	                    writer, 
1823
	                    params, 
1824
	                    null //sessionid
1825
	                    );
1826
	            
1827
	            // finally, get the HTML back
1828
	            resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
1829
	            ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
1830
	
1831
			} else {
1832
				// just return the raw bytes
1833
				resultInputStream = object;
1834
			}
1835
		} catch (IOException e) {
1836
			// report as service failure
1837
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1838
			sf.initCause(e);
1839
			throw sf;
1840
		} catch (PropertyNotFoundException e) {
1841
			// report as service failure
1842
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1843
			sf.initCause(e);
1844
			throw sf;
1845
		} catch (SQLException e) {
1846
			// report as service failure
1847
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1848
			sf.initCause(e);
1849
			throw sf;
1850
		} catch (ClassNotFoundException e) {
1851
			// report as service failure
1852
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1853
			sf.initCause(e);
1854
			throw sf;
1855
		}
1856
		
1857
		return resultInputStream;
1858
		
1859
	}
1860
    
1861
}
(4-4/6)