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.sql.SQLException;
32
import java.util.ArrayList;
33
import java.util.Calendar;
34
import java.util.Date;
35
import java.util.HashMap;
36
import java.util.List;
37
import java.util.Set;
38
import java.util.Timer;
39
import java.util.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.dataone.client.CNode;
46
import org.dataone.client.D1Client;
47
import org.dataone.client.MNode;
48
import org.dataone.client.auth.CertificateManager;
49
import org.dataone.configuration.Settings;
50
import org.dataone.service.exceptions.BaseException;
51
import org.dataone.service.exceptions.IdentifierNotUnique;
52
import org.dataone.service.exceptions.InsufficientResources;
53
import org.dataone.service.exceptions.InvalidRequest;
54
import org.dataone.service.exceptions.InvalidSystemMetadata;
55
import org.dataone.service.exceptions.InvalidToken;
56
import org.dataone.service.exceptions.NotAuthorized;
57
import org.dataone.service.exceptions.NotFound;
58
import org.dataone.service.exceptions.NotImplemented;
59
import org.dataone.service.exceptions.ServiceFailure;
60
import org.dataone.service.exceptions.SynchronizationFailed;
61
import org.dataone.service.exceptions.UnsupportedType;
62
import org.dataone.service.mn.tier1.v1.MNCore;
63
import org.dataone.service.mn.tier1.v1.MNRead;
64
import org.dataone.service.mn.tier2.v1.MNAuthorization;
65
import org.dataone.service.mn.tier3.v1.MNStorage;
66
import org.dataone.service.mn.tier4.v1.MNReplication;
67
import org.dataone.service.mn.v1.MNQuery;
68
import org.dataone.service.types.v1.Checksum;
69
import org.dataone.service.types.v1.DescribeResponse;
70
import org.dataone.service.types.v1.Event;
71
import org.dataone.service.types.v1.Identifier;
72
import org.dataone.service.types.v1.Log;
73
import org.dataone.service.types.v1.LogEntry;
74
import org.dataone.service.types.v1.MonitorInfo;
75
import org.dataone.service.types.v1.MonitorList;
76
import org.dataone.service.types.v1.Node;
77
import org.dataone.service.types.v1.NodeList;
78
import org.dataone.service.types.v1.NodeReference;
79
import org.dataone.service.types.v1.NodeState;
80
import org.dataone.service.types.v1.NodeType;
81
import org.dataone.service.types.v1.ObjectFormatIdentifier;
82
import org.dataone.service.types.v1.ObjectList;
83
import org.dataone.service.types.v1.Permission;
84
import org.dataone.service.types.v1.Ping;
85
import org.dataone.service.types.v1.ReplicationStatus;
86
import org.dataone.service.types.v1.Schedule;
87
import org.dataone.service.types.v1.Service;
88
import org.dataone.service.types.v1.Services;
89
import org.dataone.service.types.v1.Session;
90
import org.dataone.service.types.v1.Subject;
91
import org.dataone.service.types.v1.Synchronization;
92
import org.dataone.service.types.v1.SystemMetadata;
93
import org.dataone.service.types.v1.util.AuthUtils;
94
import org.dataone.service.types.v1.util.ChecksumUtil;
95
import org.dataone.service.types.v1_1.QueryEngineDescription;
96
import org.dataone.service.types.v1_1.QueryEngineList;
97
import org.dataone.service.types.v1_1.QueryField;
98
import org.dataone.service.util.Constants;
99

    
100
import edu.ucsb.nceas.ezid.EZIDException;
101
import edu.ucsb.nceas.ezid.EZIDService;
102
import edu.ucsb.nceas.metacat.DBQuery;
103
import edu.ucsb.nceas.metacat.DBUtil;
104
import edu.ucsb.nceas.metacat.EventLog;
105
import edu.ucsb.nceas.metacat.IdentifierManager;
106
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
107
import edu.ucsb.nceas.metacat.MetaCatServlet;
108
import edu.ucsb.nceas.metacat.MetacatHandler;
109
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
110
import edu.ucsb.nceas.metacat.properties.PropertyService;
111
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
112
import edu.ucsb.nceas.metacat.util.DocumentUtil;
113
import edu.ucsb.nceas.metacat.util.SystemUtil;
114
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
115

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

    
146
    private static final String PATHQUERY = "pathquery";
147

    
148
	/* the logger instance */
149
    private Logger logMetacat = null;
150
    
151
    /* A reference to a remote Memeber Node */
152
    private MNode mn;
153
    
154
    /* A reference to a Coordinating Node */
155
    private CNode cn;
156

    
157

    
158
    /**
159
     * Singleton accessor to get an instance of MNodeService.
160
     * 
161
     * @return instance - the instance of MNodeService
162
     */
