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.io.UnsupportedEncodingException;
30
import java.math.BigInteger;
31
import java.net.URISyntaxException;
32
import java.security.NoSuchAlgorithmException;
33
import java.util.ArrayList;
34
import java.util.Calendar;
35
import java.util.Date;
36
import java.util.HashSet;
37
import java.util.List;
38
import java.util.Map;
39
import java.util.Set;
40
import java.util.Timer;
41
import java.util.UUID;
42
import java.util.Vector;
43

    
44
import javax.servlet.http.HttpServletRequest;
45

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

    
108
import edu.ucsb.nceas.ezid.EZIDException;
109
import edu.ucsb.nceas.metacat.DBQuery;
110
import edu.ucsb.nceas.metacat.EventLog;
111
import edu.ucsb.nceas.metacat.IdentifierManager;
112
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
113
import edu.ucsb.nceas.metacat.MetaCatServlet;
114
import edu.ucsb.nceas.metacat.MetacatHandler;
115

    
116
import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines;
117
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
118
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
119
import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler;
120
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
121
import edu.ucsb.nceas.metacat.properties.PropertyService;
122
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
123
import edu.ucsb.nceas.metacat.util.DocumentUtil;
124
import edu.ucsb.nceas.metacat.util.SystemUtil;
125
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
126

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

    
157
    //private static final String PATHQUERY = "pathquery";
158
	public static final String UUID_SCHEME = "UUID";
159
	public static final String DOI_SCHEME = "DOI";
160
	private static final String UUID_PREFIX = "urn:uuid:";
161

    
162
	/* the logger instance */
163
    private Logger logMetacat = null;
164
    
165
    /* A reference to a remote Memeber Node */
166
    private MNode mn;
167
    
168
    /* A reference to a Coordinating Node */
169
    private CNode cn;
170

    
171

    
172
    /**
173
     * Singleton accessor to get an instance of MNodeService.
174
     * 
175
     * @return instance - the instance of MNodeService
176
     */
177
    public static MNodeService getInstance(HttpServletRequest request) {
178
        return new MNodeService(request);
179
    }
180

    
181
    /**
182
     * Constructor, private for singleton access
183
     */
184
    private MNodeService(HttpServletRequest request) {
185
        super(request);
186
        logMetacat = Logger.getLogger(MNodeService.class);
187
        
188
        // set the Member Node certificate file location
189
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
190
    }
191

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

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

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

    
254
        String localId = null;
255
        boolean allowed = false;
256
        boolean isScienceMetadata = false;
257
        
258
        if (session == null) {
259
        	throw new InvalidToken("1210", "No session has been provided");
260
        }
261
        Subject subject = session.getSubject();
262

    
263
        // verify the pid is valid format
264
        if (!isValidIdentifier(pid)) {
265
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
266
        }
267

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

    
295
        // does the subject have WRITE ( == update) priveleges on the pid?
296
        allowed = isAuthorized(session, pid, Permission.WRITE);
297

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

    
308
            // get the existing system metadata for the object
309
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
310

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

    
322
            // then update the existing system metadata
323
            updateSystemMetadata(existingSysMeta);
324

    
325
            // prep the new system metadata, add pid to the affected lists
326
            sysmeta.setObsoletes(pid);
327
            //sysmeta.addDerivedFrom(pid);
328

    
329
            isScienceMetadata = isScienceMetadata(sysmeta);
330

    
331
            // do we have XML metadata or a data object?
332
            if (isScienceMetadata) {
333

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

    
346
                    }
347

    
348
                } catch (IOException e) {
349
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
350
                    logMetacat.info(msg);
351
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
352

    
353
                }
354

    
355
            } else {
356

    
357
                // update the data object
358
                localId = insertDataObject(object, newPid, session);
359

    
360
            }
361

    
362
            // and insert the new system metadata
363
            insertSystemMetadata(sysmeta);
364

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

    
375
        } else {
376
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
377
                    + " on the Member Node.");
378
        }
379

    
380
        return newPid;
381
    }
382

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

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

    
397
        // set the dates
398
        Date now = Calendar.getInstance().getTime();
399
        sysmeta.setDateSysMetadataModified(now);
400
        sysmeta.setDateUploaded(now);
401
        
402
        // set the serial version
403
        sysmeta.setSerialVersion(BigInteger.ZERO);
404

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

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

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

    
461
        if (session != null && sysmeta != null && sourceNode != null) {
462
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
463
                            "\tSession.Subject      = "                           +
464
                            session.getSubject().getValue() + "\n"                +
465
                            "\tidentifier           = "                           + 
466
                            sysmeta.getIdentifier().getValue()                    +
467
                            "\n" + "\tSource NodeReference ="                     +
468
                            sourceNode.getValue());
