Project

General

Profile

1 6179 cjones
/**
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 7417 leinfelder
import java.io.ByteArrayInputStream;
27 6228 cjones
import java.io.IOException;
28 6179 cjones
import java.io.InputStream;
29 7021 leinfelder
import java.math.BigInteger;
30 6228 cjones
import java.security.NoSuchAlgorithmException;
31 7417 leinfelder
import java.util.ArrayList;
32 6525 leinfelder
import java.util.Calendar;
33 6179 cjones
import java.util.Date;
34 7680 tao
import java.util.HashSet;
35 6250 cjones
import java.util.List;
36 7417 leinfelder
import java.util.Set;
37 6389 leinfelder
import java.util.Timer;
38 7489 leinfelder
import java.util.UUID;
39 7418 leinfelder
import java.util.Vector;
40 6179 cjones
41 6542 leinfelder
import javax.servlet.http.HttpServletRequest;
42
43 6258 cjones
import org.apache.commons.io.IOUtils;
44 6179 cjones
import org.apache.log4j.Logger;
45 7620 tao
import org.apache.solr.client.solrj.SolrServerException;
46 6528 cjones
import org.dataone.client.CNode;
47 6332 leinfelder
import org.dataone.client.D1Client;
48
import org.dataone.client.MNode;
49 6552 leinfelder
import org.dataone.client.auth.CertificateManager;
50
import org.dataone.configuration.Settings;
51 6795 cjones
import org.dataone.service.exceptions.BaseException;
52 6179 cjones
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 6185 leinfelder
import org.dataone.service.exceptions.SynchronizationFailed;
62 6179 cjones
import org.dataone.service.exceptions.UnsupportedType;
63 6366 leinfelder
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 7417 leinfelder
import org.dataone.service.mn.v1.MNQuery;
69 6366 leinfelder
import org.dataone.service.types.v1.Checksum;
70 7144 leinfelder
import org.dataone.service.types.v1.DescribeResponse;
71 6366 leinfelder
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 6600 cjones
import org.dataone.service.types.v1.NodeList;
79 6366 leinfelder
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 6528 cjones
import org.dataone.service.types.v1.ReplicationStatus;
87 6366 leinfelder
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 7417 leinfelder
import org.dataone.service.types.v1.util.AuthUtils;
95 6366 leinfelder
import org.dataone.service.types.v1.util.ChecksumUtil;
96 7417 leinfelder
import org.dataone.service.types.v1_1.QueryEngineDescription;
97
import org.dataone.service.types.v1_1.QueryEngineList;
98 7418 leinfelder
import org.dataone.service.types.v1_1.QueryField;
99 6476 jones
import org.dataone.service.util.Constants;
100 6179 cjones
101 7448 leinfelder
import edu.ucsb.nceas.ezid.EZIDException;
102 7417 leinfelder
import edu.ucsb.nceas.metacat.DBQuery;
103 6234 cjones
import edu.ucsb.nceas.metacat.EventLog;
104 6230 cjones
import edu.ucsb.nceas.metacat.IdentifierManager;
105 6234 cjones
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
106 7417 leinfelder
import edu.ucsb.nceas.metacat.MetaCatServlet;
107 6389 leinfelder
import edu.ucsb.nceas.metacat.MetacatHandler;
108 7772 tao
109
import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines;
110 7757 leinfelder
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
111 6648 leinfelder
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
112 7662 tao
import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler;
113 7620 tao
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
114 6340 cjones
import edu.ucsb.nceas.metacat.properties.PropertyService;
115 7418 leinfelder
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
116 7441 leinfelder
import edu.ucsb.nceas.metacat.util.DocumentUtil;
117 6542 leinfelder
import edu.ucsb.nceas.metacat.util.SystemUtil;
118 6340 cjones
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
119 6230 cjones
120 6179 cjones
/**
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 6288 cjones
 *
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 6179 cjones
 */
