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 6228 cjones
import java.io.IOException;
27 6179 cjones
import java.io.InputStream;
28 6228 cjones
import java.security.NoSuchAlgorithmException;
29 6250 cjones
import java.sql.SQLException;
30 6600 cjones
import java.util.ArrayList;
31 6525 leinfelder
import java.util.Calendar;
32 6179 cjones
import java.util.Date;
33 6250 cjones
import java.util.List;
34 6389 leinfelder
import java.util.Timer;
35 6179 cjones
36 6542 leinfelder
import javax.servlet.http.HttpServletRequest;
37
38 6258 cjones
import org.apache.commons.io.IOUtils;
39 6179 cjones
import org.apache.log4j.Logger;
40 6528 cjones
import org.dataone.client.CNode;
41 6332 leinfelder
import org.dataone.client.D1Client;
42
import org.dataone.client.MNode;
43 6552 leinfelder
import org.dataone.client.auth.CertificateManager;
44
import org.dataone.configuration.Settings;
45 6179 cjones
import org.dataone.service.exceptions.IdentifierNotUnique;
46
import org.dataone.service.exceptions.InsufficientResources;
47
import org.dataone.service.exceptions.InvalidRequest;
48
import org.dataone.service.exceptions.InvalidSystemMetadata;
49
import org.dataone.service.exceptions.InvalidToken;
50
import org.dataone.service.exceptions.NotAuthorized;
51
import org.dataone.service.exceptions.NotFound;
52
import org.dataone.service.exceptions.NotImplemented;
53
import org.dataone.service.exceptions.ServiceFailure;
54 6185 leinfelder
import org.dataone.service.exceptions.SynchronizationFailed;
55 6179 cjones
import org.dataone.service.exceptions.UnsupportedType;
56 6622 leinfelder
import org.dataone.service.exceptions.VersionMismatch;
57 6366 leinfelder
import org.dataone.service.mn.tier1.v1.MNCore;
58
import org.dataone.service.mn.tier1.v1.MNRead;
59
import org.dataone.service.mn.tier2.v1.MNAuthorization;
60
import org.dataone.service.mn.tier3.v1.MNStorage;
61
import org.dataone.service.mn.tier4.v1.MNReplication;
62 6573 cjones
import org.dataone.service.types.v1.AccessPolicy;
63 6366 leinfelder
import org.dataone.service.types.v1.Checksum;
64
import org.dataone.service.types.v1.DescribeResponse;
65
import org.dataone.service.types.v1.Event;
66
import org.dataone.service.types.v1.Group;
67
import org.dataone.service.types.v1.Identifier;
68
import org.dataone.service.types.v1.Log;
69
import org.dataone.service.types.v1.LogEntry;
70
import org.dataone.service.types.v1.MonitorInfo;
71
import org.dataone.service.types.v1.MonitorList;
72
import org.dataone.service.types.v1.Node;
73 6600 cjones
import org.dataone.service.types.v1.NodeList;
74 6366 leinfelder
import org.dataone.service.types.v1.NodeReference;
75
import org.dataone.service.types.v1.NodeState;
76
import org.dataone.service.types.v1.NodeType;
77
import org.dataone.service.types.v1.ObjectFormatIdentifier;
78
import org.dataone.service.types.v1.ObjectList;
79
import org.dataone.service.types.v1.Permission;
80
import org.dataone.service.types.v1.Ping;
81 6528 cjones
import org.dataone.service.types.v1.ReplicationStatus;
82 6366 leinfelder
import org.dataone.service.types.v1.Schedule;
83
import org.dataone.service.types.v1.Service;
84
import org.dataone.service.types.v1.Services;
85
import org.dataone.service.types.v1.Session;
86
import org.dataone.service.types.v1.Subject;
87 6600 cjones
import org.dataone.service.types.v1.SubjectList;
88 6366 leinfelder
import org.dataone.service.types.v1.Synchronization;
89
import org.dataone.service.types.v1.SystemMetadata;
90
import org.dataone.service.types.v1.util.ChecksumUtil;
91 6476 jones
import org.dataone.service.util.Constants;
92 6179 cjones
93 6250 cjones
import edu.ucsb.nceas.metacat.DocumentImpl;
94 6234 cjones
import edu.ucsb.nceas.metacat.EventLog;
95 6230 cjones
import edu.ucsb.nceas.metacat.IdentifierManager;
96 6234 cjones
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
97 6389 leinfelder
import edu.ucsb.nceas.metacat.MetacatHandler;
98 6250 cjones
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
99 6260 cjones
import edu.ucsb.nceas.metacat.database.DBConnection;
100
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
101 6340 cjones
import edu.ucsb.nceas.metacat.properties.PropertyService;
102 6542 leinfelder
import edu.ucsb.nceas.metacat.util.SystemUtil;
103 6340 cjones
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
104 6230 cjones
105 6179 cjones
/**
106
 * Represents Metacat's implementation of the DataONE Member Node
107
 * service API. Methods implement the various MN* interfaces, and methods common
108
 * to both Member Node and Coordinating Node interfaces are found in the
109
 * D1NodeService base class.
110 6288 cjones
 *
111
 * Implements:
112
 * MNCore.ping()
113
 * MNCore.getLogRecords()
114
 * MNCore.getObjectStatistics()
115
 * MNCore.getOperationStatistics()
116
 * MNCore.getStatus()
117
 * MNCore.getCapabilities()
118
 * MNRead.get()
119
 * MNRead.getSystemMetadata()
120
 * MNRead.describe()
121
 * MNRead.getChecksum()
122
 * MNRead.listObjects()
123
 * MNRead.synchronizationFailed()
124
 * MNAuthorization.isAuthorized()
125
 * MNAuthorization.setAccessPolicy()
126
 * MNStorage.create()
127
 * MNStorage.update()
128
 * MNStorage.delete()
129
 * MNReplication.replicate()
130
 *
131 6179 cjones
 */