469
        }
470
        boolean result = false;
471
        String nodeIdStr = null;
472
        NodeReference nodeId = null;
473

    
474
        // get the referenced object
475
        Identifier pid = sysmeta.getIdentifier();
476

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

    
497

    
498
        // get the local node id
499
        try {
500
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
501
            nodeId = new NodeReference();
502
            nodeId.setValue(nodeIdStr);
503

    
504
        } catch (PropertyNotFoundException e1) {
505
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
506
            failure = new ServiceFailure("2151", msg);
507
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
508
            logMetacat.error(msg);
509
            return true;
510

    
511
        }
512
        
513

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

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

    
549
            }
550

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

    
558
        } catch (NotFound e) {
559
            String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
560
            failure = new ServiceFailure("2151", msg);
561
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
562
            logMetacat.error(msg);
563
            throw new ServiceFailure("2151", msg);
564

    
565
        }
566

    
567
        // verify checksum on the object, if supported
568
        if (object.markSupported()) {
569
            Checksum givenChecksum = sysmeta.getChecksum();
570
            Checksum computedChecksum = null;
571
            try {
572
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
573
                object.reset();
574

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

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

    
612
        // finish by setting the replication status
613
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
614
        return result;
615

    
616
    }
617

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

    
636
        return super.get(session, pid);
637

    
638
    }
639

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

    
662
        Checksum checksum = null;
663

    
664
        InputStream inputStream = get(session, pid);
665

    
666
        try {
667
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
668

    
669
        } catch (NoSuchAlgorithmException e) {
670
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
671
                    + e.getMessage());
672
        } catch (IOException e) {
673
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
674
                    + e.getMessage());
675
        }
676

    
677
        if (checksum == null) {
678
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
679
        }
680

    
681
        return checksum;
682
    }
683

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

    
704
        return super.getSystemMetadata(session, pid);
705
    }
706

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

    
735
        ObjectList objectList = null;
736

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

    
747
        return objectList;
748
    }
749

    
750
    /**
751
     * Return a description of the node's capabilities and services.
752
     * 
753
     * @return node - the technical capabilities of the Member Node
754
     * 
755
     * @throws ServiceFailure
756
     * @throws NotAuthorized
757
     * @throws InvalidRequest
758
     * @throws NotImplemented
759
     */
760
    @Override
761
    public Node getCapabilities() 
762
        throws NotImplemented, ServiceFailure {
763

    
764
        String nodeName = null;
765
        String nodeId = null;
766
        String subject = null;
767
        String contactSubject = null;
768
        String nodeDesc = null;
769
        String nodeTypeString = null;
770
        NodeType nodeType = null;
771
        String mnCoreServiceVersion = null;
772
        String mnReadServiceVersion = null;
773
        String mnAuthorizationServiceVersion = null;
774
        String mnStorageServiceVersion = null;
775
        String mnReplicationServiceVersion = null;
776

    
777
        boolean nodeSynchronize = false;
778
        boolean nodeReplicate = false;
779
        boolean mnCoreServiceAvailable = false;
780
        boolean mnReadServiceAvailable = false;
781
        boolean mnAuthorizationServiceAvailable = false;
782
        boolean mnStorageServiceAvailable = false;
783
        boolean mnReplicationServiceAvailable = false;
784

    
785
        try {
786
            // get the properties of the node based on configuration information
787
            nodeName = PropertyService.getProperty("dataone.nodeName");
788
            nodeId = PropertyService.getProperty("dataone.nodeId");
789
            subject = PropertyService.getProperty("dataone.subject");
790
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
791
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
792
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
793
            nodeType = NodeType.convert(nodeTypeString);
794
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
795
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
796

    
797
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
798
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
799
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
800
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
801
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
802

    
803
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
804
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
805
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
806
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
807
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
808

    
809
            // Set the properties of the node based on configuration information and
810
            // calls to current status methods
811
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
812
            Node node = new Node();
813
            node.setBaseURL(serviceName + "/" + nodeTypeString);
814
            node.setDescription(nodeDesc);
815

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

    
832
            NodeReference identifier = new NodeReference();
833
            identifier.setValue(nodeId);
834
            node.setIdentifier(identifier);
835
            Subject s = new Subject();
836
            s.setValue(subject);
837
            node.addSubject(s);
838
            Subject contact = new Subject();
839
            contact.setValue(contactSubject);
840
            node.addContactSubject(contact);
841
            node.setName(nodeName);
842
            node.setReplicate(nodeReplicate);
843
            node.setSynchronize(nodeSynchronize);
844

    
845
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
846
            Services services = new Services();
847

    
848
            Service sMNCore = new Service();
849
            sMNCore.setName("MNCore");
850
            sMNCore.setVersion(mnCoreServiceVersion);
851
            sMNCore.setAvailable(mnCoreServiceAvailable);
852

    
853
            Service sMNRead = new Service();
854
            sMNRead.setName("MNRead");
855
            sMNRead.setVersion(mnReadServiceVersion);
856
            sMNRead.setAvailable(mnReadServiceAvailable);
857

    
858
            Service sMNAuthorization = new Service();
859
            sMNAuthorization.setName("MNAuthorization");
860
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
861
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
862

    
863
            Service sMNStorage = new Service();
864
            sMNStorage.setName("MNStorage");
865
            sMNStorage.setVersion(mnStorageServiceVersion);
866
            sMNStorage.setAvailable(mnStorageServiceAvailable);
867

    
868
            Service sMNReplication = new Service();
869
            sMNReplication.setName("MNReplication");
870
            sMNReplication.setVersion(mnReplicationServiceVersion);
871
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
872

    
873
            services.addService(sMNRead);
874
            services.addService(sMNCore);
875
            services.addService(sMNAuthorization);
876
            services.addService(sMNStorage);
877
            services.addService(sMNReplication);
878
            node.setServices(services);
879

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

    
896
            node.setType(nodeType);
897
            return node;
898

    
899
        } catch (PropertyNotFoundException pnfe) {
900
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
901
            logMetacat.error(msg);
902
            throw new ServiceFailure("2162", msg);
903
        }
904
    }