163
    public static MNodeService getInstance(HttpServletRequest request) {
164
        return new MNodeService(request);
165
    }
166

    
167
    /**
168
     * Constructor, private for singleton access
169
     */
170
    private MNodeService(HttpServletRequest request) {
171
        super(request);
172
        logMetacat = Logger.getLogger(MNodeService.class);
173
        
174
        // set the Member Node certificate file location
175
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
176
    }
177

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

    
198
    	// only admin of  the MN or the CN is allowed a full delete
199
        boolean allowed = false;
200
        allowed = isAdminAuthorized(session);
201
        if (!allowed) { 
202
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
203
        }
204
    	
205
    	// defer to superclass implementation
206
        return super.delete(session, pid);
207
    }
208

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

    
240
        String localId = null;
241
        boolean allowed = false;
242
        boolean isScienceMetadata = false;
243
        
244
        if (session == null) {
245
        	throw new InvalidToken("1210", "No session has been provided");
246
        }
247
        Subject subject = session.getSubject();
248

    
249
        // verify the pid is valid format
250
        if (!isValidIdentifier(pid)) {
251
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
252
        }
253

    
254
        // check for the existing identifier
255
        try {
256
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
257
            
258
        } catch (McdbDocNotFoundException e) {
259
            throw new InvalidRequest("1202", "The object with the provided " + 
260
                "identifier was not found.");
261
            
262
        }
263
        
264
        // set the originating node
265
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
266
        sysmeta.setOriginMemberNode(originMemberNode);
267
        
268
        // set the submitter to match the certificate
269
        sysmeta.setSubmitter(subject);
270
        // set the dates
271
        Date now = Calendar.getInstance().getTime();
272
        sysmeta.setDateSysMetadataModified(now);
273
        sysmeta.setDateUploaded(now);
274

    
275
        // does the subject have WRITE ( == update) priveleges on the pid?
276
        allowed = isAuthorized(session, pid, Permission.WRITE);
277

    
278
        if (allowed) {
279
        	
280
        	// check quality of SM
281
        	if (sysmeta.getObsoletedBy() != null) {
282
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
283
        	}
284
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
285
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
286
        	}
287

    
288
            // get the existing system metadata for the object
289
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
290

    
291
            // check for previous update
292
            // see: https://redmine.dataone.org/issues/3336
293
            Identifier existingObsoletedBy = existingSysMeta.getObsoletedBy();
294
            if (existingObsoletedBy != null) {
295
            	throw new InvalidRequest("1202", 
296
            			"The previous identifier has already been made obsolete by: " + existingObsoletedBy.getValue());
297
            }
298
            
299
            // add the newPid to the obsoletedBy list for the existing sysmeta
300
            existingSysMeta.setObsoletedBy(newPid);
301

    
302
            // then update the existing system metadata
303
            updateSystemMetadata(existingSysMeta);
304

    
305
            // prep the new system metadata, add pid to the affected lists
306
            sysmeta.setObsoletes(pid);
307
            //sysmeta.addDerivedFrom(pid);
308

    
309
            isScienceMetadata = isScienceMetadata(sysmeta);
310

    
311
            // do we have XML metadata or a data object?
312
            if (isScienceMetadata) {
313

    
314
                // update the science metadata XML document
315
                // TODO: handle non-XML metadata/data documents (like netCDF)
316
                // TODO: don't put objects into memory using stream to string
317
                String objectAsXML = "";
318
                try {
319
                    objectAsXML = IOUtils.toString(object, "UTF-8");
320
                    // give the old pid so we can calculate the new local id 
321
                    localId = insertOrUpdateDocument(objectAsXML, pid, session, "update");
322
                    // register the newPid and the generated localId
323
                    if (newPid != null) {
324
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
325

    
326
                    }
327

    
328
                } catch (IOException e) {
329
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
330
                    logMetacat.info(msg);
331
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
332

    
333
                }
334

    
335
            } else {
336

    
337
                // update the data object
338
                localId = insertDataObject(object, newPid, session);
339

    
340
            }
341

    
342
            // and insert the new system metadata
343
            insertSystemMetadata(sysmeta);
344

    
345
            // log the update event
346
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
347

    
348
        } else {
349
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
350
                    + " on the Member Node.");
351
        }
352

    
353
        return newPid;
354
    }
355

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

    
359
        // check for null session
360
        if (session == null) {
361
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
362
        }
363
        // set the submitter to match the certificate
364
        sysmeta.setSubmitter(session.getSubject());
365
        // set the originating node
366
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
367
        sysmeta.setOriginMemberNode(originMemberNode);
368
        sysmeta.setArchived(false);
369

    
370
        // set the dates
371
        Date now = Calendar.getInstance().getTime();
372
        sysmeta.setDateSysMetadataModified(now);
373
        sysmeta.setDateUploaded(now);