147 6599 cjones
public class MNodeService extends D1NodeService
148 7417 leinfelder
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage, MNQuery {
149 6179 cjones
150 7772 tao
    //private static final String PATHQUERY = "pathquery";
151 7489 leinfelder
	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 7418 leinfelder
155
	/* the logger instance */
156 6475 jones
    private Logger logMetacat = null;
157 6795 cjones
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 6241 cjones
164 6795 cjones
165 6475 jones
    /**
166
     * Singleton accessor to get an instance of MNodeService.
167
     *
168
     * @return instance - the instance of MNodeService
169
     */
170 6542 leinfelder
    public static MNodeService getInstance(HttpServletRequest request) {
171
        return new MNodeService(request);
172 6179 cjones
    }
173
174 6475 jones
    /**
175
     * Constructor, private for singleton access
176
     */
177 6542 leinfelder
    private MNodeService(HttpServletRequest request) {
178
        super(request);
179 6475 jones
        logMetacat = Logger.getLogger(MNodeService.class);
180 6552 leinfelder
181
        // set the Member Node certificate file location
182
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
183 6310 cjones
    }
184 6475 jones
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 6610 cjones
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
204 6475 jones
205 7162 leinfelder
    	// only admin of  the MN or the CN is allowed a full delete
206
        boolean allowed = false;
207 7330 leinfelder
        allowed = isAdminAuthorized(session);
208 7162 leinfelder
        if (!allowed) {
209 7245 cjones
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
210 7162 leinfelder
        }
211
212 7077 leinfelder
    	// defer to superclass implementation
213
        return super.delete(session, pid);
214 6250 cjones
    }