905

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

    
929
        MonitorList monitorList = new MonitorList();
930

    
931
        try {
932

    
933
            // get log records first
934
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
935

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

    
954
            }
955
        } catch (Exception e) {
956
            e.printStackTrace();
957
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
958
        }
959

    
960
        return monitorList;
961

    
962
    }
963

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

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

    
998
            }
999
            
1000
        } else {
1001
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1002

    
1003
        }
1004
        
1005
        try {
1006
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1007
        } catch (McdbDocNotFoundException e) {
1008
            throw new ServiceFailure("2161", "The identifier specified by " + 
1009
                    syncFailed.getPid() + " was not found on this node.");
1010

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

    
1031
    }
1032

    
1033
    /**
1034
     * Essentially a get() but with different logging behavior
1035
     */
1036
    @Override
1037
    public InputStream getReplica(Session session, Identifier pid) 
1038
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1039

    
1040
        logMetacat.info("MNodeService.getReplica() called.");
1041

    
1042
        // cannot be called by public
1043
        if (session == null) {
1044
        	throw new InvalidToken("2183", "No session was provided.");
1045
        }
1046
        
1047
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1048
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1049
             "\tIdentifier           = " + pid.getValue());
1050

    
1051
        InputStream inputStream = null; // bytes to be returned
1052
        handler = new MetacatHandler(new Timer());
1053
        boolean allowed = false;
1054
        String localId; // the metacat docid for the pid
1055

    
1056
        // get the local docid from Metacat
1057
        try {
1058
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1059
        } catch (McdbDocNotFoundException e) {
1060
            throw new ServiceFailure("2181", "The object specified by " + 
1061
                    pid.getValue() + " does not exist at this node.");
1062
            
1063
        }
1064

    
1065
        Subject targetNodeSubject = session.getSubject();
1066

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

    
1078
        } catch (InvalidRequest e1) {
1079
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1080
                    + e1.getMessage());
1081

    
1082
        }
1083

    
1084
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1085
            " for identifier " + pid.getValue());
1086

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

    
1097
        // if we fail to set the input stream
1098
        if (inputStream == null) {
1099
            throw new ServiceFailure("2181", "The object specified by " + 
1100
                pid.getValue() + "does not exist at this node.");
1101
        }
1102

    
1103
        // log the replica event
1104
        String principal = null;
1105
        if (session.getSubject() != null) {
1106
            principal = session.getSubject().getValue();
1107
        }
1108
        EventLog.getInstance().log(request.getRemoteAddr(), 
1109
            request.getHeader("User-Agent"), principal, localId, "replicate");
1110

    
1111
        return inputStream;
1112
    }
1113

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

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

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

    
1297
	@Override
1298
	public boolean isAuthorized(Identifier pid, Permission permission)
