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.IOException;
28
import java.io.InputStream;
29
import java.math.BigInteger;
30
import java.security.NoSuchAlgorithmException;
31
import java.text.SimpleDateFormat;
32
import java.util.ArrayList;
33
import java.util.Calendar;
34
import java.util.Date;
35
import java.util.HashMap;
36
import java.util.List;
37
import java.util.Set;
38
import java.util.Timer;
39
import java.util.UUID;
40
import java.util.Vector;
41

    
42
import javax.servlet.http.HttpServletRequest;
43

    
44
import org.apache.commons.io.IOUtils;
45
import org.apache.log4j.Logger;
46
import org.dataone.client.CNode;
47
import org.dataone.client.D1Client;
48
import org.dataone.client.MNode;
49
import org.dataone.client.auth.CertificateManager;
50
import org.dataone.configuration.Settings;
51
import org.dataone.service.exceptions.BaseException;
52
import org.dataone.service.exceptions.IdentifierNotUnique;
53
import org.dataone.service.exceptions.InsufficientResources;
54
import org.dataone.service.exceptions.InvalidRequest;
55
import org.dataone.service.exceptions.InvalidSystemMetadata;
56
import org.dataone.service.exceptions.InvalidToken;
57
import org.dataone.service.exceptions.NotAuthorized;
58
import org.dataone.service.exceptions.NotFound;
59
import org.dataone.service.exceptions.NotImplemented;
60
import org.dataone.service.exceptions.ServiceFailure;
61
import org.dataone.service.exceptions.SynchronizationFailed;
62
import org.dataone.service.exceptions.UnsupportedType;
63
import org.dataone.service.mn.tier1.v1.MNCore;
64
import org.dataone.service.mn.tier1.v1.MNRead;
65
import org.dataone.service.mn.tier2.v1.MNAuthorization;
66
import org.dataone.service.mn.tier3.v1.MNStorage;
67
import org.dataone.service.mn.tier4.v1.MNReplication;
68
import org.dataone.service.mn.v1.MNQuery;
69
import org.dataone.service.types.v1.Checksum;
70
import org.dataone.service.types.v1.DescribeResponse;
71
import org.dataone.service.types.v1.Event;
72
import org.dataone.service.types.v1.Identifier;
73
import org.dataone.service.types.v1.Log;
74
import org.dataone.service.types.v1.LogEntry;
75
import org.dataone.service.types.v1.MonitorInfo;
76
import org.dataone.service.types.v1.MonitorList;
77
import org.dataone.service.types.v1.Node;
78
import org.dataone.service.types.v1.NodeList;
79
import org.dataone.service.types.v1.NodeReference;
80
import org.dataone.service.types.v1.NodeState;
81
import org.dataone.service.types.v1.NodeType;
82
import org.dataone.service.types.v1.ObjectFormatIdentifier;
83
import org.dataone.service.types.v1.ObjectList;
84
import org.dataone.service.types.v1.Permission;
85
import org.dataone.service.types.v1.Ping;
86
import org.dataone.service.types.v1.ReplicationStatus;
87
import org.dataone.service.types.v1.Schedule;
88
import org.dataone.service.types.v1.Service;
89
import org.dataone.service.types.v1.Services;
90
import org.dataone.service.types.v1.Session;
91
import org.dataone.service.types.v1.Subject;
92
import org.dataone.service.types.v1.Synchronization;
93
import org.dataone.service.types.v1.SystemMetadata;
94
import org.dataone.service.types.v1.util.AuthUtils;
95
import org.dataone.service.types.v1.util.ChecksumUtil;
96
import org.dataone.service.types.v1_1.QueryEngineDescription;
97
import org.dataone.service.types.v1_1.QueryEngineList;
98
import org.dataone.service.types.v1_1.QueryField;
99
import org.dataone.service.util.Constants;
100

    
101
import edu.ucsb.nceas.ezid.EZIDException;
102
import edu.ucsb.nceas.ezid.EZIDService;
103
import edu.ucsb.nceas.ezid.profile.DataCiteProfile;
104
import edu.ucsb.nceas.ezid.profile.DataCiteProfileResourceTypeValues;
105
import edu.ucsb.nceas.ezid.profile.ErcMissingValueCode;
106
import edu.ucsb.nceas.ezid.profile.InternalProfile;
107
import edu.ucsb.nceas.ezid.profile.InternalProfileValues;
108
import edu.ucsb.nceas.metacat.DBQuery;
109
import edu.ucsb.nceas.metacat.EventLog;
110
import edu.ucsb.nceas.metacat.IdentifierManager;
111
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
112
import edu.ucsb.nceas.metacat.MetaCatServlet;
113
import edu.ucsb.nceas.metacat.MetacatHandler;
114
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
115
import edu.ucsb.nceas.metacat.properties.PropertyService;
116
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
117
import edu.ucsb.nceas.metacat.util.DocumentUtil;
118
import edu.ucsb.nceas.metacat.util.SystemUtil;
119
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
120

    
121
/**
122
 * Represents Metacat's implementation of the DataONE Member Node 
123
 * service API. Methods implement the various MN* interfaces, and methods common
124
 * to both Member Node and Coordinating Node interfaces are found in the
125
 * D1NodeService base class.
126
 * 
127
 * Implements:
128
 * MNCore.ping()
129
 * MNCore.getLogRecords()
130
 * MNCore.getObjectStatistics()
131
 * MNCore.getOperationStatistics()
132
 * MNCore.getStatus()
133
 * MNCore.getCapabilities()
134
 * MNRead.get()
135
 * MNRead.getSystemMetadata()
136
 * MNRead.describe()
137
 * MNRead.getChecksum()
138
 * MNRead.listObjects()
139
 * MNRead.synchronizationFailed()
140
 * MNAuthorization.isAuthorized()
141
 * MNAuthorization.setAccessPolicy()
142
 * MNStorage.create()
143
 * MNStorage.update()
144
 * MNStorage.delete()
145
 * MNReplication.replicate()
146
 * 
147
 */
