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.util.ArrayList;
32
import java.util.Calendar;
33
import java.util.Date;
34
import java.util.HashSet;
35
import java.util.List;
36
import java.util.Set;
37
import java.util.Timer;
38
import java.util.UUID;
39
import java.util.Vector;
40

    
41
import javax.servlet.http.HttpServletRequest;
42

    
43
import org.apache.commons.io.IOUtils;
44
import org.apache.log4j.Logger;
45
import org.apache.solr.client.solrj.SolrServerException;
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.metacat.DBQuery;
103
import edu.ucsb.nceas.metacat.EventLog;
104
import edu.ucsb.nceas.metacat.IdentifierManager;
105
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
106
import edu.ucsb.nceas.metacat.MetaCatServlet;
107
import edu.ucsb.nceas.metacat.MetacatHandler;
108

    
109
import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines;
110
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
111
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
112
import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler;
113
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
114
import edu.ucsb.nceas.metacat.properties.PropertyService;
115
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
116
import edu.ucsb.nceas.metacat.util.DocumentUtil;
117
import edu.ucsb.nceas.metacat.util.SystemUtil;
118
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
119

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

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

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

    
164

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
322
            isScienceMetadata = isScienceMetadata(sysmeta);
323

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

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

    
339
                    }
340

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

    
346
                }
347

    
348
            } else {
349

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

    
353
            }
354

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

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

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

    
373
        return newPid;
374
    }
375

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

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

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

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

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

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

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

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

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

    
490

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

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

    
504
        }
505
        
506

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

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

    
542
            }
543

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

    
551
        } catch (NotFound e) {
552
            String msg = "Could not retrieve object to replicate (NotFound): "+ 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
        }
559

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

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

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

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

    
609
    }
610

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

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

    
631
    }
632

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

    
655
        Checksum checksum = null;
656

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

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

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

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

    
674
        return checksum;
675
    }
676

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

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

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

    
728
        ObjectList objectList = null;
729

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

    
740
        return objectList;
741
    }
742

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
922
        MonitorList monitorList = new MonitorList();
923

    
924
        try {
925

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

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

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

    
953
        return monitorList;
954

    
955
    }
956

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

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

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

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

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

    
1024
    }
1025

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

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

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

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

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

    
1058
        Subject targetNodeSubject = session.getSubject();
1059

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

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

    
1075
        }
1076

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

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

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

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

    
1104
        return inputStream;
1105
    }
1106

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

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

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

    
1290
	@Override
1291
	public boolean isAuthorized(Identifier pid, Permission permission)
1292
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1293
			NotAuthorized, NotImplemented {
1294

    
1295
		return isAuthorized(null, pid, permission);
1296
	}
1297

    
1298
	@Override
1299
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1300
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1301
			InvalidRequest {
1302

    
1303
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1304
	}
1305

    
1306
	@Override
1307
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1308
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1309
			NotAuthorized, NotImplemented, ServiceFailure {
1310

    
1311
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1312
	}
1313

    
1314
	@Override
1315
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1316
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1317

    
1318
		return describe(null, pid);
1319
	}
1320

    
1321
	@Override
1322
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
1323
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
1324

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

    
1328
	@Override
1329
	public Checksum getChecksum(Identifier pid, String algorithm)
1330
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1331
			ServiceFailure, NotFound {
1332

    
1333
		return getChecksum(null, pid, algorithm);
1334
	}
1335

    
1336
	@Override
1337
	public SystemMetadata getSystemMetadata(Identifier pid)
1338
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1339
			NotFound {
1340

    
1341
		return getSystemMetadata(null, pid);
1342
	}
1343

    
1344
	@Override
1345
	public ObjectList listObjects(Date startTime, Date endTime,
1346
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1347
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1348
			NotImplemented, ServiceFailure {
1349

    
1350
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1351
	}
1352

    
1353
	@Override
1354
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1355
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1356

    
1357
		return synchronizationFailed(null, syncFailed);
1358
	}
1359

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

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

    
1368
	@Override
1369
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1370
			throws NotImplemented, ServiceFailure, NotAuthorized,
1371
			InvalidRequest, InvalidToken, InsufficientResources,
1372
			UnsupportedType {
1373

    
1374
		return replicate(null, sysmeta, sourceNode);
1375
	}
1376

    
1377
	@Override
1378
	public Identifier create(Identifier pid, InputStream object,
1379
			SystemMetadata sysmeta) throws IdentifierNotUnique,
1380
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1381
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1382
			UnsupportedType {
1383

    
1384
		return create(null, pid, object, sysmeta);
1385
	}
1386

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

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

    
1394
	@Override
1395
	public Identifier generateIdentifier(String scheme, String fragment)
1396
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1397
			InvalidRequest {
1398

    
1399
		return generateIdentifier(null, scheme, fragment);
1400
	}
1401

    
1402
	@Override
1403
	public Identifier update(Identifier pid, InputStream object,
1404
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
1405
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1406
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1407
			UnsupportedType, NotFound {
1408

    
1409
		return update(null, pid, object, newPid, sysmeta);
1410
	}
1411

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

    
1457
	@Override
1458
	public QueryEngineList listQueryEngines() throws InvalidToken,
1459
			ServiceFailure, NotAuthorized, NotImplemented {
1460
		QueryEngineList qel = new QueryEngineList();
1461
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1462
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1463
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1464
		for(String name : enables) {
1465
		    qel.addQueryEngine(name);
1466
		}
1467
		return qel;
1468
	}
1469

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

    
1505
			} catch (Exception e) {
1506
				throw new ServiceFailure("Pathquery error", e.getMessage());
1507
			}
1508
			
1509
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1510
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1511
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1512
		    }
1513
		    logMetacat.info("The query is ==================================== \n"+query);
1514
		    try {
1515
		        
1516
                return MetacatSolrIndex.getInstance().query(query, subjects);
1517
            } catch (Exception e) {
1518
                // TODO Auto-generated catch block
1519
                throw new ServiceFailure("Solr server error", e.getMessage());
1520
            } 
1521
		}
1522
		return null;
1523
	}
1524
    
1525
}
(4-4/6)