1299
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1300
			NotAuthorized, NotImplemented {
1301

    
1302
		return isAuthorized(null, pid, permission);
1303
	}
1304

    
1305
	@Override
1306
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1307
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1308
			InvalidRequest {
1309

    
1310
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1311
	}
1312

    
1313
	@Override
1314
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1315
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1316
			NotAuthorized, NotImplemented, ServiceFailure {
1317

    
1318
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1319
	}
1320

    
1321
	@Override
1322
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1323
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1324

    
1325
		return describe(null, pid);
1326
	}
1327

    
1328
	@Override
1329
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
1330
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
1331

    
1332
		return get(null, pid);
1333
	}
1334

    
1335
	@Override
1336
	public Checksum getChecksum(Identifier pid, String algorithm)
1337
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1338
			ServiceFailure, NotFound {
1339

    
1340
		return getChecksum(null, pid, algorithm);
1341
	}
1342

    
1343
	@Override
1344
	public SystemMetadata getSystemMetadata(Identifier pid)
1345
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1346
			NotFound {
1347

    
1348
		return getSystemMetadata(null, pid);
1349
	}
1350

    
1351
	@Override
1352
	public ObjectList listObjects(Date startTime, Date endTime,
1353
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1354
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1355
			NotImplemented, ServiceFailure {
1356

    
1357
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1358
	}
1359

    
1360
	@Override
1361
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1362
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1363

    
1364
		return synchronizationFailed(null, syncFailed);
1365
	}
1366

    
1367
	@Override
1368
	public InputStream getReplica(Identifier pid) throws InvalidToken,
1369
			NotAuthorized, NotImplemented, ServiceFailure, NotFound,
1370
			InsufficientResources {
1371

    
1372
		return getReplica(null, pid);
1373
	}
1374

    
1375
	@Override
1376
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1377
			throws NotImplemented, ServiceFailure, NotAuthorized,
1378
			InvalidRequest, InvalidToken, InsufficientResources,
1379
			UnsupportedType {
1380

    
1381
		return replicate(null, sysmeta, sourceNode);
1382
	}
1383

    
1384
	@Override
1385
	public Identifier create(Identifier pid, InputStream object,
1386
			SystemMetadata sysmeta) throws IdentifierNotUnique,
1387
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1388
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1389
			UnsupportedType {
1390

    
1391
		return create(null, pid, object, sysmeta);
1392
	}
1393

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

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

    
1401
	@Override
1402
	public Identifier generateIdentifier(String scheme, String fragment)
1403
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1404
			InvalidRequest {
1405

    
1406
		return generateIdentifier(null, scheme, fragment);
1407
	}
1408

    
1409
	@Override
1410
	public Identifier update(Identifier pid, InputStream object,
1411
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
1412
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1413
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1414
			UnsupportedType, NotFound {
1415

    
1416
		return update(null, pid, object, newPid, sysmeta);
1417
	}
1418

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

    
1464
	@Override
1465
	public QueryEngineList listQueryEngines() throws InvalidToken,
1466
			ServiceFailure, NotAuthorized, NotImplemented {
1467
		QueryEngineList qel = new QueryEngineList();
1468
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1469
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1470
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1471
		for(String name : enables) {
1472
		    qel.addQueryEngine(name);
1473
		}
1474
		return qel;
1475
	}
1476

    
1477
	@Override
1478
	public InputStream query(String engine, String query) throws InvalidToken,
1479
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1480
			NotFound {
1481
	    String user = Constants.SUBJECT_PUBLIC;
1482
        String[] groups= null;
1483
        Set<Subject> subjects = null;
1484
        if (session != null) {
1485
            user = session.getSubject().getValue();
1486
            subjects = AuthUtils.authorizedClientSubjects(session);
1487
            if (subjects != null) {
1488
                List<String> groupList = new ArrayList<String>();
1489
                for (Subject subject: subjects) {
1490
                    groupList.add(subject.getValue());
1491
                }
1492
                groups = groupList.toArray(new String[0]);
1493
            }
1494
        } else {
1495
            //add the public user subject to the set 
1496
            Subject subject = new Subject();
1497
            subject.setValue(Constants.SUBJECT_PUBLIC);
1498
            subjects = new HashSet<Subject>();
1499
            subjects.add(subject);
1500
        }
1501
        //System.out.println("====== user is "+user);
1502
        //System.out.println("====== groups are "+groups);
1503
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1504
			try {
1505
				DBQuery queryobj = new DBQuery();
1506
				
1507
				String results = queryobj.performPathquery(query, user, groups);
1508
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1509
				ctbais.setContentType("text/xml");
1510
				return ctbais;
1511

    
1512
			} catch (Exception e) {
1513
				throw new ServiceFailure("Pathquery error", e.getMessage());
1514
			}
1515
			
1516
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1517
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1518
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1519
		    }
1520
		    logMetacat.info("The query is ==================================== \n"+query);
1521
		    try {
1522
		        
1523
                return MetacatSolrIndex.getInstance().query(query, subjects);
1524
            } catch (Exception e) {
1525
                // TODO Auto-generated catch block
1526
                throw new ServiceFailure("Solr server error", e.getMessage());
1527
            } 
1528
		}