132 6599 cjones
public class MNodeService extends D1NodeService
133
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage {
134 6179 cjones
135 6475 jones
    /* the logger instance */
136
    private Logger logMetacat = null;
137 6241 cjones
138 6475 jones
    /**
139
     * Singleton accessor to get an instance of MNodeService.
140
     *
141
     * @return instance - the instance of MNodeService
142
     */
143 6542 leinfelder
    public static MNodeService getInstance(HttpServletRequest request) {
144
        return new MNodeService(request);
145 6179 cjones
    }
146
147 6475 jones
    /**
148
     * Constructor, private for singleton access
149
     */
150 6542 leinfelder
    private MNodeService(HttpServletRequest request) {
151
        super(request);
152 6475 jones
        logMetacat = Logger.getLogger(MNodeService.class);
153 6552 leinfelder
154
        // set the Member Node certificate file location
155
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
156 6310 cjones
    }
157 6475 jones
158
    /**
159
     * Deletes an object from the Member Node, where the object is either a
160
     * data object or a science metadata object.
161
     *
162
     * @param session - the Session object containing the credentials for the Subject
163
     * @param pid - The object identifier to be deleted
164
     *
165
     * @return pid - the identifier of the object used for the deletion
166
     *
167
     * @throws InvalidToken
168
     * @throws ServiceFailure
169
     * @throws NotAuthorized
170
     * @throws NotFound
171
     * @throws NotImplemented
172
     * @throws InvalidRequest
173
     */
174
    @Override
175
    public Identifier delete(Session session, Identifier pid)
176 6610 cjones
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
177 6475 jones
178
        String localId = null;
179
        boolean allowed = false;
180 6532 leinfelder
        String username = Constants.SUBJECT_PUBLIC;
181 6475 jones
        String[] groupnames = null;
182
        if (session != null) {
183
            username = session.getSubject().getValue();
184 6532 leinfelder
            if (session.getSubjectInfo() != null) {
185
                List<Group> groupList = session.getSubjectInfo().getGroupList();
186 6475 jones
                if (groupList != null) {
187
                    groupnames = new String[groupList.size()];
188
                    for (int i = 0; i > groupList.size(); i++) {
189
                        groupnames[i] = groupList.get(i).getGroupName();
190
                    }
191
                }
192
            }
193
        }
194
195
        // do we have a valid pid?
196
        if (pid == null || pid.getValue().trim().equals("")) {
197 6610 cjones
            throw new ServiceFailure("1350", "The provided identifier was invalid.");
198 6475 jones
        }
199
200
        // check for the existing identifier
201
        try {
202
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
203
        } catch (McdbDocNotFoundException e) {
204 6610 cjones
            throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
205 6475 jones
        }
206
207
        // does the subject have DELETE (a D1 CHANGE_PERMISSION level) priveleges on the pid?
208
        allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
209 6610 cjones
210 6475 jones
211
        if (allowed) {
212
            try {
213
                // delete the document
214
                DocumentImpl.delete(localId, username, groupnames, null);
215 6542 leinfelder
                EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
216 6475 jones
217
            } catch (McdbDocNotFoundException e) {
218 6610 cjones
                throw new NotFound("1340", "The provided identifier was invalid.");
219 6475 jones
220
            } catch (SQLException e) {
221
                throw new ServiceFailure("1350", "There was a problem deleting the object." + "The error message was: " + e.getMessage());
222
223
            } catch (InsufficientKarmaException e) {
224
                throw new NotAuthorized("1320", "The provided identity does not have " + "permission to DELETE objects on the Member Node.");
225
226
            } catch (Exception e) { // for some reason DocumentImpl throws a general Exception
227
                throw new ServiceFailure("1350", "There was a problem deleting the object." + "The error message was: " + e.getMessage());
228
            }
229
230
        } else {
231
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to DELETE objects on the Member Node.");
232
        }
233
234
        return pid;
235 6250 cjones
    }
236
237 6475 jones
    /**
238
     * Updates an existing object by creating a new object identified by
239
     * newPid on the Member Node which explicitly obsoletes the object
240
     * identified by pid through appropriate changes to the SystemMetadata
241
     * of pid and newPid
242
     *
243
     * @param session - the Session object containing the credentials for the Subject
244
     * @param pid - The identifier of the object to be updated
245
     * @param object - the new object bytes
246
     * @param sysmeta - the new system metadata describing the object
247
     *
248
     * @return newPid - the identifier of the new object
249
     *
250
     * @throws InvalidToken
251
     * @throws ServiceFailure
252
     * @throws NotAuthorized
253
     * @throws NotFound
254
     * @throws NotImplemented
255
     * @throws IdentifierNotUnique
256
     * @throws UnsupportedType
257
     * @throws InsufficientResources
258
     * @throws InvalidSystemMetadata
259
     * @throws InvalidRequest
260
     */