215
216 6475 jones
    /**
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 6575 cjones
    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 6250 cjones
247 6475 jones
        String localId = null;
248
        boolean allowed = false;
249
        boolean isScienceMetadata = false;
250 6645 leinfelder
251
        if (session == null) {
252
        	throw new InvalidToken("1210", "No session has been provided");
253
        }
254 6475 jones
        Subject subject = session.getSubject();
255
256 7315 leinfelder
        // verify the pid is valid format
257 7318 leinfelder
        if (!isValidIdentifier(pid)) {
258 7315 leinfelder
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
259 6475 jones
        }
260
261
        // check for the existing identifier
262
        try {
263
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
264 6575 cjones
265 6475 jones
        } catch (McdbDocNotFoundException e) {
266 6575 cjones
            throw new InvalidRequest("1202", "The object with the provided " +
267
                "identifier was not found.");
268
269 6475 jones
        }
270 6518 leinfelder
271 6521 leinfelder
        // set the originating node
272
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
273
        sysmeta.setOriginMemberNode(originMemberNode);
274
275 6518 leinfelder
        // set the submitter to match the certificate
276
        sysmeta.setSubmitter(subject);
277 6525 leinfelder
        // set the dates
278
        Date now = Calendar.getInstance().getTime();
279 6575 cjones
        sysmeta.setDateSysMetadataModified(now);
280
        sysmeta.setDateUploaded(now);
281 7486 leinfelder
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 6475 jones
288
        // does the subject have WRITE ( == update) priveleges on the pid?
289
        allowed = isAuthorized(session, pid, Permission.WRITE);
290
291
        if (allowed) {
292 6649 leinfelder
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 6475 jones
301
            // get the existing system metadata for the object
302
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
303
304 7400 leinfelder
            // 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 6475 jones
            // 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 7443 leinfelder
                    // give the old pid so we can calculate the new local id
334
                    localId = insertOrUpdateDocument(objectAsXML, pid, session, "update");
335 6475 jones
                    // 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 6542 leinfelder
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
360 7507 leinfelder
361
            // attempt to register the identifier - it checks if it is a doi
362
            try {
363 7512 leinfelder
    			DOIService.getInstance().registerDOI(sysmeta);
364 7507 leinfelder
    		} catch (EZIDException e) {
365
                throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
366
    		}
367 6475 jones
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 6250 cjones
    }
375 6254 cjones
376 6475 jones
    public Identifier create(Session session, Identifier pid, InputStream object, SystemMetadata sysmeta) throws InvalidToken, ServiceFailure, NotAuthorized,
377
            IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, InvalidRequest {
378 6250 cjones
379 6916 cjones
        // check for null session
380 6530 leinfelder
        if (session == null) {
381 6575 cjones
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
382 6530 leinfelder
        }
383 6518 leinfelder
        // set the submitter to match the certificate
384
        sysmeta.setSubmitter(session.getSubject());
385 6520 leinfelder
        // set the originating node
386
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
387
        sysmeta.setOriginMemberNode(originMemberNode);
388 6916 cjones
        sysmeta.setArchived(false);
389
390 6525 leinfelder
        // set the dates
391
        Date now = Calendar.getInstance().getTime();
392 6916 cjones
        sysmeta.setDateSysMetadataModified(now);
393
        sysmeta.setDateUploaded(now);
394 7021 leinfelder
395
        // set the serial version
396
        sysmeta.setSerialVersion(BigInteger.ZERO);
397 7083 cjones
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 7021 leinfelder
405 7083 cjones
        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 6518 leinfelder
        // call the shared impl
412 7507 leinfelder
        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 7512 leinfelder
			DOIService.getInstance().registerDOI(sysmeta);
417 7507 leinfelder
		} catch (EZIDException e) {
418 7510 leinfelder
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
419
			sf.initCause(e);
420
            throw sf;
421 7507 leinfelder
		}
422
423
        // return
424
		return resultPid ;
425 6475 jones
    }
426 6250 cjones
427 6475 jones
    /**
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 6786 cjones
    public boolean replicate(Session session, SystemMetadata sysmeta,
450
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
451
            NotAuthorized, InvalidRequest, InsufficientResources,
452
            UnsupportedType {
453
454 6875 cjones
        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 7082 cjones
                            "\tidentifier           = "                           +
459
                            sysmeta.getIdentifier().getValue()                    +
460 6875 cjones
                            "\n" + "\tSource NodeReference ="                     +
461
                            sourceNode.getValue());
462
        }
463 6475 jones
        boolean result = false;
464 6786 cjones
        String nodeIdStr = null;
465 6651 cjones
        NodeReference nodeId = null;
466 6786 cjones
467 6795 cjones
        // 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 6818 cjones
        String localId = null;
479
480 6795 cjones
        // TODO: check credentials
481
        // cannot be called by public
482 7063 leinfelder
        if (session == null || session.getSubject() == null) {
483 7082 cjones
            String msg = "No session was provided to replicate identifier " +
484
            sysmeta.getIdentifier().getValue();
485 6795 cjones
            logMetacat.info(msg);
486 7192 cjones
            throw new NotAuthorized("2152", msg);
487
488 6795 cjones
        }
489
490
491 6651 cjones
        // get the local node id
492
        try {
493 7030 cjones
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
494 6651 cjones
            nodeId = new NodeReference();
495
            nodeId.setValue(nodeIdStr);
496 6786 cjones
497 6651 cjones
        } catch (PropertyNotFoundException e1) {
498 7030 cjones
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
499 6795 cjones
            failure = new ServiceFailure("2151", msg);
500
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
501
            logMetacat.error(msg);
502
            return true;
503 6786 cjones
504 6651 cjones
        }
505 6795 cjones
506 6475 jones
507
        try {
508 6786 cjones
            // do we already have a replica?
509
            try {
510 6818 cjones
                localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
511 6822 cjones
                // if we have a local id, get the local object
512
                try {
513
                    object = MetacatHandler.read(localId);
514
                } catch (Exception e) {
515 7127 leinfelder
                	// 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 6822 cjones
                    // let the CN know that the replication failed
520 7127 leinfelder
                	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 7113 leinfelder
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, serviceFailure);
524
                    logMetacat.warn(msg);
525 6822 cjones
                    throw serviceFailure;
526
527
                }
528
529 6817 cjones
            } catch (McdbDocNotFoundException e) {
530 6818 cjones
                logMetacat.info("No replica found. Continuing.");
531 6817 cjones
532 6819 cjones
            }
533
534 6786 cjones
            // no local replica, get a replica
535 6819 cjones
            if ( object == null ) {
536 6786 cjones
                // session should be null to use the default certificate
537
                // location set in the Certificate manager
538
                object = mn.getReplica(thisNodeSession, pid);
539 7082 cjones
                logMetacat.info("MNodeService.getReplica() called for identifier "
540 6786 cjones
                                + pid.getValue());
541
542
            }
543
544 6795 cjones
        } 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 6786 cjones
551 6475 jones
        } catch (NotFound e) {
552 6795 cjones
            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 6786 cjones
558 6475 jones
        }
559
560 6693 leinfelder
        // verify checksum on the object, if supported
561
        if (object.markSupported()) {
562 6786 cjones
            Checksum givenChecksum = sysmeta.getChecksum();
563
            Checksum computedChecksum = null;
564
            try {
565 7127 leinfelder
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
566 6786 cjones
                object.reset();
567 6948 cjones
568 6786 cjones
            } catch (Exception e) {
569 7127 leinfelder
                String msg = "Error computing checksum on replica: " + e.getMessage();
570 7113 leinfelder
                logMetacat.error(msg);
571 6786 cjones
                ServiceFailure sf = new ServiceFailure("2151", msg);
572
                sf.initCause(e);
573
                throw sf;
574
            }
575 6948 cjones
            if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
576 7113 leinfelder
                logMetacat.error("Given    checksum for " + pid.getValue() +
577 6948 cjones
                    "is " + givenChecksum.getValue());
578 7113 leinfelder
                logMetacat.error("Computed checksum for " + pid.getValue() +
579 6948 cjones
                    "is " + computedChecksum.getValue());
580 6786 cjones
                throw new ServiceFailure("2151",
581
                        "Computed checksum does not match declared checksum");
582
            }
583 6693 leinfelder
        }
584 6786 cjones
585 6475 jones
        // add it to local store
586
        Identifier retPid;
587
        try {
588 7127 leinfelder
            // skip the MN.create -- this mutates the system metadata and we don't want it to
589 6818 cjones
            if ( localId == null ) {
590 7127 leinfelder
                // TODO: this will fail if we already "know" about the identifier
591
            	// FIXME: see https://redmine.dataone.org/issues/2572
592 6818 cjones
                retPid = super.create(session, pid, object, sysmeta);
593
                result = (retPid.getValue().equals(pid.getValue()));
594
            }
595 6795 cjones
596 7125 leinfelder
        } catch (Exception e) {
597 7126 leinfelder
            String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
598 7125 leinfelder
            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 6475 jones
        }
604
605 6795 cjones
        // finish by setting the replication status
606
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
607 6475 jones
        return result;
608
609 6250 cjones
    }
610 6179 cjones
611 6475 jones
    /**
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 6610 cjones
    public InputStream get(Session session, Identifier pid)
627
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
628 6258 cjones
629 6475 jones
        return super.get(session, pid);
630 6258 cjones
631 6259 cjones
    }
632 6258 cjones
633 6475 jones
    /**
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 6610 cjones
    public Checksum getChecksum(Session session, Identifier pid, String algorithm)
652
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
653
        InvalidRequest, NotImplemented {
654 6258 cjones
655 6475 jones
        Checksum checksum = null;
656
657
        InputStream inputStream = get(session, pid);
658
659 6259 cjones
        try {
660 6475 jones
            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 6259 cjones
        } catch (IOException e) {
666 6475 jones
            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 6259 cjones
        }
669 6382 cjones
670 6475 jones
        if (checksum == null) {
671
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
672
        }
673 6258 cjones
674 6475 jones
        return checksum;
675 6259 cjones
    }
676 6179 cjones
677 6475 jones
    /**
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 6610 cjones
    public SystemMetadata getSystemMetadata(Session session, Identifier pid)
694
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
695
        NotImplemented {
696 6341 leinfelder
697 6475 jones
        return super.getSystemMetadata(session, pid);
698
    }
699 6341 leinfelder
700 6475 jones
    /**
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 6179 cjones
728 6475 jones
        ObjectList objectList = null;
729 6332 leinfelder
730 6475 jones
        try {
731 7439 leinfelder
        	// safeguard against large requests
732
            if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
733
            	count = MAXIMUM_DB_RECORD_COUNT;
734
            }
735 6475 jones
            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 6332 leinfelder
740 6475 jones
        return objectList;
741 6229 cjones
    }
742 6179 cjones
743 6475 jones
    /**
744 6476 jones
     * Return a description of the node's capabilities and services.
745 6475 jones
     *
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 6610 cjones
    public Node getCapabilities()
755
        throws NotImplemented, ServiceFailure {
756 6179 cjones
757 6475 jones
        String nodeName = null;
758
        String nodeId = null;
759 6492 jones
        String subject = null;
760 6938 cjones
        String contactSubject = null;
761 6475 jones
        String nodeDesc = null;
762 6476 jones
        String nodeTypeString = null;
763
        NodeType nodeType = null;
764 6475 jones
        String mnCoreServiceVersion = null;
765
        String mnReadServiceVersion = null;
766
        String mnAuthorizationServiceVersion = null;
767
        String mnStorageServiceVersion = null;
768
        String mnReplicationServiceVersion = null;
769 6179 cjones
770 6475 jones
        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 6179 cjones
778 6475 jones
        try {
779
            // get the properties of the node based on configuration information
780 6492 jones
            nodeName = PropertyService.getProperty("dataone.nodeName");
781 7030 cjones
            nodeId = PropertyService.getProperty("dataone.nodeId");
782 6492 jones
            subject = PropertyService.getProperty("dataone.subject");
783 6938 cjones
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
784 6475 jones
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
785 6476 jones
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
786
            nodeType = NodeType.convert(nodeTypeString);
787 6475 jones
            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 6476 jones
            // Set the properties of the node based on configuration information and
803
            // calls to current status methods
804 7286 leinfelder
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
805 6476 jones
            Node node = new Node();
806 6542 leinfelder
            node.setBaseURL(serviceName + "/" + nodeTypeString);
807 6476 jones
            node.setDescription(nodeDesc);
808 6475 jones
809 6476 jones
            // 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 6803 leinfelder
            	Date pingDate = ping();
817
                canPing.setSuccess(pingDate != null);
818
            } catch (BaseException e) {
819 6476 jones
                e.printStackTrace();
820 6803 leinfelder
                // guess it can't be pinged
821 6476 jones
            }
822 6610 cjones
823 6476 jones
            node.setPing(canPing);
824 6475 jones
825 6476 jones
            NodeReference identifier = new NodeReference();
826
            identifier.setValue(nodeId);
827
            node.setIdentifier(identifier);
828 6492 jones
            Subject s = new Subject();
829
            s.setValue(subject);
830
            node.addSubject(s);
831 6938 cjones
            Subject contact = new Subject();
832
            contact.setValue(contactSubject);
833
            node.addContactSubject(contact);
834 6476 jones
            node.setName(nodeName);
835
            node.setReplicate(nodeReplicate);
836
            node.setSynchronize(nodeSynchronize);
837 6475 jones
838 6476 jones
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
839
            Services services = new Services();
840 6475 jones
841 6476 jones
            Service sMNCore = new Service();
842
            sMNCore.setName("MNCore");
843
            sMNCore.setVersion(mnCoreServiceVersion);
844
            sMNCore.setAvailable(mnCoreServiceAvailable);
845 6475 jones
846 6476 jones
            Service sMNRead = new Service();
847
            sMNRead.setName("MNRead");
848
            sMNRead.setVersion(mnReadServiceVersion);
849
            sMNRead.setAvailable(mnReadServiceAvailable);
850 6475 jones
851 6476 jones
            Service sMNAuthorization = new Service();
852
            sMNAuthorization.setName("MNAuthorization");
853
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
854
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
855 6475 jones
856 6476 jones
            Service sMNStorage = new Service();
857
            sMNStorage.setName("MNStorage");
858
            sMNStorage.setVersion(mnStorageServiceVersion);
859
            sMNStorage.setAvailable(mnStorageServiceAvailable);
860 6475 jones
861 6476 jones
            Service sMNReplication = new Service();
862
            sMNReplication.setName("MNReplication");
863
            sMNReplication.setVersion(mnReplicationServiceVersion);
864
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
865 6475 jones
866 6476 jones
            services.addService(sMNRead);
867
            services.addService(sMNCore);
868
            services.addService(sMNAuthorization);
869
            services.addService(sMNStorage);
870
            services.addService(sMNReplication);
871
            node.setServices(services);
872 6475 jones
873 6476 jones
            // Set the schedule for synchronization
874
            Synchronization synchronization = new Synchronization();
875
            Schedule schedule = new Schedule();
876
            Date now = new Date();
877 6689 leinfelder
            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 6476 jones
            synchronization.setSchedule(schedule);
885
            synchronization.setLastHarvested(now);
886
            synchronization.setLastCompleteHarvest(now);
887
            node.setSynchronization(synchronization);
888 6475 jones
889 6476 jones
            node.setType(nodeType);
890
            return node;
891 6475 jones
892 6476 jones
        } 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 6228 cjones
    }
898 6179 cjones
899 6475 jones
    /**
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 6610 cjones
    public MonitorList getOperationStatistics(Session session, Date startTime,
919
        Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
920
        throws NotImplemented, ServiceFailure, NotAuthorized, InsufficientResources, UnsupportedType {
921 6179 cjones
922 6475 jones
        MonitorList monitorList = new MonitorList();
923 6179 cjones
924 6475 jones
        try {
925 6179 cjones
926 6475 jones
            // get log records first
927 7101 leinfelder
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
928 6179 cjones
929 6475 jones
            // 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 6692 leinfelder
                    SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
937 6561 leinfelder
                    if (!sysmeta.getFormatId().getValue().equals(formatId.getValue())) {
938 6475 jones
                        // 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 6179 cjones
947 6475 jones
            }
948
        } catch (Exception e) {
949
            e.printStackTrace();
950
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
951
        }
952 6345 cjones
953 6475 jones
        return monitorList;
954 6345 cjones
955 6340 cjones
    }
956
957 6475 jones
    /**
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 6991 leinfelder
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed)
971 6610 cjones
        throws NotImplemented, ServiceFailure, NotAuthorized {
972 6179 cjones
973 6475 jones
        String localId;
974 7075 cjones
        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 7142 leinfelder
                allowed = isAdminAuthorized(session);
983 7075 cjones
                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 6331 leinfelder
991 7075 cjones
            }
992
993
        } else {
994
            throw new ServiceFailure("2161", "The identifier cannot be null.");
995
996
        }
997
998 6475 jones
        try {
999 7075 cjones
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1000 6475 jones
        } catch (McdbDocNotFoundException e) {
1001 7075 cjones
            throw new ServiceFailure("2161", "The identifier specified by " +
1002
                    syncFailed.getPid() + " was not found on this node.");
1003 6179 cjones
1004 6475 jones
        }
1005
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1006
        // method is changed to include the URL as a parameter
1007 7075 cjones
        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 6475 jones
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1011 6532 leinfelder
        String principal = Constants.SUBJECT_PUBLIC;
1012 6506 leinfelder
        if (session != null && session.getSubject() != null) {
1013 6575 cjones
          principal = session.getSubject().getValue();
1014 6506 leinfelder
        }
1015
        try {
1016 6575 cjones
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1017 6506 leinfelder
        } catch (Exception e) {
1018 7075 cjones
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1019 6991 leinfelder
        }
1020 6475 jones
        //EventLog.getInstance().log("CN URL WILL GO HERE",
1021
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1022 6991 leinfelder
        return true;
1023 6179 cjones
1024 6260 cjones
    }
1025
1026 6475 jones
    /**
1027
     * Essentially a get() but with different logging behavior
1028
     */