374
        
375
        // set the serial version
376
        sysmeta.setSerialVersion(BigInteger.ZERO);
377

    
378
        // check that we are not attempting to subvert versioning
379
        if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
380
            throw new InvalidSystemMetadata("1180", 
381
              "The supplied system metadata is invalid. " +
382
              "The obsoletes field cannot have a value when creating entries.");
383
        }
384
        
385
        if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
386
            throw new InvalidSystemMetadata("1180", 
387
              "The supplied system metadata is invalid. " +
388
              "The obsoletedBy field cannot have a value when creating entries.");
389
        }
390
        
391

    
392
        // call the shared impl
393
        return super.create(session, pid, object, sysmeta);
394
    }
395

    
396
    /**
397
     * Called by a Coordinating Node to request that the Member Node create a 
398
     * copy of the specified object by retrieving it from another Member 
399
     * Node and storing it locally so that it can be made accessible to 
400
     * the DataONE system.
401
     * 
402
     * @param session - the Session object containing the credentials for the Subject
403
     * @param sysmeta - Copy of the CN held system metadata for the object
404
     * @param sourceNode - A reference to node from which the content should be 
405
     *                     retrieved. The reference should be resolved by 
406
     *                     checking the CN node registry.
407
     * 
408
     * @return true if the replication succeeds
409
     * 
410
     * @throws ServiceFailure
411
     * @throws NotAuthorized
412
     * @throws NotImplemented
413
     * @throws UnsupportedType
414
     * @throws InsufficientResources
415
     * @throws InvalidRequest
416
     */
417
    @Override
418
    public boolean replicate(Session session, SystemMetadata sysmeta,
419
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
420
            NotAuthorized, InvalidRequest, InsufficientResources,
421
            UnsupportedType {
422

    
423
        if (session != null && sysmeta != null && sourceNode != null) {
424
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
425
                            "\tSession.Subject      = "                           +
426
                            session.getSubject().getValue() + "\n"                +
427
                            "\tidentifier           = "                           + 
428
                            sysmeta.getIdentifier().getValue()                    +
429
                            "\n" + "\tSource NodeReference ="                     +
430
                            sourceNode.getValue());
431
        }
432
        boolean result = false;
433
        String nodeIdStr = null;
434
        NodeReference nodeId = null;
435

    
436
        // get the referenced object
437
        Identifier pid = sysmeta.getIdentifier();
438

    
439
        // get from the membernode
440
        // TODO: switch credentials for the server retrieval?
441
        this.mn = D1Client.getMN(sourceNode);
442
        this.cn = D1Client.getCN();
443
        InputStream object = null;
444
        Session thisNodeSession = null;
445
        SystemMetadata localSystemMetadata = null;
446
        BaseException failure = null;
447
        String localId = null;
448
        
449
        // TODO: check credentials
450
        // cannot be called by public
451
        if (session == null || session.getSubject() == null) {
452
            String msg = "No session was provided to replicate identifier " +
453
            sysmeta.getIdentifier().getValue();
454
            logMetacat.info(msg);
455
            throw new NotAuthorized("2152", msg);
456
            
457
        }
458

    
459

    
460
        // get the local node id
461
        try {
462
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
463
            nodeId = new NodeReference();
464
            nodeId.setValue(nodeIdStr);
465

    
466
        } catch (PropertyNotFoundException e1) {
467
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
468
            failure = new ServiceFailure("2151", msg);
469
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
470
            logMetacat.error(msg);
471
            return true;
472

    
473
        }
474
        
475

    
476
        try {
477
            // do we already have a replica?
478
            try {
479
                localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
480
                // if we have a local id, get the local object
481
                try {
482
                    object = MetacatHandler.read(localId);
483
                } catch (Exception e) {
484
                	// NOTE: we may already know about this ID because it could be a data file described by a metadata file
485
                	// https://redmine.dataone.org/issues/2572
486
                	// TODO: fix this so that we don't prevent ourselves from getting replicas
487
                	
488
                    // let the CN know that the replication failed
489
                	logMetacat.warn("Object content not found on this node despite having localId: " + localId);
490
                	String msg = "Can't read the object bytes properly, replica is invalid.";
491
                    ServiceFailure serviceFailure = new ServiceFailure("2151", msg);
492
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, serviceFailure);
493
                    logMetacat.warn(msg);
494
                    throw serviceFailure;
495
                    
496
                }
497

    
498
            } catch (McdbDocNotFoundException e) {
499
                logMetacat.info("No replica found. Continuing.");
500
                
501
            }
502
            
503
            // no local replica, get a replica
504
            if ( object == null ) {
505
                // session should be null to use the default certificate
506
                // location set in the Certificate manager
507
                object = mn.getReplica(thisNodeSession, pid);
508
                logMetacat.info("MNodeService.getReplica() called for identifier "
509
                                + pid.getValue());
510

    
511
            }
512

    
513
        } catch (InvalidToken e) {            
514
            String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
515
            failure = new ServiceFailure("2151", msg);
516
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
517
            logMetacat.error(msg);
518
            throw new ServiceFailure("2151", msg);
519

    
520
        } catch (NotFound e) {
521
            String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
522
            failure = new ServiceFailure("2151", msg);
523
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
524
            logMetacat.error(msg);
525
            throw new ServiceFailure("2151", msg);
526

    
527
        }