148
public class MNodeService extends D1NodeService 
149
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage, MNQuery {
150

    
151
    private static final String PATHQUERY = "pathquery";
152
	private static final String UUID_SCHEME = "UUID";
153
	private static final String DOI_SCHEME = "DOI";
154
	private static final String UUID_PREFIX = "urn:uuid:";
155

    
156
	/* the logger instance */
157
    private Logger logMetacat = null;
158
    
159
    /* A reference to a remote Memeber Node */
160
    private MNode mn;
161
    
162
    /* A reference to a Coordinating Node */
163
    private CNode cn;
164

    
165

    
166
    /**
167
     * Singleton accessor to get an instance of MNodeService.
168
     * 
169
     * @return instance - the instance of MNodeService
170
     */
171
    public static MNodeService getInstance(HttpServletRequest request) {
172
        return new MNodeService(request);
173
    }
174

    
175
    /**
176
     * Constructor, private for singleton access
177
     */
178
    private MNodeService(HttpServletRequest request) {
179
        super(request);
180
        logMetacat = Logger.getLogger(MNodeService.class);
181
        
182
        // set the Member Node certificate file location
183
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
184
    }
185

    
186
    /**
187
     * Deletes an object from the Member Node, where the object is either a 
188
     * data object or a science metadata object.
189
     * 
190
     * @param session - the Session object containing the credentials for the Subject
191
     * @param pid - The object identifier to be deleted
192
     * 
193
     * @return pid - the identifier of the object used for the deletion
194
     * 
195
     * @throws InvalidToken
196
     * @throws ServiceFailure
197
     * @throws NotAuthorized
198
     * @throws NotFound
199
     * @throws NotImplemented
200
     * @throws InvalidRequest
201
     */
202
    @Override
203
    public Identifier delete(Session session, Identifier pid) 
204
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
205

    
206
    	// only admin of  the MN or the CN is allowed a full delete
207
        boolean allowed = false;
208
        allowed = isAdminAuthorized(session);
209
        if (!allowed) { 
210
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
211
        }
212
    	
213
    	// defer to superclass implementation
214
        return super.delete(session, pid);
215
    }
216

    
217
    /**
218
     * Updates an existing object by creating a new object identified by 
219
     * newPid on the Member Node which explicitly obsoletes the object 
220
     * identified by pid through appropriate changes to the SystemMetadata 
221
     * of pid and newPid
222
     * 
223
     * @param session - the Session object containing the credentials for the Subject
224
     * @param pid - The identifier of the object to be updated
225
     * @param object - the new object bytes
226
     * @param sysmeta - the new system metadata describing the object
227
     * 
228
     * @return newPid - the identifier of the new object
229
     * 
230
     * @throws InvalidToken
231
     * @throws ServiceFailure
232
     * @throws NotAuthorized
233
     * @throws NotFound
234
     * @throws NotImplemented
235
     * @throws IdentifierNotUnique
236
     * @throws UnsupportedType
237
     * @throws InsufficientResources
238
     * @throws InvalidSystemMetadata
239
     * @throws InvalidRequest
240
     */
241
    @Override
242
    public Identifier update(Session session, Identifier pid, InputStream object, 
243
        Identifier newPid, SystemMetadata sysmeta) 
244
        throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
245
        UnsupportedType, InsufficientResources, NotFound, 
246
        InvalidSystemMetadata, NotImplemented, InvalidRequest {
247

    
248
        String localId = null;
249
        boolean allowed = false;
250
        boolean isScienceMetadata = false;
251
        
252
        if (session == null) {
253
        	throw new InvalidToken("1210", "No session has been provided");
254
        }
255
        Subject subject = session.getSubject();
256

    
257
        // verify the pid is valid format
258
        if (!isValidIdentifier(pid)) {
259
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
260
        }
261

    
262
        // check for the existing identifier
263
        try {
264
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
265
            
266
        } catch (McdbDocNotFoundException e) {
267
            throw new InvalidRequest("1202", "The object with the provided " + 
268
                "identifier was not found.");
269
            
270
        }
271
        
272
        // set the originating node
273
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
274
        sysmeta.setOriginMemberNode(originMemberNode);
275
        
276
        // set the submitter to match the certificate
277
        sysmeta.setSubmitter(subject);
278
        // set the dates
279
        Date now = Calendar.getInstance().getTime();
280
        sysmeta.setDateSysMetadataModified(now);
281
        sysmeta.setDateUploaded(now);
282
        
283
        // make sure serial version is set to something
284
        BigInteger serialVersion = sysmeta.getSerialVersion();
285
        if (serialVersion == null) {
286
        	sysmeta.setSerialVersion(BigInteger.ZERO);
287
        }
288

    
289
        // does the subject have WRITE ( == update) priveleges on the pid?
290
        allowed = isAuthorized(session, pid, Permission.WRITE);
291

    
292
        if (allowed) {
293
        	
294
        	// check quality of SM
295
        	if (sysmeta.getObsoletedBy() != null) {
296
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
297
        	}
298
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
299
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
300
        	}
301

    
302
            // get the existing system metadata for the object
303
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
304

    
305
            // check for previous update
306
            // see: https://redmine.dataone.org/issues/3336
307
            Identifier existingObsoletedBy = existingSysMeta.getObsoletedBy();
308
            if (existingObsoletedBy != null) {
309
            	throw new InvalidRequest("1202", 
310
            			"The previous identifier has already been made obsolete by: " + existingObsoletedBy.getValue());
311
            }
312
            
313
            // add the newPid to the obsoletedBy list for the existing sysmeta
314
            existingSysMeta.setObsoletedBy(newPid);
315

    
316
            // then update the existing system metadata
317
            updateSystemMetadata(existingSysMeta);
318

    
319
            // prep the new system metadata, add pid to the affected lists
320
            sysmeta.setObsoletes(pid);
321
            //sysmeta.addDerivedFrom(pid);
322

    
323
            isScienceMetadata = isScienceMetadata(sysmeta);
324

    
325
            // do we have XML metadata or a data object?
326
            if (isScienceMetadata) {
327

    
328
                // update the science metadata XML document
329
                // TODO: handle non-XML metadata/data documents (like netCDF)
330
                // TODO: don't put objects into memory using stream to string
331
                String objectAsXML = "";
332
                try {
333
                    objectAsXML = IOUtils.toString(object, "UTF-8");
334
                    // give the old pid so we can calculate the new local id 
335
                    localId = insertOrUpdateDocument(objectAsXML, pid, session, "update");
336
                    // register the newPid and the generated localId
337
                    if (newPid != null) {
338
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
339

    
340
                    }
341

    
342
                } catch (IOException e) {
343
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
344
                    logMetacat.info(msg);
345
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
346

    
347
                }
348

    
349
            } else {
350

    
351
                // update the data object
352
                localId = insertDataObject(object, newPid, session);
353

    
354
            }
355

    
356
            // and insert the new system metadata
357
            insertSystemMetadata(sysmeta);
358

    
359
            // log the update event
360
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
361
            
362
            // attempt to register the identifier - it checks if it is a doi
363
            try {
364
    			registerDOI(sysmeta);
365
    		} catch (EZIDException e) {
366
                throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
367
    		}
368

    
369
        } else {
370
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
371
                    + " on the Member Node.");
372
        }
373

    
374
        return newPid;
375
    }
376

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

    
380
        // check for null session
381
        if (session == null) {
382
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
383
        }
384
        // set the submitter to match the certificate
385
        sysmeta.setSubmitter(session.getSubject());
386
        // set the originating node
387
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
388
        sysmeta.setOriginMemberNode(originMemberNode);