1029
    @Override
1030 6540 cjones
    public InputStream getReplica(Session session, Identifier pid)
1031 6653 leinfelder
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1032 6179 cjones
1033 6540 cjones
        logMetacat.info("MNodeService.getReplica() called.");
1034
1035 6653 leinfelder
        // cannot be called by public
1036
        if (session == null) {
1037
        	throw new InvalidToken("2183", "No session was provided.");
1038
        }
1039
1040 6631 cjones
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1041
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1042
             "\tIdentifier           = " + pid.getValue());
1043
1044 6475 jones
        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 6179 cjones
1049 6475 jones
        // get the local docid from Metacat
1050
        try {
1051
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1052
        } catch (McdbDocNotFoundException e) {
1053 6610 cjones
            throw new ServiceFailure("2181", "The object specified by " +
1054
                    pid.getValue() + " does not exist at this node.");
1055
1056 6475 jones
        }
1057 6234 cjones
1058 6552 leinfelder
        Subject targetNodeSubject = session.getSubject();
1059 6185 leinfelder
1060 6552 leinfelder
        // check for authorization to replicate, null session to act as this source MN
1061 6610 cjones
        try {
1062 6777 leinfelder
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1063 6610 cjones
        } 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 6384 cjones
1071 6610 cjones
        } catch (InvalidRequest e1) {
1072
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
1073
                    + e1.getMessage());