261
    @Override
262 6575 cjones
    public Identifier update(Session session, Identifier pid, InputStream object,
263
        Identifier newPid, SystemMetadata sysmeta)
264
        throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique,
265
        UnsupportedType, InsufficientResources, NotFound,
266
        InvalidSystemMetadata, NotImplemented, InvalidRequest {
267 6250 cjones
268 6475 jones
        String localId = null;
269
        boolean allowed = false;
270
        boolean isScienceMetadata = false;
271
        Subject subject = session.getSubject();
272
273
        // do we have a valid pid?
274
        if (pid == null || pid.getValue().trim().equals("")) {
275
            throw new InvalidRequest("1202", "The provided identifier was invalid.");
276 6575 cjones
277 6475 jones
        }
278
279
        // check for the existing identifier
280
        try {
281
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
282 6575 cjones
283 6475 jones
        } catch (McdbDocNotFoundException e) {
284 6575 cjones
            throw new InvalidRequest("1202", "The object with the provided " +
285
                "identifier was not found.");
286
287 6475 jones
        }
288 6518 leinfelder
289 6521 leinfelder
        // set the originating node
290
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
291
        sysmeta.setOriginMemberNode(originMemberNode);
292
293 6518 leinfelder
        // set the submitter to match the certificate
294
        sysmeta.setSubmitter(subject);
295 6525 leinfelder
        // set the dates
296
        Date now = Calendar.getInstance().getTime();
297 6575 cjones
        sysmeta.setDateSysMetadataModified(now);
298
        sysmeta.setDateUploaded(now);
299 6475 jones
300
        // does the subject have WRITE ( == update) priveleges on the pid?
301
        allowed = isAuthorized(session, pid, Permission.WRITE);
302
303
        if (allowed) {
304
305
            // get the existing system metadata for the object
306
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
307
308
            // add the newPid to the obsoletedBy list for the existing sysmeta
309
            existingSysMeta.setObsoletedBy(newPid);
310
311
            // then update the existing system metadata
312
            updateSystemMetadata(existingSysMeta);
313
314
            // prep the new system metadata, add pid to the affected lists
315
            sysmeta.setObsoletes(pid);
316
            //sysmeta.addDerivedFrom(pid);
317
318
            isScienceMetadata = isScienceMetadata(sysmeta);
319
320
            // do we have XML metadata or a data object?
321
            if (isScienceMetadata) {
322
323
                // update the science metadata XML document
324
                // TODO: handle non-XML metadata/data documents (like netCDF)
325
                // TODO: don't put objects into memory using stream to string
326
                String objectAsXML = "";
327
                try {
328
                    objectAsXML = IOUtils.toString(object, "UTF-8");
329
                    localId = insertOrUpdateDocument(objectAsXML, newPid, session, "update");
330
                    // register the newPid and the generated localId
331
                    if (newPid != null) {
332
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
333
334
                    }
335
336
                } catch (IOException e) {
337
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
338
                    logMetacat.info(msg);
339
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
340
341
                }
342
343
            } else {
344
345
                // update the data object
346
                localId = insertDataObject(object, newPid, session);
347
348
            }
349
350
            // and insert the new system metadata
351
            insertSystemMetadata(sysmeta);
352
353
            // log the update event
354 6542 leinfelder
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
355 6475 jones
356
        } else {
357
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
358
                    + " on the Member Node.");
359
        }
360
361
        return newPid;
362 6250 cjones
    }
363 6254 cjones
364 6475 jones
    public Identifier create(Session session, Identifier pid, InputStream object, SystemMetadata sysmeta) throws InvalidToken, ServiceFailure, NotAuthorized,
365
            IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, InvalidRequest {
366 6250 cjones
367 6575 cjones
      // check for null session
368 6530 leinfelder
        if (session == null) {
369 6575 cjones
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
370 6530 leinfelder
        }
371 6518 leinfelder
        // set the submitter to match the certificate
372
        sysmeta.setSubmitter(session.getSubject());
373 6520 leinfelder
        // set the originating node
374
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
375
        sysmeta.setOriginMemberNode(originMemberNode);
376 6525 leinfelder
        // set the dates
377
        Date now = Calendar.getInstance().getTime();
378 6575 cjones
    sysmeta.setDateSysMetadataModified(now);
379
    sysmeta.setDateUploaded(now);
380 6518 leinfelder
        // call the shared impl
381 6475 jones
        return super.create(session, pid, object, sysmeta);
382
    }
383 6250 cjones
384 6475 jones
    /**
385
     * Called by a Coordinating Node to request that the Member Node create a
386
     * copy of the specified object by retrieving it from another Member
387
     * Node and storing it locally so that it can be made accessible to
388
     * the DataONE system.
389
     *
390
     * @param session - the Session object containing the credentials for the Subject
391
     * @param sysmeta - Copy of the CN held system metadata for the object
392
     * @param sourceNode - A reference to node from which the content should be
393
     *                     retrieved. The reference should be resolved by
394
     *                     checking the CN node registry.
395
     *
396
     * @return true if the replication succeeds
397
     *
398
     * @throws ServiceFailure
399
     * @throws NotAuthorized
400
     * @throws NotImplemented
401
     * @throws UnsupportedType
402
     * @throws InsufficientResources
403
     * @throws InvalidRequest
404
     */
405
    @Override