389
        sysmeta.setArchived(false);
390

    
391
        // set the dates
392
        Date now = Calendar.getInstance().getTime();
393
        sysmeta.setDateSysMetadataModified(now);
394
        sysmeta.setDateUploaded(now);
395
        
396
        // set the serial version
397
        sysmeta.setSerialVersion(BigInteger.ZERO);
398

    
399
        // check that we are not attempting to subvert versioning
400
        if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
401
            throw new InvalidSystemMetadata("1180", 
402
              "The supplied system metadata is invalid. " +
403
              "The obsoletes field cannot have a value when creating entries.");
404
        }
405
        
406
        if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
407
            throw new InvalidSystemMetadata("1180", 
408
              "The supplied system metadata is invalid. " +
409
              "The obsoletedBy field cannot have a value when creating entries.");
410
        }
411

    
412
        // call the shared impl
413
        Identifier resultPid = super.create(session, pid, object, sysmeta);
414
        
415
        // attempt to register the identifier - it checks if it is a doi
416
        try {
417
			registerDOI(sysmeta);
418
		} catch (EZIDException e) {
419
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
420
			sf.initCause(e);
421
            throw sf;
422
		}
423
        
424
        // return 
425
		return resultPid ;
426
    }
427

    
428
    /**
429
     * Called by a Coordinating Node to request that the Member Node create a 
430
     * copy of the specified object by retrieving it from another Member 
431
     * Node and storing it locally so that it can be made accessible to 
432
     * the DataONE system.
433
     * 
434
     * @param session - the Session object containing the credentials for the Subject
435
     * @param sysmeta - Copy of the CN held system metadata for the object
436
     * @param sourceNode - A reference to node from which the content should be 
437
     *                     retrieved. The reference should be resolved by 
438
     *                     checking the CN node registry.
439
     * 
440
     * @return true if the replication succeeds
441
     * 
442
     * @throws ServiceFailure
443
     * @throws NotAuthorized
444
     * @throws NotImplemented
445
     * @throws UnsupportedType
446
     * @throws InsufficientResources
447
     * @throws InvalidRequest
448
     */
449
    @Override
450
    public boolean replicate(Session session, SystemMetadata sysmeta,
451
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
452
            NotAuthorized, InvalidRequest, InsufficientResources,
453
            UnsupportedType {
454

    
455
        if (session != null && sysmeta != null && sourceNode != null) {
456
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
457
                            "\tSession.Subject      = "                           +
458
                            session.getSubject().getValue() + "\n"                +
459
                            "\tidentifier           = "                           + 
460
                            sysmeta.getIdentifier().getValue()                    +
461
                            "\n" + "\tSource NodeReference ="                     +
462
                            sourceNode.getValue());
463
        }
464
        boolean result = false;
465
        String nodeIdStr = null;
466
        NodeReference nodeId = null;
467

    
468
        // get the referenced object
469
        Identifier pid = sysmeta.getIdentifier();
470

    
471
        // get from the membernode
472
        // TODO: switch credentials for the server retrieval?
473
        this.mn = D1Client.getMN(sourceNode);
474
        this.cn = D1Client.getCN();
475
        InputStream object = null;
476
        Session thisNodeSession = null;
477
        SystemMetadata localSystemMetadata = null;
478
        BaseException failure = null;
479
        String localId = null;
480
        
481
        // TODO: check credentials
482
        // cannot be called by public
483
        if (session == null || session.getSubject() == null) {
484
            String msg = "No session was provided to replicate identifier " +
485
            sysmeta.getIdentifier().getValue();
486
            logMetacat.info(msg);
487
            throw new NotAuthorized("2152", msg);
488
            
489
        }
490

    
491

    
492
        // get the local node id
493
        try {
494
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
495
            nodeId = new NodeReference();
496
            nodeId.setValue(nodeIdStr);
497

    
498
        } catch (PropertyNotFoundException e1) {
499
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
500
            failure = new ServiceFailure("2151", msg);
501
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
502
            logMetacat.error(msg);
503
            return true;
504

    
505
        }
506
        
507

    
508
        try {
509
            // do we already have a replica?
510
            try {
511
                localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
512
                // if we have a local id, get the local object
513
                try {
514
                    object = MetacatHandler.read(localId);
515
                } catch (Exception e) {
516
                	// NOTE: we may already know about this ID because it could be a data file described by a metadata file
517
                	// https://redmine.dataone.org/issues/2572
518
                	// TODO: fix this so that we don't prevent ourselves from getting replicas
519
                	
520
                    // let the CN know that the replication failed
521
                	logMetacat.warn("Object content not found on this node despite having localId: " + localId);
522
                	String msg = "Can't read the object bytes properly, replica is invalid.";
523
                    ServiceFailure serviceFailure = new ServiceFailure("2151", msg);
524
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, serviceFailure);
525
                    logMetacat.warn(msg);
526
                    throw serviceFailure;
527
                    
528
                }
529

    
530
            } catch (McdbDocNotFoundException e) {
531
                logMetacat.info("No replica found. Continuing.");
532
                
533
            }
534
            
535
            // no local replica, get a replica
536
            if ( object == null ) {
537
                // session should be null to use the default certificate
538
                // location set in the Certificate manager
539
                object = mn.getReplica(thisNodeSession, pid);
540
                logMetacat.info("MNodeService.getReplica() called for identifier "
541
                                + pid.getValue());
542

    
543
            }
544

    
545
        } catch (InvalidToken e) {            
546
            String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
547
            failure = new ServiceFailure("2151", msg);
548
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
549
            logMetacat.error(msg);
550
            throw new ServiceFailure("2151", msg);
551

    
552
        } catch (NotFound e) {
553
            String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
554
            failure = new ServiceFailure("2151", msg);
555
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
556
            logMetacat.error(msg);
557
            throw new ServiceFailure("2151", msg);
558

    
559
        }
560

    
561
        // verify checksum on the object, if supported
562
        if (object.markSupported()) {
563
            Checksum givenChecksum = sysmeta.getChecksum();
564
            Checksum computedChecksum = null;
565
            try {
566
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
567
                object.reset();
568

    
569
            } catch (Exception e) {
570
                String msg = "Error computing checksum on replica: " + e.getMessage();
571
                logMetacat.error(msg);
572
                ServiceFailure sf = new ServiceFailure("2151", msg);
573
                sf.initCause(e);
574
                throw sf;
575
            }
576
            if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
577
                logMetacat.error("Given    checksum for " + pid.getValue() + 
578
                    "is " + givenChecksum.getValue());
579
                logMetacat.error("Computed checksum for " + pid.getValue() + 
580
                    "is " + computedChecksum.getValue());
581
                throw new ServiceFailure("2151",
582
                        "Computed checksum does not match declared checksum");
583
            }
584
        }
585

    
586
        // add it to local store