528

    
529
        // verify checksum on the object, if supported
530
        if (object.markSupported()) {
531
            Checksum givenChecksum = sysmeta.getChecksum();
532
            Checksum computedChecksum = null;
533
            try {
534
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
535
                object.reset();
536

    
537
            } catch (Exception e) {
538
                String msg = "Error computing checksum on replica: " + e.getMessage();
539
                logMetacat.error(msg);
540
                ServiceFailure sf = new ServiceFailure("2151", msg);
541
                sf.initCause(e);
542
                throw sf;
543
            }
544
            if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
545
                logMetacat.error("Given    checksum for " + pid.getValue() + 
546
                    "is " + givenChecksum.getValue());
547
                logMetacat.error("Computed checksum for " + pid.getValue() + 
548
                    "is " + computedChecksum.getValue());
549
                throw new ServiceFailure("2151",
550
                        "Computed checksum does not match declared checksum");
551
            }
552
        }
553

    
554
        // add it to local store
555
        Identifier retPid;
556
        try {
557
            // skip the MN.create -- this mutates the system metadata and we don't want it to
558
            if ( localId == null ) {
559
                // TODO: this will fail if we already "know" about the identifier
560
            	// FIXME: see https://redmine.dataone.org/issues/2572
561
                retPid = super.create(session, pid, object, sysmeta);
562
                result = (retPid.getValue().equals(pid.getValue()));
563
            }
564
            
565
        } catch (Exception e) {
566
            String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
567
            failure = new ServiceFailure("2151", msg);
568
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
569
            logMetacat.error(msg);
570
            throw new ServiceFailure("2151", msg);
571
            
572
        }
573

    
574
        // finish by setting the replication status
575
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
576
        return result;
577

    
578
    }
579

    
580
    /**
581
     * Return the object identified by the given object identifier
582
     * 
583
     * @param session - the Session object containing the credentials for the Subject
584
     * @param pid - the object identifier for the given object
585
     * 
586
     * @return inputStream - the input stream of the given object
587
     * 
588
     * @throws InvalidToken
589
     * @throws ServiceFailure
590
     * @throws NotAuthorized
591
     * @throws InvalidRequest
592
     * @throws NotImplemented
593
     */
594
    @Override
595
    public InputStream get(Session session, Identifier pid) 
596
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
597

    
598
        return super.get(session, pid);
599

    
600
    }
601

    
602
    /**
603
     * Returns a Checksum for the specified object using an accepted hashing algorithm
604
     * 
605
     * @param session - the Session object containing the credentials for the Subject
606
     * @param pid - the object identifier for the given object
607
     * @param algorithm -  the name of an algorithm that will be used to compute 
608
     *                     a checksum of the bytes of the object
609
     * 
610
     * @return checksum - the checksum of the given object
611
     * 
612
     * @throws InvalidToken
613
     * @throws ServiceFailure
614
     * @throws NotAuthorized
615
     * @throws NotFound
616
     * @throws InvalidRequest
617
     * @throws NotImplemented
618
     */
619
    @Override
620
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
621
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
622
        InvalidRequest, NotImplemented {
623

    
624
        Checksum checksum = null;
625

    
626
        InputStream inputStream = get(session, pid);
627

    
628
        try {
629
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
630

    
631
        } catch (NoSuchAlgorithmException e) {
632
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
633
                    + e.getMessage());
634
        } catch (IOException e) {
635
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
636
                    + e.getMessage());
637
        }
638

    
639
        if (checksum == null) {
640
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
641
        }
642

    
643
        return checksum;
644
    }
645

    
646
    /**
647
     * Return the system metadata for a given object
648
     * 
649
     * @param session - the Session object containing the credentials for the Subject
650
     * @param pid - the object identifier for the given object
651
     * 
652
     * @return inputStream - the input stream of the given system metadata object
653
     * 
654
     * @throws InvalidToken
655
     * @throws ServiceFailure
656
     * @throws NotAuthorized
657
     * @throws NotFound
658
     * @throws InvalidRequest
659
     * @throws NotImplemented
660
     */
661
    @Override
662
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) 
663
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
664
        NotImplemented {
665

    
666
        return super.getSystemMetadata(session, pid);
667
    }