406 6528 cjones
    public boolean replicate(Session session, SystemMetadata sysmeta, NodeReference sourceNode)
407
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
408
        InsufficientResources, UnsupportedType {
409 6250 cjones
410 6540 cjones
        logMetacat.info("MNodeService.replicate() called with parameters: \n" +
411
            "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
412
            "\tSystemMetadata       = " + sysmeta.toString()              + "\n" +
413
            "\tSource NodeReference ="  + sourceNode.getValue());
414
415 6475 jones
        boolean result = false;
416
417
        // TODO: check credentials
418
419
        // get the referenced object
420
        Identifier pid = sysmeta.getIdentifier();
421
422
        // get from the membernode
423
        // TODO: switch credentials for the server retrieval?
424
        MNode mn = D1Client.getMN(sourceNode);
425 6528 cjones
        CNode cn = D1Client.getCN();
426 6599 cjones
        long serialVersion = sysmeta.getSerialVersion().longValue();
427 6475 jones
        InputStream object = null;
428 6552 leinfelder
429 6475 jones
        try {
430 6575 cjones
          // session should be null to use the default certificate location set in the Certificate manager
431 6552 leinfelder
            object = mn.getReplica(null, pid);
432 6540 cjones
            logMetacat.info("MNodeService.replicate() called for identifier " + pid.getValue());
433
434 6475 jones
        } catch (InvalidToken e) {
435
            e.printStackTrace();
436
            throw new ServiceFailure("2151", "Could not retrieve object to replicate (InvalidToken): " + e.getMessage());
437
        } catch (NotFound e) {
438
            e.printStackTrace();
439
            throw new ServiceFailure("2151", "Could not retrieve object to replicate (NotFound): " + e.getMessage());
440
        }
441
442
        // add it to local store
443
        Identifier retPid;
444
        try {
445 6575 cjones
          // skip the MN.create -- this mutates the system metadata and we dont want it to
446 6552 leinfelder
            retPid = super.create(session, pid, object, sysmeta);
447 6475 jones
            result = (retPid.getValue().equals(pid.getValue()));
448
        } catch (InvalidToken e) {
449
            e.printStackTrace();
450
            throw new ServiceFailure("2151", "Could not save object to local store (InvalidToken): " + e.getMessage());
451
        } catch (IdentifierNotUnique e) {
452
            e.printStackTrace();
453
            throw new ServiceFailure("2151", "Could not save object to local store (IdentifierNotUnique): " + e.getMessage());
454
        } catch (InvalidSystemMetadata e) {
455
            e.printStackTrace();
456
            throw new ServiceFailure("2151", "Could not save object to local store (InvalidSystemMetadata): " + e.getMessage());
457
        }
458
459 6528 cjones
        try {
460 6575 cjones
          // call the CN as the MN to set the replication status
461 6599 cjones
            cn.setReplicationStatus(null, pid, sourceNode, ReplicationStatus.COMPLETED, serialVersion);
462 6528 cjones
463
        } catch (InvalidToken e) {
464
            // TODO Auto-generated catch block
465
            e.printStackTrace();
466
        } catch (NotFound e) {
467
            // TODO Auto-generated catch block
468
            e.printStackTrace();
469 6622 leinfelder
        } catch (VersionMismatch e) {
470
			// TODO Auto-generated catch block
471
			e.printStackTrace();
472
		}
473 6475 jones
        return result;
474
475 6250 cjones
    }
476 6179 cjones
477 6475 jones
    /**
478
     * This method provides a lighter weight mechanism than
479
     * MN_read.getSystemMetadata() for a client to determine basic
480
     * properties of the referenced object.
481
     *
482
     * @param session - the Session object containing the credentials for the Subject
483
     * @param pid - the identifier of the object to be described
484
     *
485
     * @return describeResponse - A set of values providing a basic description
486
     *                            of the object.
487
     *
488
     * @throws InvalidToken
489
     * @throws ServiceFailure
490
     * @throws NotAuthorized
491
     * @throws NotFound
492
     * @throws NotImplemented
493
     * @throws InvalidRequest
494
     */
495
    @Override
496 6610 cjones
    public DescribeResponse describe(Session session, Identifier pid)
497
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
498 6251 cjones
499 6575 cjones
      // get system metadata and construct the describe response
500 6475 jones
        SystemMetadata sysmeta = getSystemMetadata(session, pid);
501 6561 leinfelder
        DescribeResponse describeResponse = new DescribeResponse(sysmeta.getFormatId(), sysmeta.getSize(), sysmeta.getDateSysMetadataModified(),
502 6475 jones
                sysmeta.getChecksum());
503
504
        return describeResponse;
505
506 6259 cjones
    }
507 6258 cjones
508 6475 jones
    /**
509
     * Return the object identified by the given object identifier
510
     *
511
     * @param session - the Session object containing the credentials for the Subject
512
     * @param pid - the object identifier for the given object
513
     *
514
     * @return inputStream - the input stream of the given object
515
     *
516
     * @throws InvalidToken
517
     * @throws ServiceFailure
518
     * @throws NotAuthorized
519
     * @throws InvalidRequest
520
     * @throws NotImplemented
521
     */
522
    @Override
523 6610 cjones
    public InputStream get(Session session, Identifier pid)
524
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
525 6258 cjones
526 6475 jones
        return super.get(session, pid);
527 6258 cjones
528 6259 cjones
    }