587
        Identifier retPid;
588
        try {
589
            // skip the MN.create -- this mutates the system metadata and we don't want it to
590
            if ( localId == null ) {
591
                // TODO: this will fail if we already "know" about the identifier
592
            	// FIXME: see https://redmine.dataone.org/issues/2572
593
                retPid = super.create(session, pid, object, sysmeta);
594
                result = (retPid.getValue().equals(pid.getValue()));
595
            }
596
            
597
        } catch (Exception e) {
598
            String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
599
            failure = new ServiceFailure("2151", msg);
600
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
601
            logMetacat.error(msg);
602
            throw new ServiceFailure("2151", msg);
603
            
604
        }
605

    
606
        // finish by setting the replication status
607
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
608
        return result;
609

    
610
    }
611

    
612
    /**
613
     * Return the object identified by the given object identifier
614
     * 
615
     * @param session - the Session object containing the credentials for the Subject
616
     * @param pid - the object identifier for the given object
617
     * 
618
     * @return inputStream - the input stream of the given object
619
     * 
620
     * @throws InvalidToken
621
     * @throws ServiceFailure
622
     * @throws NotAuthorized
623
     * @throws InvalidRequest
624
     * @throws NotImplemented
625
     */
626
    @Override
627
    public InputStream get(Session session, Identifier pid) 
628
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
629

    
630
        return super.get(session, pid);
631

    
632
    }
633

    
634
    /**
635
     * Returns a Checksum for the specified object using an accepted hashing algorithm
636
     * 
637
     * @param session - the Session object containing the credentials for the Subject
638
     * @param pid - the object identifier for the given object
639
     * @param algorithm -  the name of an algorithm that will be used to compute 
640
     *                     a checksum of the bytes of the object
641
     * 
642
     * @return checksum - the checksum of the given object
643
     * 
644
     * @throws InvalidToken
645
     * @throws ServiceFailure
646
     * @throws NotAuthorized
647
     * @throws NotFound
648
     * @throws InvalidRequest
649
     * @throws NotImplemented
650
     */
651
    @Override
652
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
653
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
654
        InvalidRequest, NotImplemented {
655

    
656
        Checksum checksum = null;
657

    
658
        InputStream inputStream = get(session, pid);
659

    
660
        try {
661
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
662

    
663
        } catch (NoSuchAlgorithmException e) {
664
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
665
                    + e.getMessage());
666
        } catch (IOException e) {
667
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
668
                    + e.getMessage());
669
        }
670

    
671
        if (checksum == null) {
672
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
673
        }
674

    
675
        return checksum;
676
    }
677

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

    
698
        return super.getSystemMetadata(session, pid);
699
    }
700

    
701
    /**
702
     * Retrieve the list of objects present on the MN that match the calling parameters
703
     * 
704
     * @param session - the Session object containing the credentials for the Subject
705
     * @param startTime - Specifies the beginning of the time range from which 
706
     *                    to return object (>=)
707
     * @param endTime - Specifies the beginning of the time range from which 
708
     *                  to return object (>=)
709
     * @param objectFormat - Restrict results to the specified object format
710
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
711
     * @param start - The zero-based index of the first value, relative to the 
712
     *                first record of the resultset that matches the parameters.
713
     * @param count - The maximum number of entries that should be returned in 
714
     *                the response. The Member Node may return less entries 
715
     *                than specified in this value.
716
     * 
717
     * @return objectList - the list of objects matching the criteria
718
     * 
719
     * @throws InvalidToken
720
     * @throws ServiceFailure
721
     * @throws NotAuthorized
722
     * @throws InvalidRequest
723
     * @throws NotImplemented
724
     */
725
    @Override
726
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
727
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
728

    
729
        ObjectList objectList = null;
730

    
731
        try {
732
        	// safeguard against large requests
733
            if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
734
            	count = MAXIMUM_DB_RECORD_COUNT;
735
            }
736
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
737
        } catch (Exception e) {
738
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
739
        }
740

    
741
        return objectList;
742
    }
743

    
744
    /**
745
     * Return a description of the node's capabilities and services.
746
     * 
747
     * @return node - the technical capabilities of the Member Node
748
     * 
749
     * @throws ServiceFailure
750
     * @throws NotAuthorized
751
     * @throws InvalidRequest
752
     * @throws NotImplemented
753
     */
754
    @Override
755
    public Node getCapabilities() 
756
        throws NotImplemented, ServiceFailure {
757

    
758
        String nodeName = null;
759
        String nodeId = null;
760
        String subject = null;
761
        String contactSubject = null;
762
        String nodeDesc = null;
763
        String nodeTypeString = null;
764
        NodeType nodeType = null;
765
        String mnCoreServiceVersion = null;
766
        String mnReadServiceVersion = null;
767
        String mnAuthorizationServiceVersion = null;
768
        String mnStorageServiceVersion = null;
769
        String mnReplicationServiceVersion = null;
770

    
771
        boolean nodeSynchronize = false;
772
        boolean nodeReplicate = false;
773
        boolean mnCoreServiceAvailable = false;
774
        boolean mnReadServiceAvailable = false;
775
        boolean mnAuthorizationServiceAvailable = false;
776
        boolean mnStorageServiceAvailable = false;
777
        boolean mnReplicationServiceAvailable = false;
778

    
779
        try {
780
            // get the properties of the node based on configuration information
781
            nodeName = PropertyService.getProperty("dataone.nodeName");
782
            nodeId = PropertyService.getProperty("dataone.nodeId");
783
            subject = PropertyService.getProperty("dataone.subject");
784
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
785
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
786
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
787
            nodeType = NodeType.convert(nodeTypeString);
788
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
789
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
790

    
791
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
792
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
793
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
794
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
795
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
796

    
797
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
798
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
799
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
800
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
801
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
802

    
803
            // Set the properties of the node based on configuration information and
804
            // calls to current status methods
805
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
806
            Node node = new Node();
807
            node.setBaseURL(serviceName + "/" + nodeTypeString);
808
            node.setDescription(nodeDesc);
809

    
810
            // set the node's health information
811
            node.setState(NodeState.UP);
812
            
813
            // set the ping response to the current value
814
            Ping canPing = new Ping();
815
            canPing.setSuccess(false);
816
            try {
817
            	Date pingDate = ping();
818
                canPing.setSuccess(pingDate != null);
819
            } catch (BaseException e) {
820
                e.printStackTrace();
821
                // guess it can't be pinged
822
            }
823
            
824
            node.setPing(canPing);
825

    
826
            NodeReference identifier = new NodeReference();
827
            identifier.setValue(nodeId);
828
            node.setIdentifier(identifier);
829
            Subject s = new Subject();
830
            s.setValue(subject);
831
            node.addSubject(s);
832
            Subject contact = new Subject();
833
            contact.setValue(contactSubject);
834
            node.addContactSubject(contact);
835
            node.setName(nodeName);
836
            node.setReplicate(nodeReplicate);
837
            node.setSynchronize(nodeSynchronize);
838

    
839
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
840
            Services services = new Services();
841

    
842
            Service sMNCore = new Service();
843
            sMNCore.setName("MNCore");
844
            sMNCore.setVersion(mnCoreServiceVersion);
845
            sMNCore.setAvailable(mnCoreServiceAvailable);
846

    
847
            Service sMNRead = new Service();
848
            sMNRead.setName("MNRead");
849
            sMNRead.setVersion(mnReadServiceVersion);
850
            sMNRead.setAvailable(mnReadServiceAvailable);
851

    
852
            Service sMNAuthorization = new Service();
853
            sMNAuthorization.setName("MNAuthorization");
854
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
855
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
856

    
857
            Service sMNStorage = new Service();
858
            sMNStorage.setName("MNStorage");
859
            sMNStorage.setVersion(mnStorageServiceVersion);
860
            sMNStorage.setAvailable(mnStorageServiceAvailable);
861

    
862
            Service sMNReplication = new Service();
863
            sMNReplication.setName("MNReplication");
864
            sMNReplication.setVersion(mnReplicationServiceVersion);
865
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
866

    
867
            services.addService(sMNRead);
868
            services.addService(sMNCore);
869
            services.addService(sMNAuthorization);
870
            services.addService(sMNStorage);
871
            services.addService(sMNReplication);
872
            node.setServices(services);
873

    
874
            // Set the schedule for synchronization
875
            Synchronization synchronization = new Synchronization();
876
            Schedule schedule = new Schedule();
877
            Date now = new Date();
878
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
879
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
880
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
881
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
882
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
883
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
884
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
885
            synchronization.setSchedule(schedule);
886
            synchronization.setLastHarvested(now);
887
            synchronization.setLastCompleteHarvest(now);
888
            node.setSynchronization(synchronization);
889

    
890
            node.setType(nodeType);
891
            return node;
892

    
893
        } catch (PropertyNotFoundException pnfe) {
894
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
895
            logMetacat.error(msg);
896
            throw new ServiceFailure("2162", msg);
897
        }
898
    }