1074
1075
        }
1076
1077 6540 cjones
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1078
            " for identifier " + pid.getValue());
1079
1080 6475 jones
        // if the person is authorized, perform the read
1081
        if (allowed) {
1082
            try {
1083 6986 jones
                inputStream = MetacatHandler.read(localId);
1084 6475 jones
            } catch (Exception e) {
1085 6610 cjones
                throw new ServiceFailure("1020", "The object specified by " +
1086
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1087 6475 jones
            }
1088
        }
1089 6384 cjones
1090 6475 jones
        // if we fail to set the input stream
1091
        if (inputStream == null) {
1092 6610 cjones
            throw new ServiceFailure("2181", "The object specified by " +
1093
                pid.getValue() + "does not exist at this node.");
1094 6475 jones
        }
1095
1096
        // log the replica event
1097
        String principal = null;
1098
        if (session.getSubject() != null) {
1099
            principal = session.getSubject().getValue();
1100
        }
1101 6576 cjones
        EventLog.getInstance().log(request.getRemoteAddr(),
1102
            request.getHeader("User-Agent"), principal, localId, "replicate");
1103 6475 jones
1104
        return inputStream;
1105
    }
1106
1107 6573 cjones
    /**
1108 6600 cjones
     * A method to notify the Member Node that the authoritative copy of
1109 6599 cjones
     * 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 6991 leinfelder
    public boolean systemMetadataChanged(Session session, Identifier pid,
1123 6599 cjones
        long serialVersion, Date dateSysMetaLastModified)
1124
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1125
        InvalidToken {
1126
1127 7600 cjones
        // cannot be called by public
1128
        if (session == null) {
1129
        	throw new InvalidToken("2183", "No session was provided.");
1130
        }
1131
1132 6600 cjones
        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 6692 leinfelder
            currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1166
1167
        } catch (RuntimeException e) {
1168 6600 cjones
            String msg = "SystemMetadata for pid " + pid.getValue() +
1169 6692 leinfelder
              " couldn't be updated because it couldn't be found locally: " +
1170 6600 cjones
              e.getMessage();
1171 6692 leinfelder
            logMetacat.error(msg);
1172
            ServiceFailure sf = new ServiceFailure("1333", msg);
1173
            sf.initCause(e);
1174
            throw sf;
1175 6600 cjones
        }
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 6692 leinfelder
            	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 6600 cjones
            }
1190 6692 leinfelder
1191 6600 cjones
            // update the local copy of system metadata for the pid
1192
            try {
1193 6692 leinfelder
                HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1194 7812 leinfelder
                // submit for indexing
1195
                HazelcastService.getInstance().getIndexQueue().add(newSysMeta);
1196 6600 cjones
                logMetacat.info("Updated local copy of system metadata for pid " +
1197
                    pid.getValue() + " after change notification from the CN.");
1198
1199 6692 leinfelder
            } catch (RuntimeException e) {
1200 6600 cjones
                String msg = "SystemMetadata for pid " + pid.getValue() +
1201 6692 leinfelder
                  " couldn't be updated: " +
1202 6600 cjones
                  e.getMessage();
1203 6692 leinfelder
                logMetacat.error(msg);
1204
                ServiceFailure sf = new ServiceFailure("1333", msg);
1205
                sf.initCause(e);
1206
                throw sf;
1207 6600 cjones
            }
1208
        }
1209
1210 6991 leinfelder
        return true;
1211
1212 6599 cjones
    }
1213
1214 6795 cjones
    /*
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 7091 leinfelder
        	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 6795 cjones
1240
        } catch (NotFound e) {
1241 7091 leinfelder
        	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 6795 cjones
1246
        }
1247
    }
1248 7099 leinfelder
1249
	@Override
1250 7441 leinfelder
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1251 7099 leinfelder
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1252
			InvalidRequest {
1253 7448 leinfelder
1254 7441 leinfelder
		Identifier identifier = new Identifier();
1255 7448 leinfelder
1256 7489 leinfelder
		// 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 7512 leinfelder
			// generate a DOI
1263 7448 leinfelder
			try {
1264 7512 leinfelder
				identifier = DOIService.getInstance().generateDOI();
1265 7448 leinfelder
			} catch (EZIDException e) {
1266 7512 leinfelder
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1267
				sf.initCause(e);
1268
				throw sf;
1269 7448 leinfelder
			}
1270 7489 leinfelder
		} 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 7448 leinfelder
		}
1282
1283 7441 leinfelder
		// 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 7099 leinfelder
	}
1289 7144 leinfelder
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 7441 leinfelder
	public Identifier generateIdentifier(String scheme, String fragment)
1396 7144 leinfelder
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1397
			InvalidRequest {
1398
1399 7441 leinfelder
		return generateIdentifier(null, scheme, fragment);
1400 7144 leinfelder
	}
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 7417 leinfelder
1412
	@Override
1413
	public QueryEngineDescription getQueryEngineDescription(String engine)
1414
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1415
			NotFound {
1416 7772 tao
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1417 7634 tao
	        QueryEngineDescription qed = new QueryEngineDescription();
1418 7772 tao
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1419 7634 tao
	        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 7772 tao
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1441 7781 tao
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1442 7772 tao
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1443 7781 tao
            }
1444 7634 tao
	        try {
1445 7662 tao
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1446 7634 tao
	            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 7417 leinfelder
	}
1456
1457
	@Override
1458
	public QueryEngineList listQueryEngines() throws InvalidToken,
1459
			ServiceFailure, NotAuthorized, NotImplemented {
1460
		QueryEngineList qel = new QueryEngineList();
1461 7781 tao
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1462
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1463
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1464 7772 tao
		for(String name : enables) {
1465
		    qel.addQueryEngine(name);
1466 7781 tao
		}
1467 7417 leinfelder
		return qel;
1468
	}
1469
1470
	@Override
1471
	public InputStream query(String engine, String query) throws InvalidToken,
1472
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1473
			NotFound {
1474 7648 tao
	    String user = Constants.SUBJECT_PUBLIC;
1475
        String[] groups= null;
1476 7680 tao
        Set<Subject> subjects = null;
1477 7648 tao
        if (session != null) {
1478
            user = session.getSubject().getValue();
1479 7680 tao
            subjects = AuthUtils.authorizedClientSubjects(session);
1480 7648 tao
            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 7680 tao
        } else {
1488
            //add the public user subject to the set
1489
            Subject subject = new Subject();
1490
            subject.setValue(Constants.SUBJECT_PUBLIC);
1491 7757 leinfelder
            subjects = new HashSet<Subject>();
1492 7680 tao
            subjects.add(subject);
1493 7648 tao
        }
1494 7680 tao
        //System.out.println("====== user is "+user);
1495
        //System.out.println("====== groups are "+groups);
1496 7772 tao
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1497 7417 leinfelder
			try {
1498
				DBQuery queryobj = new DBQuery();
1499 7648 tao
1500 7417 leinfelder
				String results = queryobj.performPathquery(query, user, groups);
1501 7757 leinfelder
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1502
				ctbais.setContentType("text/xml");
1503
				return ctbais;
1504 7417 leinfelder
1505
			} catch (Exception e) {
1506 7757 leinfelder
				throw new ServiceFailure("Pathquery error", e.getMessage());
1507 7417 leinfelder
			}
1508
1509 7772 tao
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1510 7781 tao
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1511 7772 tao
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1512 7781 tao
		    }
1513 7634 tao
		    logMetacat.info("The query is ==================================== \n"+query);
1514 7620 tao
		    try {
1515 7634 tao
1516 7680 tao
                return MetacatSolrIndex.getInstance().query(query, subjects);
1517 7620 tao
            } catch (Exception e) {
1518
                // TODO Auto-generated catch block
1519
                throw new ServiceFailure("Solr server error", e.getMessage());
1520
            }
1521 7417 leinfelder
		}
1522
		return null;
1523
	}
1524 6795 cjones
1525 6179 cjones
}