529 6258 cjones
530 6475 jones
    /**
531
     * Returns a Checksum for the specified object using an accepted hashing algorithm
532
     *
533
     * @param session - the Session object containing the credentials for the Subject
534
     * @param pid - the object identifier for the given object
535
     * @param algorithm -  the name of an algorithm that will be used to compute
536
     *                     a checksum of the bytes of the object
537
     *
538
     * @return checksum - the checksum of the given object
539
     *
540
     * @throws InvalidToken
541
     * @throws ServiceFailure
542
     * @throws NotAuthorized
543
     * @throws NotFound
544
     * @throws InvalidRequest
545
     * @throws NotImplemented
546
     */
547
    @Override
548 6610 cjones
    public Checksum getChecksum(Session session, Identifier pid, String algorithm)
549
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
550
        InvalidRequest, NotImplemented {
551 6258 cjones
552 6475 jones
        Checksum checksum = null;
553
554
        InputStream inputStream = get(session, pid);
555
556 6259 cjones
        try {
557 6475 jones
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
558
559
        } catch (NoSuchAlgorithmException e) {
560
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
561
                    + e.getMessage());
562 6259 cjones
        } catch (IOException e) {
563 6475 jones
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
564
                    + e.getMessage());
565 6259 cjones
        }
566 6382 cjones
567 6475 jones
        if (checksum == null) {
568
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
569
        }
570 6258 cjones
571 6475 jones
        return checksum;
572 6259 cjones
    }
573 6179 cjones
574 6475 jones
    /**
575
     * Return the system metadata for a given object
576
     *
577
     * @param session - the Session object containing the credentials for the Subject
578
     * @param pid - the object identifier for the given object
579
     *
580
     * @return inputStream - the input stream of the given system metadata object
581
     *
582
     * @throws InvalidToken
583
     * @throws ServiceFailure
584
     * @throws NotAuthorized
585
     * @throws NotFound
586
     * @throws InvalidRequest
587
     * @throws NotImplemented
588
     */
589
    @Override
590 6610 cjones
    public SystemMetadata getSystemMetadata(Session session, Identifier pid)
591
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
592
        NotImplemented {
593 6341 leinfelder
594 6475 jones
        return super.getSystemMetadata(session, pid);
595
    }
596 6341 leinfelder
597 6475 jones
    /**
598
     * Retrieve the list of objects present on the MN that match the calling parameters
599
     *
600
     * @param session - the Session object containing the credentials for the Subject
601
     * @param startTime - Specifies the beginning of the time range from which
602
     *                    to return object (>=)
603
     * @param endTime - Specifies the beginning of the time range from which
604
     *                  to return object (>=)
605
     * @param objectFormat - Restrict results to the specified object format
606
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
607
     * @param start - The zero-based index of the first value, relative to the
608
     *                first record of the resultset that matches the parameters.
609
     * @param count - The maximum number of entries that should be returned in
610
     *                the response. The Member Node may return less entries
611
     *                than specified in this value.
612
     *
613
     * @return objectList - the list of objects matching the criteria
614
     *
615
     * @throws InvalidToken
616
     * @throws ServiceFailure
617
     * @throws NotAuthorized
618
     * @throws InvalidRequest
619
     * @throws NotImplemented
620
     */
621
    @Override
622
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
623
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
624 6179 cjones
625 6475 jones
        ObjectList objectList = null;
626 6332 leinfelder
627 6475 jones
        try {
628
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
629
        } catch (Exception e) {
630
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
631
        }
632 6332 leinfelder
633 6475 jones
        return objectList;
634 6229 cjones
    }
635 6179 cjones
636 6475 jones
    /**
637 6476 jones
     * Return a description of the node's capabilities and services.
638 6475 jones
     *
639
     * @return node - the technical capabilities of the Member Node
640
     *
641
     * @throws ServiceFailure
642
     * @throws NotAuthorized
643
     * @throws InvalidRequest
644
     * @throws NotImplemented
645
     */
646
    @Override
647 6610 cjones
    public Node getCapabilities()