899

    
900
    /**
901
     * Returns the number of operations that have been serviced by the node 
902
     * over time periods of one and 24 hours.
903
     * 
904
     * @param session - the Session object containing the credentials for the Subject
905
     * @param period - An ISO8601 compatible DateTime range specifying the time 
906
     *                 range for which to return operation statistics.
907
     * @param requestor - Limit to operations performed by given requestor identity.
908
     * @param event -  Enumerated value indicating the type of event being examined
909
     * @param format - Limit to events involving objects of the specified format
910
     * 
911
     * @return the desired log records
912
     * 
913
     * @throws InvalidToken
914
     * @throws ServiceFailure
915
     * @throws NotAuthorized
916
     * @throws InvalidRequest
917
     * @throws NotImplemented
918
     */
919
    public MonitorList getOperationStatistics(Session session, Date startTime, 
920
        Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
921
        throws NotImplemented, ServiceFailure, NotAuthorized, InsufficientResources, UnsupportedType {
922

    
923
        MonitorList monitorList = new MonitorList();
924

    
925
        try {
926

    
927
            // get log records first
928
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
929

    
930
            // TODO: aggregate by day or hour -- needs clarification
931
            int count = 1;
932
            for (LogEntry logEntry : logs.getLogEntryList()) {
933
                Identifier pid = logEntry.getIdentifier();
934
                Date logDate = logEntry.getDateLogged();
935
                // if we are filtering by format
936
                if (formatId != null) {
937
                    SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
938
                    if (!sysmeta.getFormatId().getValue().equals(formatId.getValue())) {
939
                        // does not match
940
                        continue;
941
                    }
942
                }
943
                MonitorInfo item = new MonitorInfo();
944
                item.setCount(count);
945
                item.setDate(new java.sql.Date(logDate.getTime()));
946
                monitorList.addMonitorInfo(item);
947

    
948
            }
949
        } catch (Exception e) {
950
            e.printStackTrace();
951
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
952
        }
953

    
954
        return monitorList;
955

    
956
    }
957

    
958
    /**
959
     * A callback method used by a CN to indicate to a MN that it cannot 
960
     * complete synchronization of the science metadata identified by pid.  Log
961
     * the event in the metacat event log.
962
     * 
963
     * @param session
964
     * @param syncFailed
965
     * 
966
     * @throws ServiceFailure
967
     * @throws NotAuthorized
968
     * @throws NotImplemented
969
     */
970
    @Override
971
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
972
        throws NotImplemented, ServiceFailure, NotAuthorized {
973

    
974
        String localId;
975
        Identifier pid;
976
        if ( syncFailed.getPid() != null ) {
977
            pid = new Identifier();
978
            pid.setValue(syncFailed.getPid());
979
            boolean allowed;
980
            
981
            //are we allowed? only CNs
982
            try {
983
                allowed = isAdminAuthorized(session);
984
                if ( !allowed ){
985
                    throw new NotAuthorized("2162", 
986
                            "Not allowed to call synchronizationFailed() on this node.");
987
                }
988
            } catch (InvalidToken e) {
989
                throw new NotAuthorized("2162", 
990
                        "Not allowed to call synchronizationFailed() on this node.");
991

    
992
            }
993
            
994
        } else {
995
            throw new ServiceFailure("2161", "The identifier cannot be null.");
996

    
997
        }
998
        
999
        try {
1000
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1001
        } catch (McdbDocNotFoundException e) {
1002
            throw new ServiceFailure("2161", "The identifier specified by " + 
1003
                    syncFailed.getPid() + " was not found on this node.");
1004

    
1005
        }
1006
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1007
        // method is changed to include the URL as a parameter
1008
        logMetacat.debug("Synchronization for the object identified by " + 
1009
                pid.getValue() + " failed from " + syncFailed.getNodeId() + 
1010
                " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
1011
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1012
        String principal = Constants.SUBJECT_PUBLIC;
1013
        if (session != null && session.getSubject() != null) {
1014
          principal = session.getSubject().getValue();
1015
        }
1016
        try {
1017
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1018
        } catch (Exception e) {
1019
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1020
        }
1021
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
1022
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1023
        return true;
1024

    
1025
    }
1026

    
1027
    /**
1028
     * Essentially a get() but with different logging behavior
1029
     */
1030
    @Override
1031
    public InputStream getReplica(Session session, Identifier pid) 
1032
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1033

    
1034
        logMetacat.info("MNodeService.getReplica() called.");
1035

    
1036
        // cannot be called by public
1037
        if (session == null) {
1038
        	throw new InvalidToken("2183", "No session was provided.");
1039
        }
1040
        
1041
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1042
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1043
             "\tIdentifier           = " + pid.getValue());
1044

    
1045
        InputStream inputStream = null; // bytes to be returned
1046
        handler = new MetacatHandler(new Timer());
1047
        boolean allowed = false;
1048
        String localId; // the metacat docid for the pid
1049

    
1050
        // get the local docid from Metacat
1051
        try {
1052
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1053
        } catch (McdbDocNotFoundException e) {
1054
            throw new ServiceFailure("2181", "The object specified by " + 
1055
                    pid.getValue() + " does not exist at this node.");
1056
            
1057
        }
1058

    
1059
        Subject targetNodeSubject = session.getSubject();
1060

    
1061
        // check for authorization to replicate, null session to act as this source MN
1062
        try {
1063
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1064
        } catch (InvalidToken e1) {
1065
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1066
                + e1.getMessage());
1067
            
1068
        } catch (NotFound e1) {
1069
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1070
                    + e1.getMessage());