668

    
669
    /**
670
     * Retrieve the list of objects present on the MN that match the calling parameters
671
     * 
672
     * @param session - the Session object containing the credentials for the Subject
673
     * @param startTime - Specifies the beginning of the time range from which 
674
     *                    to return object (>=)
675
     * @param endTime - Specifies the beginning of the time range from which 
676
     *                  to return object (>=)
677
     * @param objectFormat - Restrict results to the specified object format
678
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
679
     * @param start - The zero-based index of the first value, relative to the 
680
     *                first record of the resultset that matches the parameters.
681
     * @param count - The maximum number of entries that should be returned in 
682
     *                the response. The Member Node may return less entries 
683
     *                than specified in this value.
684
     * 
685
     * @return objectList - the list of objects matching the criteria
686
     * 
687
     * @throws InvalidToken
688
     * @throws ServiceFailure
689
     * @throws NotAuthorized
690
     * @throws InvalidRequest
691
     * @throws NotImplemented
692
     */
693
    @Override
694
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
695
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
696

    
697
        ObjectList objectList = null;
698

    
699
        try {
700
        	// safeguard against large requests
701
            if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
702
            	count = MAXIMUM_DB_RECORD_COUNT;
703
            }
704
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
705
        } catch (Exception e) {
706
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
707
        }
708

    
709
        return objectList;
710
    }
711

    
712
    /**
713
     * Return a description of the node's capabilities and services.
714
     * 
715
     * @return node - the technical capabilities of the Member Node
716
     * 
717
     * @throws ServiceFailure
718
     * @throws NotAuthorized
719
     * @throws InvalidRequest
720
     * @throws NotImplemented
721
     */
722
    @Override
723
    public Node getCapabilities() 
724
        throws NotImplemented, ServiceFailure {
725

    
726
        String nodeName = null;
727
        String nodeId = null;
728
        String subject = null;
729
        String contactSubject = null;
730
        String nodeDesc = null;
731
        String nodeTypeString = null;
732
        NodeType nodeType = null;
733
        String mnCoreServiceVersion = null;
734
        String mnReadServiceVersion = null;
735
        String mnAuthorizationServiceVersion = null;
736
        String mnStorageServiceVersion = null;
737
        String mnReplicationServiceVersion = null;
738

    
739
        boolean nodeSynchronize = false;
740
        boolean nodeReplicate = false;
741
        boolean mnCoreServiceAvailable = false;
742
        boolean mnReadServiceAvailable = false;
743
        boolean mnAuthorizationServiceAvailable = false;
744
        boolean mnStorageServiceAvailable = false;
745
        boolean mnReplicationServiceAvailable = false;
746

    
747
        try {
748
            // get the properties of the node based on configuration information
749
            nodeName = PropertyService.getProperty("dataone.nodeName");
750
            nodeId = PropertyService.getProperty("dataone.nodeId");
751
            subject = PropertyService.getProperty("dataone.subject");
752
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
753
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
754
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
755
            nodeType = NodeType.convert(nodeTypeString);
756
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
757
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
758

    
759
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
760
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
761
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
762
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
763
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
764

    
765
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
766
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
767
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
768
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
769
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
770

    
771
            // Set the properties of the node based on configuration information and
772
            // calls to current status methods
773
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
774
            Node node = new Node();
775
            node.setBaseURL(serviceName + "/" + nodeTypeString);
776
            node.setDescription(nodeDesc);
777

    
778
            // set the node's health information
779
            node.setState(NodeState.UP);
780
            
781
            // set the ping response to the current value
782
            Ping canPing = new Ping();
783
            canPing.setSuccess(false);
784
            try {
785
            	Date pingDate = ping();
786
                canPing.setSuccess(pingDate != null);
787
            } catch (BaseException e) {
788
                e.printStackTrace();
789
                // guess it can't be pinged
790
            }
791
            
792
            node.setPing(canPing);
793

    
794
            NodeReference identifier = new NodeReference();
795
            identifier.setValue(nodeId);
796
            node.setIdentifier(identifier);
797
            Subject s = new Subject();
798
            s.setValue(subject);
799
            node.addSubject(s);
800
            Subject contact = new Subject();
801
            contact.setValue(contactSubject);
802
            node.addContactSubject(contact);
803
            node.setName(nodeName);
804
            node.setReplicate(nodeReplicate);
805
            node.setSynchronize(nodeSynchronize);
806

    
807
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
808
            Services services = new Services();
809

    
810
            Service sMNCore = new Service();
811
            sMNCore.setName("MNCore");
812
            sMNCore.setVersion(mnCoreServiceVersion);
813
            sMNCore.setAvailable(mnCoreServiceAvailable);
814

    
815
            Service sMNRead = new Service();
816
            sMNRead.setName("MNRead");
817
            sMNRead.setVersion(mnReadServiceVersion);
818
            sMNRead.setAvailable(mnReadServiceAvailable);
819

    
820
            Service sMNAuthorization = new Service();
821
            sMNAuthorization.setName("MNAuthorization");
822
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
823
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
824

    
825
            Service sMNStorage = new Service();
826
            sMNStorage.setName("MNStorage");
827
            sMNStorage.setVersion(mnStorageServiceVersion);
828
            sMNStorage.setAvailable(mnStorageServiceAvailable);
829

    
830
            Service sMNReplication = new Service();
831
            sMNReplication.setName("MNReplication");
832
            sMNReplication.setVersion(mnReplicationServiceVersion);
833
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
834

    
835
            services.addService(sMNRead);
836
            services.addService(sMNCore);
837
            services.addService(sMNAuthorization);
838
            services.addService(sMNStorage);
839
            services.addService(sMNReplication);
840
            node.setServices(services);
841

    
842
            // Set the schedule for synchronization
843
            Synchronization synchronization = new Synchronization();
844
            Schedule schedule = new Schedule();
845
            Date now = new Date();
846
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
847
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
848
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
849
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
850
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
851
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
852
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
853
            synchronization.setSchedule(schedule);
854
            synchronization.setLastHarvested(now);
855
            synchronization.setLastCompleteHarvest(now);
856
            node.setSynchronization(synchronization);
857

    
858
            node.setType(nodeType);
859
            return node;
860

    
861
        } catch (PropertyNotFoundException pnfe) {
862
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
863
            logMetacat.error(msg);
864
            throw new ServiceFailure("2162", msg);
865
        }
866
    }