648
        throws NotImplemented, ServiceFailure {
649 6179 cjones
650 6475 jones
        String nodeName = null;
651
        String nodeId = null;
652 6492 jones
        String subject = null;
653 6475 jones
        String nodeDesc = null;
654 6476 jones
        String nodeTypeString = null;
655
        NodeType nodeType = null;
656 6475 jones
        String mnCoreServiceVersion = null;
657
        String mnReadServiceVersion = null;
658
        String mnAuthorizationServiceVersion = null;
659
        String mnStorageServiceVersion = null;
660
        String mnReplicationServiceVersion = null;
661 6179 cjones
662 6475 jones
        boolean nodeSynchronize = false;
663
        boolean nodeReplicate = false;
664
        boolean mnCoreServiceAvailable = false;
665
        boolean mnReadServiceAvailable = false;
666
        boolean mnAuthorizationServiceAvailable = false;
667
        boolean mnStorageServiceAvailable = false;
668
        boolean mnReplicationServiceAvailable = false;
669 6179 cjones
670 6475 jones
        try {
671
            // get the properties of the node based on configuration information
672 6492 jones
            nodeName = PropertyService.getProperty("dataone.nodeName");
673 6475 jones
            nodeId = PropertyService.getProperty("dataone.memberNodeId");
674 6492 jones
            subject = PropertyService.getProperty("dataone.subject");
675 6475 jones
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
676 6476 jones
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
677
            nodeType = NodeType.convert(nodeTypeString);
678 6475 jones
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
679
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
680
681
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
682
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
683
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
684
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
685
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
686
687
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
688
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
689
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
690
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
691
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
692
693 6476 jones
            // Set the properties of the node based on configuration information and
694
            // calls to current status methods
695 6542 leinfelder
            String serviceName = SystemUtil.getContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
696 6476 jones
            Node node = new Node();
697 6542 leinfelder
            node.setBaseURL(serviceName + "/" + nodeTypeString);
698 6476 jones
            node.setDescription(nodeDesc);
699 6475 jones
700 6476 jones
            // set the node's health information
701
            node.setState(NodeState.UP);
702
703
            // set the ping response to the current value
704
            Ping canPing = new Ping();
705
            canPing.setSuccess(false);
706
            try {
707
                canPing.setSuccess(ping());
708
            } catch (InsufficientResources e) {
709
                e.printStackTrace();
710
            }
711 6610 cjones
712 6476 jones
            node.setPing(canPing);
713 6475 jones
714 6476 jones
            NodeReference identifier = new NodeReference();
715
            identifier.setValue(nodeId);
716
            node.setIdentifier(identifier);
717 6492 jones
            Subject s = new Subject();
718
            s.setValue(subject);
719
            node.addSubject(s);
720 6476 jones
            node.setName(nodeName);
721
            node.setReplicate(nodeReplicate);
722
            node.setSynchronize(nodeSynchronize);
723 6475 jones
724 6476 jones
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
725
            Services services = new Services();
726 6475 jones
727 6476 jones
            Service sMNCore = new Service();
728
            sMNCore.setName("MNCore");
729
            sMNCore.setVersion(mnCoreServiceVersion);
730
            sMNCore.setAvailable(mnCoreServiceAvailable);
731 6475 jones
732 6476 jones
            Service sMNRead = new Service();
733
            sMNRead.setName("MNRead");
734
            sMNRead.setVersion(mnReadServiceVersion);
735
            sMNRead.setAvailable(mnReadServiceAvailable);
736 6475 jones
737 6476 jones
            Service sMNAuthorization = new Service();
738
            sMNAuthorization.setName("MNAuthorization");
739
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
740
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
741 6475 jones
742 6476 jones
            Service sMNStorage = new Service();
743
            sMNStorage.setName("MNStorage");
744
            sMNStorage.setVersion(mnStorageServiceVersion);
745
            sMNStorage.setAvailable(mnStorageServiceAvailable);
746 6475 jones
747 6476 jones
            Service sMNReplication = new Service();
748
            sMNReplication.setName("MNReplication");
749
            sMNReplication.setVersion(mnReplicationServiceVersion);
750
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
751 6475 jones
752 6476 jones
            services.addService(sMNRead);
753
            services.addService(sMNCore);
754
            services.addService(sMNAuthorization);
755
            services.addService(sMNStorage);
756
            services.addService(sMNReplication);
757
            node.setServices(services);
758 6475 jones
759 6476 jones
            // TODO: Allow the metacat admin to determine the schedule
760
            // Set the schedule for synchronization
761
            Synchronization synchronization = new Synchronization();
762
            Schedule schedule = new Schedule();
763
            Date now = new Date();
764
            schedule.setYear("*");
765
            schedule.setMon("*");
766
            schedule.setMday("*");
767 6512 jones
            schedule.setWday("?");
768 6476 jones
            schedule.setHour("*");
769 6512 jones
            schedule.setMin("0/3");
770
            schedule.setSec("10");
771 6476 jones
            synchronization.setSchedule(schedule);
772
            synchronization.setLastHarvested(now);
773
            synchronization.setLastCompleteHarvest(now);
774
            node.setSynchronization(synchronization);
775 6475 jones
776 6476 jones
            node.setType(nodeType);
777
            return node;
778 6475 jones
779 6476 jones
        } catch (PropertyNotFoundException pnfe) {
780
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
781
            logMetacat.error(msg);
782
            throw new ServiceFailure("2162", msg);
783
        }
784 6228 cjones
    }
785 6179 cjones
786 6475 jones
    /**
787
     * Returns the number of operations that have been serviced by the node
788
     * over time periods of one and 24 hours.
789
     *
790
     * @param session - the Session object containing the credentials for the Subject
791
     * @param period - An ISO8601 compatible DateTime range specifying the time
792
     *                 range for which to return operation statistics.
793
     * @param requestor - Limit to operations performed by given requestor identity.
794
     * @param event -  Enumerated value indicating the type of event being examined
795
     * @param format - Limit to events involving objects of the specified format
796
     *
797
     * @return the desired log records
798
     *
799
     * @throws InvalidToken
800
     * @throws ServiceFailure
801
     * @throws NotAuthorized
802
     * @throws InvalidRequest
803
     * @throws NotImplemented
804
     */