1071

    
1072
        } catch (InvalidRequest e1) {
1073
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1074
                    + e1.getMessage());
1075

    
1076
        }
1077

    
1078
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1079
            " for identifier " + pid.getValue());
1080

    
1081
        // if the person is authorized, perform the read
1082
        if (allowed) {
1083
            try {
1084
                inputStream = MetacatHandler.read(localId);
1085
            } catch (Exception e) {
1086
                throw new ServiceFailure("1020", "The object specified by " + 
1087
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1088
            }
1089
        }
1090

    
1091
        // if we fail to set the input stream
1092
        if (inputStream == null) {
1093
            throw new ServiceFailure("2181", "The object specified by " + 
1094
                pid.getValue() + "does not exist at this node.");
1095
        }
1096

    
1097
        // log the replica event
1098
        String principal = null;
1099
        if (session.getSubject() != null) {
1100
            principal = session.getSubject().getValue();
1101
        }
1102
        EventLog.getInstance().log(request.getRemoteAddr(), 
1103
            request.getHeader("User-Agent"), principal, localId, "replicate");
1104

    
1105
        return inputStream;
1106
    }
1107

    
1108
    /**
1109
     * A method to notify the Member Node that the authoritative copy of 
1110
     * system metadata on the Coordinating Nodes has changed.
1111
     * 
1112
     * @param session   Session information that contains the identity of the 
1113
     *                  calling user as retrieved from the X.509 certificate 
1114
     *                  which must be traceable to the CILogon service.
1115
     * @param serialVersion   The serialVersion of the system metadata
1116
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1117
     * @throws NotImplemented
1118
     * @throws ServiceFailure
1119
     * @throws NotAuthorized
1120
     * @throws InvalidRequest
1121
     * @throws InvalidToken
1122
     */
1123
    public boolean systemMetadataChanged(Session session, Identifier pid,
1124
        long serialVersion, Date dateSysMetaLastModified) 
1125
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1126
        InvalidToken {
1127
        
1128
        SystemMetadata currentLocalSysMeta = null;
1129
        SystemMetadata newSysMeta = null;
1130
        CNode cn = D1Client.getCN();
1131
        NodeList nodeList = null;
1132
        Subject callingSubject = null;
1133
        boolean allowed = false;
1134
        
1135
        // are we allowed to call this?
1136
        callingSubject = session.getSubject();
1137
        nodeList = cn.listNodes();
1138
        
1139
        for(Node node : nodeList.getNodeList()) {
1140
            // must be a CN
1141
            if ( node.getType().equals(NodeType.CN)) {
1142
               List<Subject> subjectList = node.getSubjectList();
1143
               // the calling subject must be in the subject list
1144
               if ( subjectList.contains(callingSubject)) {
1145
                   allowed = true;
1146
                   
1147
               }
1148
               
1149
            }
1150
        }
1151
        
1152
        if (!allowed ) {
1153
            String msg = "The subject identified by " + callingSubject.getValue() +
1154
              " is not authorized to call this service.";
1155
            throw new NotAuthorized("1331", msg);
1156
            
1157
        }
1158
        
1159
        // compare what we have locally to what is sent in the change notification
1160
        try {
1161
            currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1162
             
1163
        } catch (RuntimeException e) {
1164
            String msg = "SystemMetadata for pid " + pid.getValue() +
1165
              " couldn't be updated because it couldn't be found locally: " +
1166
              e.getMessage();
1167
            logMetacat.error(msg);
1168
            ServiceFailure sf = new ServiceFailure("1333", msg);
1169
            sf.initCause(e);
1170
            throw sf; 
1171
        }
1172
        
1173
        if (currentLocalSysMeta.getSerialVersion().longValue() < serialVersion ) {
1174
            try {
1175
                newSysMeta = cn.getSystemMetadata(null, pid);
1176
            } catch (NotFound e) {
1177
                // huh? you just said you had it
1178
            	String msg = "On updating the local copy of system metadata " + 
1179
                "for pid " + pid.getValue() +", the CN reports it is not found." +
1180
                " The error message was: " + e.getMessage();
1181
                logMetacat.error(msg);
1182
                ServiceFailure sf = new ServiceFailure("1333", msg);
1183
                sf.initCause(e);
1184
                throw sf;
1185
            }
1186
            
1187
            // update the local copy of system metadata for the pid
1188
            try {
1189
                HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1190
                logMetacat.info("Updated local copy of system metadata for pid " +
1191
                    pid.getValue() + " after change notification from the CN.");
1192
                
1193
            } catch (RuntimeException e) {
1194
                String msg = "SystemMetadata for pid " + pid.getValue() +
1195
                  " couldn't be updated: " +
1196
                  e.getMessage();
1197
                logMetacat.error(msg);
1198
                ServiceFailure sf = new ServiceFailure("1333", msg);
1199
                sf.initCause(e);
1200
                throw sf;
1201
            }
1202
        }
1203
        
1204
        return true;
1205
        
1206
    }
1207
    
1208
    /*
1209
     * Set the replication status for the object on the Coordinating Node
1210
     * 
1211
     * @param session - the session for the this target node
1212
     * @param pid - the identifier of the object being updated
1213
     * @param nodeId - the identifier of this target node
1214
     * @param status - the replication status to set
1215
     * @param failure - the exception to include, if any
1216
     */
1217
    private void setReplicationStatus(Session session, Identifier pid, 
1218
        NodeReference nodeId, ReplicationStatus status, BaseException failure) 
1219
        throws ServiceFailure, NotImplemented, NotAuthorized, 