867

    
868
    /**
869
     * Returns the number of operations that have been serviced by the node 
870
     * over time periods of one and 24 hours.
871
     * 
872
     * @param session - the Session object containing the credentials for the Subject
873
     * @param period - An ISO8601 compatible DateTime range specifying the time 
874
     *                 range for which to return operation statistics.
875
     * @param requestor - Limit to operations performed by given requestor identity.
876
     * @param event -  Enumerated value indicating the type of event being examined
877
     * @param format - Limit to events involving objects of the specified format
878
     * 
879
     * @return the desired log records
880
     * 
881
     * @throws InvalidToken
882
     * @throws ServiceFailure
883
     * @throws NotAuthorized
884
     * @throws InvalidRequest
885
     * @throws NotImplemented
886
     */
887
    public MonitorList getOperationStatistics(Session session, Date startTime, 
888
        Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
889
        throws NotImplemented, ServiceFailure, NotAuthorized, InsufficientResources, UnsupportedType {
890

    
891
        MonitorList monitorList = new MonitorList();
892

    
893
        try {
894

    
895
            // get log records first
896
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
897

    
898
            // TODO: aggregate by day or hour -- needs clarification
899
            int count = 1;
900
            for (LogEntry logEntry : logs.getLogEntryList()) {
901
                Identifier pid = logEntry.getIdentifier();
902
                Date logDate = logEntry.getDateLogged();
903
                // if we are filtering by format
904
                if (formatId != null) {
905
                    SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
906
                    if (!sysmeta.getFormatId().getValue().equals(formatId.getValue())) {
907
                        // does not match
908
                        continue;
909
                    }
910
                }
911
                MonitorInfo item = new MonitorInfo();
912
                item.setCount(count);
913
                item.setDate(new java.sql.Date(logDate.getTime()));
914
                monitorList.addMonitorInfo(item);
915

    
916
            }
917
        } catch (Exception e) {
918
            e.printStackTrace();
919
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
920
        }
921

    
922
        return monitorList;
923

    
924
    }
925

    
926
    /**
927
     * A callback method used by a CN to indicate to a MN that it cannot 
928
     * complete synchronization of the science metadata identified by pid.  Log
929
     * the event in the metacat event log.
930
     * 
931
     * @param session
932
     * @param syncFailed
933
     * 
934
     * @throws ServiceFailure
935
     * @throws NotAuthorized
936
     * @throws NotImplemented
937
     */
938
    @Override
939
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
940
        throws NotImplemented, ServiceFailure, NotAuthorized {
941

    
942
        String localId;
943
        Identifier pid;
944
        if ( syncFailed.getPid() != null ) {
945
            pid = new Identifier();
946
            pid.setValue(syncFailed.getPid());
947
            boolean allowed;
948
            
949
            //are we allowed? only CNs
950
            try {
951
                allowed = isAdminAuthorized(session);
952
                if ( !allowed ){
953
                    throw new NotAuthorized("2162", 
954
                            "Not allowed to call synchronizationFailed() on this node.");
955
                }
956
            } catch (InvalidToken e) {
957
                throw new NotAuthorized("2162", 
958
                        "Not allowed to call synchronizationFailed() on this node.");
959

    
960
            }
961
            
962
        } else {
963
            throw new ServiceFailure("2161", "The identifier cannot be null.");
964

    
965
        }
966
        
967
        try {
968
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
969
        } catch (McdbDocNotFoundException e) {
970
            throw new ServiceFailure("2161", "The identifier specified by " + 
971
                    syncFailed.getPid() + " was not found on this node.");
972

    
973
        }
974
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
975
        // method is changed to include the URL as a parameter
976
        logMetacat.debug("Synchronization for the object identified by " + 
977
                pid.getValue() + " failed from " + syncFailed.getNodeId() + 
978
                " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
979
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
980
        String principal = Constants.SUBJECT_PUBLIC;
981
        if (session != null && session.getSubject() != null) {
982
          principal = session.getSubject().getValue();
983
        }
984
        try {
985
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
986
        } catch (Exception e) {
987
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
988
        }
989
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
990
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
991
        return true;
992

    
993
    }
994

    
995
    /**
996
     * Essentially a get() but with different logging behavior
997
     */
998
    @Override
999
    public InputStream getReplica(Session session, Identifier pid) 
1000
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1001

    
1002
        logMetacat.info("MNodeService.getReplica() called.");
1003

    
1004
        // cannot be called by public
1005
        if (session == null) {
1006
        	throw new InvalidToken("2183", "No session was provided.");
1007
        }
1008
        
1009
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1010
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1011
             "\tIdentifier           = " + pid.getValue());
1012

    
1013
        InputStream inputStream = null; // bytes to be returned
1014
        handler = new MetacatHandler(new Timer());
1015
        boolean allowed = false;
1016
        String localId; // the metacat docid for the pid
1017

    
1018
        // get the local docid from Metacat
1019
        try {
1020
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1021
        } catch (McdbDocNotFoundException e) {
1022
            throw new ServiceFailure("2181", "The object specified by " + 
1023
                    pid.getValue() + " does not exist at this node.");
1024
            
1025
        }
1026

    
1027
        Subject targetNodeSubject = session.getSubject();
1028

    
1029
        // check for authorization to replicate, null session to act as this source MN
1030
        try {
1031
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1032
        } catch (InvalidToken e1) {
1033
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1034
                + e1.getMessage());
1035
            
1036
        } catch (NotFound e1) {
1037
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1038
                    + e1.getMessage());