805 6610 cjones
    public MonitorList getOperationStatistics(Session session, Date startTime,
806
        Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
807
        throws NotImplemented, ServiceFailure, NotAuthorized, InsufficientResources, UnsupportedType {
808 6179 cjones
809 6475 jones
        MonitorList monitorList = new MonitorList();
810 6179 cjones
811 6475 jones
        try {
812 6179 cjones
813 6475 jones
            // get log records first
814
            Log logs = getLogRecords(session, startTime, endTime, event, 0, null);
815 6179 cjones
816 6475 jones
            // TODO: aggregate by day or hour -- needs clarification
817
            int count = 1;
818
            for (LogEntry logEntry : logs.getLogEntryList()) {
819
                Identifier pid = logEntry.getIdentifier();
820
                Date logDate = logEntry.getDateLogged();
821
                // if we are filtering by format
822
                if (formatId != null) {
823
                    SystemMetadata sysmeta = IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
824 6561 leinfelder
                    if (!sysmeta.getFormatId().getValue().equals(formatId.getValue())) {
825 6475 jones
                        // does not match
826
                        continue;
827
                    }
828
                }
829
                MonitorInfo item = new MonitorInfo();
830
                item.setCount(count);
831
                item.setDate(new java.sql.Date(logDate.getTime()));
832
                monitorList.addMonitorInfo(item);
833 6179 cjones
834 6475 jones
            }
835
        } catch (Exception e) {
836
            e.printStackTrace();
837
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
838
        }
839 6345 cjones
840 6475 jones
        return monitorList;
841 6345 cjones
842 6340 cjones
    }
843
844 6475 jones
    /**
845
     * Low level “are you alive” operation. A valid ping response is
846
     * indicated by a HTTP status of 200.
847
     *
848
     * @return true if the service is alive
849
     *
850
     * @throws InvalidToken
851
     * @throws ServiceFailure
852
     * @throws NotImplemented
853
     */
854
    @Override
855 6610 cjones
    public boolean ping()
856
        throws NotImplemented, ServiceFailure, InsufficientResources {
857 6475 jones
858
        // test if we can get a database connection
859
        boolean alive = false;
860
        int serialNumber = -1;
861
        DBConnection dbConn = null;
862
        try {
863
            dbConn = DBConnectionPool.getDBConnection("MNodeService.ping");
864
            serialNumber = dbConn.getCheckOutSerialNumber();
865
            alive = true;
866
        } catch (SQLException e) {
867
            return alive;
868
        } finally {
869
            // Return the database connection
870
            DBConnectionPool.returnDBConnection(dbConn, serialNumber);
871
        }
872
873
        return alive;
874 6351 cjones
    }
875
876 6475 jones
    /**
877
     * A callback method used by a CN to indicate to a MN that it cannot
878
     * complete synchronization of the science metadata identified by pid.  Log
879
     * the event in the metacat event log.
880
     *
881
     * @param session
882
     * @param syncFailed
883
     *
884
     * @throws ServiceFailure
885
     * @throws NotAuthorized
886
     * @throws NotImplemented
887
     */
888
    @Override
889 6610 cjones
    public void synchronizationFailed(Session session, SynchronizationFailed syncFailed)
890
        throws NotImplemented, ServiceFailure, NotAuthorized {
891 6179 cjones
892 6475 jones
        String localId;
893 6331 leinfelder
894 6475 jones
        try {
895
            localId = IdentifierManager.getInstance().getLocalId(syncFailed.getPid());
896
        } catch (McdbDocNotFoundException e) {
897 6610 cjones
            throw new ServiceFailure("2161", "The identifier specified by " + syncFailed.getPid() + " was not found on this node.");
898 6179 cjones
899 6475 jones
        }
900
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
901
        // method is changed to include the URL as a parameter
902
        logMetacat.debug("Synchronization for the object identified by " + syncFailed.getPid() + " failed from " + syncFailed.getNodeId()
903
                + " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
904
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
905 6532 leinfelder
        String principal = Constants.SUBJECT_PUBLIC;
906 6506 leinfelder
        if (session != null && session.getSubject() != null) {
907 6575 cjones
          principal = session.getSubject().getValue();
908 6506 leinfelder
        }
909
        try {
910 6575 cjones
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
911 6506 leinfelder
        } catch (Exception e) {
912
            throw new ServiceFailure("2161", "Could not log the error for: " + syncFailed.getPid());
913 6575 cjones
    }
914 6475 jones
        //EventLog.getInstance().log("CN URL WILL GO HERE",
915
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
916 6179 cjones
917 6260 cjones
    }
918
919 6475 jones
    /**
920
     * Essentially a get() but with different logging behavior
921
     */
922
    @Override
923 6540 cjones
    public InputStream getReplica(Session session, Identifier pid)
924 6610 cjones
        throws NotAuthorized, NotImplemented, ServiceFailure {
925 6179 cjones
926 6540 cjones
        logMetacat.info("MNodeService.getReplica() called.");
927
928 6475 jones
        InputStream inputStream = null; // bytes to be returned
929
        handler = new MetacatHandler(new Timer());
930
        boolean allowed = false;
931
        String localId; // the metacat docid for the pid
932 6179 cjones
933 6475 jones
        // get the local docid from Metacat
934
        try {
935
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
936
        } catch (McdbDocNotFoundException e) {
937 6610 cjones
            throw new ServiceFailure("2181", "The object specified by " +
938
                    pid.getValue() + " does not exist at this node.");
939
940 6475 jones
        }
941 6234 cjones
942 6552 leinfelder
        Subject targetNodeSubject = session.getSubject();
943 6185 leinfelder
944 6552 leinfelder
        // check for authorization to replicate, null session to act as this source MN
945 6610 cjones
        try {
946
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid, Permission.REPLICATE);
947
        } catch (InvalidToken e1) {
948
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
949
                + e1.getMessage());
950
951
        } catch (NotFound e1) {
952
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
953
                    + e1.getMessage());
954 6384 cjones
955 6610 cjones
        } catch (InvalidRequest e1) {
956
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
957
                    + e1.getMessage());