1220
        InvalidRequest {
1221
        
1222
        // call the CN as the MN to set the replication status
1223
        try {
1224
            this.cn = D1Client.getCN();
1225
            this.cn.setReplicationStatus(session, pid, nodeId,
1226
                    status, failure);
1227
            
1228
        } catch (InvalidToken e) {
1229
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (InvalidToken): " + e.getMessage();
1230
            logMetacat.error(msg);
1231
        	throw new ServiceFailure("2151",
1232
                    msg);
1233
            
1234
        } catch (NotFound e) {
1235
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (NotFound): " + e.getMessage();
1236
            logMetacat.error(msg);
1237
        	throw new ServiceFailure("2151",
1238
                    msg);
1239
            
1240
        }
1241
    }
1242

    
1243
	@Override
1244
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1245
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1246
			InvalidRequest {
1247
		
1248
		Identifier identifier = new Identifier();
1249
		
1250
		// handle different schemes
1251
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1252
			// UUID
1253
			UUID uuid = UUID.randomUUID();
1254
            identifier.setValue(UUID_PREFIX + uuid.toString());
1255
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1256
			// for DOIs
1257
			// prepend the doi shoulder to the generated identifier
1258
			String shoulder = null;
1259
			String ezidUsername = null;
1260
			String ezidPassword = null;
1261
			boolean doiEnabled = false;
1262
			try {
1263
	            doiEnabled = new Boolean(PropertyService.getProperty("guid.ezid.enabled")).booleanValue();
1264
				shoulder = PropertyService.getProperty("guid.ezid.doishoulder.1");
1265
				ezidUsername = PropertyService.getProperty("guid.ezid.username");
1266
				ezidPassword = PropertyService.getProperty("guid.ezid.password");
1267
			} catch (PropertyNotFoundException e1) {
1268
				throw new InvalidRequest("2193", "DOI shoulder is not configured at this node.");
1269
			}
1270
			
1271
			// only continue if we have the feature turned on
1272
			if (!doiEnabled) {
1273
				throw new InvalidRequest("2193", "DOI scheme is not enabled at this node.");
1274
			}
1275
			
1276
			// add only the minimal metadata required for this DOI
1277
			HashMap<String, String> metadata = new HashMap<String, String>();
1278
			metadata.put(DataCiteProfile.TITLE.toString(), ErcMissingValueCode.UNKNOWN.toString());
1279
			metadata.put(DataCiteProfile.CREATOR.toString(), ErcMissingValueCode.UNKNOWN.toString());
1280
			metadata.put(DataCiteProfile.PUBLISHER.toString(), ErcMissingValueCode.UNKNOWN.toString());
1281
			metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), ErcMissingValueCode.UNKNOWN.toString());
1282
			metadata.put(InternalProfile.STATUS.toString(), InternalProfileValues.RESERVED.toString());
1283
			metadata.put(InternalProfile.EXPORT.toString(), InternalProfileValues.NO.toString());
1284

    
1285
			try {
1286
				// call the EZID service
1287
				String ezidServiceBaseUrl = null;
1288
				try {
1289
					ezidServiceBaseUrl = PropertyService.getProperty("guid.ezid.baseurl");
1290
				} catch (PropertyNotFoundException e) {
1291
					logMetacat.warn("Using default EZID baseUrl");
1292
				}
1293
				EZIDService ezid = new EZIDService(ezidServiceBaseUrl);
1294
				ezid.login(ezidUsername, ezidPassword);
1295
				String doi = null;
1296
				// TODO: perhaps we want to use the identifier as given
1297
				boolean mintDoi = true;
1298
				if (!mintDoi) {
1299
					doi = shoulder + identifier.getValue();
1300
					identifier.setValue(doi);
1301
					doi = ezid.createIdentifier(identifier.getValue(), metadata);
1302
				} else {
1303
					doi = ezid.mintIdentifier(shoulder, metadata);
1304
				}
1305
				identifier.setValue(doi);
1306
				ezid.logout();
1307
			} catch (EZIDException e) {
1308
				throw new ServiceFailure("2191", "Error registering DOI identifier: " + e.getMessage());
1309
			}
1310
		} else {
1311
			// default if we don't know the scheme
1312
			if (fragment != null) {
1313
				// for now, just autogen with fragment
1314
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1315
				identifier.setValue(autogenId);			
1316
			} else {
1317
				// autogen with no fragment
1318
				String autogenId = DocumentUtil.generateDocumentId(0);
1319
				identifier.setValue(autogenId);
1320
			}
1321
		}
1322
		
1323
		// TODO: reserve the identifier with the CN. We can only do this when
1324
		// 1) the MN is part of a CN cluster
1325
		// 2) the request is from an authenticated user
1326
		
1327
		return identifier;
1328
	}
1329

    
1330
	@Override
1331
	public boolean isAuthorized(Identifier pid, Permission permission)
1332
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1333
			NotAuthorized, NotImplemented {
1334

    
1335
		return isAuthorized(null, pid, permission);
1336
	}
1337

    
1338
	@Override
1339
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1340
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1341
			InvalidRequest {
1342

    
1343
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1344
	}
1345

    
1346
	@Override
1347
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1348
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1349
			NotAuthorized, NotImplemented, ServiceFailure {
1350

    
1351
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1352
	}
1353

    
1354
	@Override
1355
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1356
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1357

    
1358
		return describe(null, pid);
1359
	}
1360

    
1361
	@Override
1362
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
1363
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
1364

    
1365
		return get(null, pid);
1366
	}
1367

    
1368
	@Override
1369
	public Checksum getChecksum(Identifier pid, String algorithm)
1370
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1371
			ServiceFailure, NotFound {
1372

    
1373
		return getChecksum(null, pid, algorithm);
1374
	}
1375

    
1376
	@Override
1377
	public SystemMetadata getSystemMetadata(Identifier pid)
1378
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1379
			NotFound {
1380

    
1381
		return getSystemMetadata(null, pid);
1382
	}
1383

    
1384
	@Override
1385
	public ObjectList listObjects(Date startTime, Date endTime,
1386
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1387
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1388
			NotImplemented, ServiceFailure {
1389

    
1390
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1391
	}
1392

    
1393
	@Override
1394
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1395
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1396

    
1397
		return synchronizationFailed(null, syncFailed);
1398
	}
1399

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

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

    
1408
	@Override
1409
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1410
			throws NotImplemented, ServiceFailure, NotAuthorized,
1411
			InvalidRequest, InvalidToken, InsufficientResources,
1412
			UnsupportedType {
1413

    
1414
		return replicate(null, sysmeta, sourceNode);
1415
	}
1416

    
1417
	@Override
1418
	public Identifier create(Identifier pid, InputStream object,
1419
			SystemMetadata sysmeta) throws IdentifierNotUnique,
1420
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1421
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1422
			UnsupportedType {
1423

    
1424
		return create(null, pid, object, sysmeta);
1425
	}
1426

    
1427
	@Override
1428
	public Identifier delete(Identifier pid) throws InvalidToken,
1429
			ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1430

    
1431
		return delete(null, pid);
1432
	}
1433

    
1434
	@Override