1039

    
1040
        } catch (InvalidRequest e1) {
1041
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1042
                    + e1.getMessage());
1043

    
1044
        }
1045

    
1046
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1047
            " for identifier " + pid.getValue());
1048

    
1049
        // if the person is authorized, perform the read
1050
        if (allowed) {
1051
            try {
1052
                inputStream = MetacatHandler.read(localId);
1053
            } catch (Exception e) {
1054
                throw new ServiceFailure("1020", "The object specified by " + 
1055
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1056
            }
1057
        }
1058

    
1059
        // if we fail to set the input stream
1060
        if (inputStream == null) {
1061
            throw new ServiceFailure("2181", "The object specified by " + 
1062
                pid.getValue() + "does not exist at this node.");
1063
        }
1064

    
1065
        // log the replica event
1066
        String principal = null;
1067
        if (session.getSubject() != null) {
1068
            principal = session.getSubject().getValue();
1069
        }
1070
        EventLog.getInstance().log(request.getRemoteAddr(), 
1071
            request.getHeader("User-Agent"), principal, localId, "replicate");
1072

    
1073
        return inputStream;
1074
    }
1075

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

    
1211
	@Override
1212
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1213
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1214
			InvalidRequest {
1215
		
1216
		Identifier identifier = new Identifier();
1217
		
1218
		//throw new InvalidRequest("2193", "The scheme: '" + scheme + "' is not supported at this node.");
1219
		// handle default
1220
		if (fragment != null) {
1221
			// for now, just autogen with fragment
1222
			String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1223
			identifier.setValue(autogenId);
1224
			
1225
			// TODO: need to reserve this identifier locally so we don't give it out again!
1226
			if (false) {
1227
				try {
1228
					DBUtil dbutil = new DBUtil();
1229
					String lastDocid = dbutil.getMaxDocid(fragment);
1230
					String docidWithoutRev = DocumentUtil.getDocIdFromAccessionNumber(lastDocid);
1231
					String docidWithoutScope = docidWithoutRev.substring(docidWithoutRev.lastIndexOf("."));
1232
					int docid = Integer.valueOf(docidWithoutScope);
1233
					docid++;
1234
					identifier.setValue(docidWithoutRev);
1235
				} catch (SQLException e) {
1236
					throw new ServiceFailure("2191", "Error generating identifier: " + e.getMessage());
1237
				}
1238
			}
1239
		} else {
1240
			// autogen with no fragment
1241
			String autogenId = DocumentUtil.generateDocumentId(0);
1242
			identifier.setValue(autogenId);
1243
		}
1244
		
1245
		// for DOIs
1246
		if (scheme.equalsIgnoreCase("doi")) {
1247
			// prepend the doi shoulder to the generated identifier
1248
			String shoulder = null;
1249
			String ezidUsername = null;
1250
			String ezidPassword = null;
1251
			try {
1252
				shoulder = PropertyService.getProperty("guid.ezid.doishoulder.1");
1253
				ezidUsername = PropertyService.getProperty("guid.ezid.username");
1254
				ezidPassword = PropertyService.getProperty("guid.ezid.password");
1255
			} catch (PropertyNotFoundException e1) {
1256
				throw new InvalidRequest("2193", "DOI shoulder is not configured at this node.");
1257
			}
1258
			
1259
			// TODO: enter metadata about this identifier?
1260
			HashMap<String, String> metadata = null;
1261
			try {
1262
				// call the EZID service
1263
				EZIDService ezid = new EZIDService();
1264
				ezid.login(ezidUsername, ezidPassword);
1265
				String doi = null;
1266
				boolean mintDoi = true;
1267
				if (!mintDoi) {
1268
					doi = shoulder + identifier.getValue();
1269
					identifier.setValue(doi);
1270
					ezid.createIdentifier(identifier.getValue(), metadata);
1271
				} else {
1272
					doi = ezid.mintIdentifier(shoulder, metadata);
1273
				}
1274
				identifier.setValue(doi);
1275
				ezid.logout();
1276
			} catch (EZIDException e) {
1277
				throw new ServiceFailure("2191", "Error registering DOI identifier: " + e.getMessage());
1278
			}
1279
			
1280
		}
1281
		
1282
		// TODO: reserve the identifier with the CN. We can only do this when
1283
		// 1) the MN is part of a CN cluster
1284
		// 2) the request is from an authenticated user
1285
		
1286
		return identifier;
1287
	}