958
959
        }
960
961 6540 cjones
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
962
            " for identifier " + pid.getValue());
963
964 6475 jones
        // if the person is authorized, perform the read
965
        if (allowed) {
966
            try {
967
                inputStream = handler.read(localId);
968
            } catch (Exception e) {
969 6610 cjones
                throw new ServiceFailure("1020", "The object specified by " +
970
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
971 6475 jones
            }
972
        }
973 6384 cjones
974 6475 jones
        // if we fail to set the input stream
975
        if (inputStream == null) {
976 6610 cjones
            throw new ServiceFailure("2181", "The object specified by " +
977
                pid.getValue() + "does not exist at this node.");
978 6475 jones
        }
979
980
        // log the replica event
981
        String principal = null;
982
        if (session.getSubject() != null) {
983
            principal = session.getSubject().getValue();
984
        }
985 6576 cjones
        EventLog.getInstance().log(request.getRemoteAddr(),
986
            request.getHeader("User-Agent"), principal, localId, "replicate");
987 6475 jones
988
        return inputStream;
989
    }
990
991 6573 cjones
    /**
992
     * Set the access policy
993
     */
994
    @Deprecated
995
    @Override
996
    public boolean setAccessPolicy(Session session, Identifier pid,
997
        AccessPolicy policy)
998
        throws InvalidToken, ServiceFailure, NotFound, NotAuthorized,
999
        NotImplemented, InvalidRequest {
1000
1001
        throw new NotImplemented("4401", "This method is deprecated for Member Nodes.");
1002
1003
    }
1004
1005 6599 cjones
    /**
1006 6600 cjones
     * A method to notify the Member Node that the authoritative copy of
1007 6599 cjones
     * system metadata on the Coordinating Nodes has changed.
1008
     *
1009
     * @param session   Session information that contains the identity of the
1010
     *                  calling user as retrieved from the X.509 certificate
1011
     *                  which must be traceable to the CILogon service.
1012
     * @param serialVersion   The serialVersion of the system metadata
1013
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1014
     * @throws NotImplemented
1015
     * @throws ServiceFailure
1016
     * @throws NotAuthorized
1017
     * @throws InvalidRequest
1018
     * @throws InvalidToken
1019
     */
1020
    public void systemMetadataChanged(Session session, Identifier pid,
1021
        long serialVersion, Date dateSysMetaLastModified)
1022
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1023
        InvalidToken {
1024
1025 6600 cjones
        SystemMetadata currentLocalSysMeta = null;
1026
        SystemMetadata newSysMeta = null;
1027
        CNode cn = D1Client.getCN();
1028
        NodeList nodeList = null;
1029
        Subject callingSubject = null;
1030
        boolean allowed = false;
1031
1032
        // are we allowed to call this?
1033
        callingSubject = session.getSubject();
1034
        nodeList = cn.listNodes();
1035
1036
        for(Node node : nodeList.getNodeList()) {
1037
            // must be a CN
1038
            if ( node.getType().equals(NodeType.CN)) {
1039
               List<Subject> subjectList = node.getSubjectList();
1040
               // the calling subject must be in the subject list
1041
               if ( subjectList.contains(callingSubject)) {
1042
                   allowed = true;
1043
1044
               }
1045
1046
            }
1047
        }
1048
1049
        if (!allowed ) {
1050
            String msg = "The subject identified by " + callingSubject.getValue() +
1051
              " is not authorized to call this service.";
1052
            throw new NotAuthorized("1331", msg);
1053
1054
        }
1055
1056
        // compare what we have locally to what is sent in the change notification
1057
        try {
1058
            currentLocalSysMeta =
1059
                IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
1060
1061
        } catch (McdbDocNotFoundException e) {
1062
            String msg = "SystemMetadata for pid " + pid.getValue() +
1063
              " cpouldn't be updated because it couldn't be found locally: " +
1064
              e.getMessage();
1065
            logMetacat.warn(msg);
1066
1067
        }
1068
1069
        if (currentLocalSysMeta.getSerialVersion().longValue() < serialVersion ) {
1070
            try {
1071
                newSysMeta = cn.getSystemMetadata(null, pid);
1072
            } catch (NotFound e) {
1073
                // huh? you just said you had it
1074
                logMetacat.error("On updating the local copy of system metadata " +
1075
                    "for pid " + pid.getValue() +", the CN reports it is not found." +
1076
                    " The error message was: " + e.getMessage());
1077
1078
            }
1079
            // update the local copy of system metadata for the pid
1080
            try {
1081
                IdentifierManager.getInstance().updateSystemMetadata(newSysMeta);
1082
                logMetacat.info("Updated local copy of system metadata for pid " +
1083
                    pid.getValue() + " after change notification from the CN.");
1084
1085
            } catch (McdbDocNotFoundException e) {
1086
                String msg = "SystemMetadata for pid " + pid.getValue() +
1087
                  " cpouldn't be updated because it couldn't be found: " +
1088
                  e.getMessage();
1089
                logMetacat.warn(msg);
1090
1091
            }
1092
        }
1093
1094 6599 cjones
    }
1095
1096 6179 cjones
}