1435
	public Identifier generateIdentifier(String scheme, String fragment)
1436
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1437
			InvalidRequest {
1438

    
1439
		return generateIdentifier(null, scheme, fragment);
1440
	}
1441

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

    
1449
		return update(null, pid, object, newPid, sysmeta);
1450
	}
1451

    
1452
	@Override
1453
	public QueryEngineDescription getQueryEngineDescription(String engine)
1454
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1455
			NotFound {
1456
		QueryEngineDescription qed = new QueryEngineDescription();
1457
		qed.setName(PATHQUERY);
1458
		qed.setQueryEngineVersion("1.0");
1459
		qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1460
		Vector<String> pathsForIndexing = null;
1461
		try {
1462
			pathsForIndexing = SystemUtil.getPathsForIndexing();
1463
		} catch (MetacatUtilException e) {
1464
			logMetacat.warn("Could not get index paths", e);
1465
		}
1466
		for (String fieldName: pathsForIndexing) {
1467
			QueryField field = new QueryField();
1468
			field.addDescription("Indexed field for path '" + fieldName + "'");
1469
			field.setName(fieldName);
1470
			field.setReturnable(true);
1471
			field.setSearchable(true);
1472
			field.setSortable(false);
1473
			// TODO: determine type and multivaluedness
1474
			field.setType(String.class.getName());
1475
			//field.setMultivalued(true);
1476
			qed.addQueryField(field);
1477
		}
1478
		return qed;
1479
	}
1480

    
1481
	@Override
1482
	public QueryEngineList listQueryEngines() throws InvalidToken,
1483
			ServiceFailure, NotAuthorized, NotImplemented {
1484
		QueryEngineList qel = new QueryEngineList();
1485
		// support pathquery initially
1486
		qel.addQueryEngine(PATHQUERY);
1487
		// TODO: implement solr-based query
1488
		//qel.addQueryEngine("solr");
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
		if (engine.equals(PATHQUERY)) {
1497
			try {
1498
				DBQuery queryobj = new DBQuery();
1499
				String user = Constants.SUBJECT_PUBLIC;
1500
				String[] groups= null;
1501
				if (session != null) {
1502
					user = session.getSubject().getValue();
1503
					Set<Subject> subjects = AuthUtils.authorizedClientSubjects(session);
1504
					if (subjects != null) {
1505
						List<String> groupList = new ArrayList<String>();
1506
						for (Subject subject: subjects) {
1507
							groupList.add(subject.getValue());
1508
						}
1509
						groups = groupList.toArray(new String[0]);
1510
					}
1511
				}
1512
				String results = queryobj.performPathquery(query, user, groups);
1513
				return new ByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1514

    
1515
			} catch (Exception e) {
1516
				
1517
			}
1518
			
1519
		}
1520
		return null;
1521
	}
1522
	
1523
	/**
1524
	 * submits DOI metadata information about the object to EZID
1525
	 * @param sysMeta
1526
	 * @return
1527
	 * @throws EZIDException 
1528
	 * @throws ServiceFailure 
1529
	 * @throws NotImplemented 
1530
	 */
1531
	private boolean registerDOI(SystemMetadata sysMeta) throws EZIDException, NotImplemented, ServiceFailure {
1532
		
1533
		// for DOIs
1534
		String ezidUsername = null;
1535
		String ezidPassword = null;
1536
		String shoulder = null;
1537
		boolean doiEnabled = false;
1538
		try {
1539
            doiEnabled = new Boolean(PropertyService.getProperty("guid.ezid.enabled")).booleanValue();
1540
			shoulder = PropertyService.getProperty("guid.ezid.doishoulder.1");
1541
			ezidUsername = PropertyService.getProperty("guid.ezid.username");
1542
			ezidPassword = PropertyService.getProperty("guid.ezid.password");
1543
		} catch (PropertyNotFoundException e) {
1544
			logMetacat.warn("DOI support is not configured at this node.", e);
1545
			return false;
1546
		}
1547
		
1548
		// only continue if we have the feature turned on
1549
		if (doiEnabled) {
1550
			
1551
			String identifier = sysMeta.getIdentifier().getValue();
1552
			
1553
			// only continue if this DOI is in our configured shoulder
1554
			if (identifier.startsWith(shoulder)) {
1555
				
1556
				// enter metadata about this identifier
1557
				HashMap<String, String> metadata = null;
1558
				
1559
				// login to EZID service
1560
				String ezidServiceBaseUrl = null;
1561
				try {
1562
					ezidServiceBaseUrl = PropertyService.getProperty("guid.ezid.baseurl");
1563
				} catch (PropertyNotFoundException e) {
1564
					logMetacat.warn("Using default EZID baseUrl");
1565
				}
1566
				EZIDService ezid = new EZIDService(ezidServiceBaseUrl);
1567
				ezid.login(ezidUsername, ezidPassword);
1568
				
1569
				// check for existing metadata
1570
				boolean create = false;
1571
				try {
1572
					metadata = ezid.getMetadata(identifier);
1573
				} catch (EZIDException e) {
1574
					// expected much of the time
1575
					logMetacat.warn("No metadata found for given identifier: " + e.getMessage());
1576
				}
1577
				if (metadata == null) {
1578
					create = true;
1579
				}
1580
				// confuses the API if we send the original metadata that it gave us, so start from scratch
1581
				metadata = new HashMap<String, String>();
1582
				
1583
				// TODO: set title and creator to better values
1584
				Node node = getCapabilities();
1585
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
1586
				String year = sdf .format(sysMeta.getDateUploaded());
1587
				metadata.put(DataCiteProfile.TITLE.toString(), ErcMissingValueCode.UNKNOWN.toString());
1588
				metadata.put(DataCiteProfile.CREATOR.toString(), sysMeta.getRightsHolder().getValue());
1589
				metadata.put(DataCiteProfile.PUBLISHER.toString(), node.getName());
1590
				metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), year);
1591
				metadata.put(DataCiteProfile.RESOURCE_TYPE.toString(), DataCiteProfileResourceTypeValues.DATASET.toString() + "/" + sysMeta.getFormatId().getValue());
1592
				metadata.put(InternalProfile.TARGET.toString(), node.getBaseURL() + "/v1/object/" + identifier);
1593
				metadata.put(InternalProfile.STATUS.toString(), InternalProfileValues.PUBLIC.toString());
1594
				metadata.put(InternalProfile.EXPORT.toString(), InternalProfileValues.YES.toString());
1595
	
1596
				// set using the API
1597
				if (create) {
1598
					ezid.createIdentifier(identifier, metadata);
1599
				} else {
1600
					ezid.setMetadata(identifier, metadata);
1601
				}
1602
				
1603
				ezid.logout();
1604
			}
1605
			
1606
		}
1607
		
1608
		return true;
1609
	}
1610
    
1611
}
(3-3/5)