1288

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1411
	@Override
1412
	public QueryEngineDescription getQueryEngineDescription(String engine)
1413
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1414
			NotFound {
1415
		QueryEngineDescription qed = new QueryEngineDescription();
1416
		qed.setName(PATHQUERY);
1417
		qed.setQueryEngineVersion("1.0");
1418
		qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1419
		Vector<String> pathsForIndexing = null;
1420
		try {
1421
			pathsForIndexing = SystemUtil.getPathsForIndexing();
1422
		} catch (MetacatUtilException e) {
1423
			logMetacat.warn("Could not get index paths", e);
1424
		}
1425
		for (String fieldName: pathsForIndexing) {
1426
			QueryField field = new QueryField();
1427
			field.addDescription("Indexed field for path '" + fieldName + "'");
1428
			field.setName(fieldName);
1429
			field.setReturnable(true);
1430
			field.setSearchable(true);
1431
			field.setSortable(false);
1432
			// TODO: determine type and multivaluedness
1433
			field.setType(String.class.getName());
1434
			//field.setMultivalued(true);
1435
			qed.addQueryField(field);
1436
		}
1437
		return qed;
1438
	}
1439

    
1440
	@Override
1441
	public QueryEngineList listQueryEngines() throws InvalidToken,
1442
			ServiceFailure, NotAuthorized, NotImplemented {
1443
		QueryEngineList qel = new QueryEngineList();
1444
		// support pathquery initially
1445
		qel.addQueryEngine(PATHQUERY);
1446
		// TODO: implement solr-based query
1447
		//qel.addQueryEngine("solr");
1448
		return qel;
1449
	}
1450

    
1451
	@Override
1452
	public InputStream query(String engine, String query) throws InvalidToken,
1453
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1454
			NotFound {
1455
		if (engine.equals(PATHQUERY)) {
1456
			try {
1457
				DBQuery queryobj = new DBQuery();
1458
				String user = Constants.SUBJECT_PUBLIC;
1459
				String[] groups= null;
1460
				if (session != null) {
1461
					user = session.getSubject().getValue();
1462
					Set<Subject> subjects = AuthUtils.authorizedClientSubjects(session);
1463
					if (subjects != null) {
1464
						List<String> groupList = new ArrayList<String>();
1465
						for (Subject subject: subjects) {
1466
							groupList.add(subject.getValue());
1467
						}
1468
						groups = groupList.toArray(new String[0]);
1469
					}
1470
				}
1471
				String results = queryobj.performPathquery(query, user, groups);
1472
				return new ByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1473

    
1474
			} catch (Exception e) {
1475
				
1476
			}
1477
			
1478
		}
1479
		return null;
1480
	}
1481
    
1482
}
(3-3/5)