1529
		return null;
1530
	}
1531
	
1532
	/**
1533
	 * Given an existing Science Metadata PID, this method mints a DOI
1534
	 * and updates the original object "publishing" the update with the DOI.
1535
	 * This includes updating the ORE map that describes the Science Metadata+data.
1536
	 * TODO: ensure all referenced objects allow public read
1537
	 * 
1538
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1539
	 * 
1540
	 * @param originalIdentifier
1541
	 * @param request
1542
	 * @throws InvalidRequest
1543
	 * @throws EZIDException
1544
	 * @throws InvalidToken
1545
	 * @throws NotAuthorized
1546
	 * @throws NotImplemented
1547
	 * @throws ServiceFailure
1548
	 * @throws NotFound
1549
	 * @throws InsufficientResources
1550
	 * @throws IdentifierNotUnique
1551
	 * @throws InvalidSystemMetadata
1552
	 * @throws UnsupportedType
1553
	 * @throws McdbDocNotFoundException 
1554
	 * @throws OREParserException 
1555
	 * @throws URISyntaxException 
1556
	 * @throws OREException 
1557
	 * @throws UnsupportedEncodingException 
1558
	 * @throws ORESerialiserException 
1559
	 * @throws NoSuchAlgorithmException 
1560
	 */
1561
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidRequest, EZIDException, InvalidToken, NotAuthorized, NotImplemented, ServiceFailure, NotFound, InsufficientResources, IdentifierNotUnique, InvalidSystemMetadata, UnsupportedType, McdbDocNotFoundException, UnsupportedEncodingException, OREException, URISyntaxException, OREParserException, ORESerialiserException, NoSuchAlgorithmException {
1562
		
1563
		// mint a DOI for the new revision
1564
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1565
		
1566
		// get the original SM and update the values
1567
		SystemMetadata sysmeta = this.getSystemMetadata(session, originalIdentifier);
1568
		sysmeta.setIdentifier(newIdentifier);
1569
		sysmeta.setObsoletes(originalIdentifier);
1570
		sysmeta.setObsoletedBy(null);
1571
		
1572
		// get the bytes
1573
		InputStream inputStream = this.get(session, originalIdentifier);
1574
		
1575
		// update the object
1576
		this.update(session, originalIdentifier, inputStream , newIdentifier, sysmeta);
1577
		
1578
		// TODO: update ORE that references the scimeta
1579
		String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1580
		Identifier potentialOreIdentifier = new Identifier();
1581
		potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1582
		
1583
		InputStream oreInputStream = null;
1584
		try {
1585
			oreInputStream = this.get(session, potentialOreIdentifier);
1586
		} catch (NotFound nf) {
1587
			// this is probably okay for many sci meta data docs
1588
			logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1589
		}
1590
		if (oreInputStream != null) {
1591
			Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1592

    
1593
			Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1594
			Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1595
			List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1596
			
1597
			// TODO: ensure all data package objects allow public read
1598

    
1599
			// reconstruct the ORE with the new identifiers
1600
			sciMetaMap.remove(originalIdentifier);
1601
			sciMetaMap.put(newIdentifier, dataIdentifiers);
1602
			
1603
			ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1604
			String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1605
			
1606
			// get the original ORE SM and update the values
1607
			SystemMetadata oreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1608
			oreSysMeta.setIdentifier(newOreIdentifier);
1609
			oreSysMeta.setObsoletes(potentialOreIdentifier);
1610
			oreSysMeta.setObsoletedBy(null);
1611
			oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
1612
			oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
1613
			
1614
			// save the updated ORE
1615
			this.update(
1616
					session, 
1617
					potentialOreIdentifier, 
1618
					new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")), 
1619
					newOreIdentifier, 
1620
					oreSysMeta);
1621
			
1622
		}
1623
		
1624
		return newIdentifier;
1625
	}
1626
    
1627
}
(4-4/6)