Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000-2011 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author:  $'
7
 *     '$Date:  $'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23

    
24
package edu.ucsb.nceas.metacat.dataone;
25

    
26
import java.io.IOException;
27
import java.io.InputStream;
28
import java.security.NoSuchAlgorithmException;
29
import java.sql.SQLException;
30
import java.text.SimpleDateFormat;
31
import java.util.Date;
32
import java.util.List;
33
import java.util.Timer;
34

    
35
import org.apache.commons.io.IOUtils;
36
import org.apache.log4j.Logger;
37
import org.dataone.client.D1Client;
38
import org.dataone.client.MNode;
39
import org.dataone.service.util.Constants;
40
import org.dataone.service.exceptions.IdentifierNotUnique;
41
import org.dataone.service.exceptions.InsufficientResources;
42
import org.dataone.service.exceptions.InvalidRequest;
43
import org.dataone.service.exceptions.InvalidSystemMetadata;
44
import org.dataone.service.exceptions.InvalidToken;
45
import org.dataone.service.exceptions.NotAuthorized;
46
import org.dataone.service.exceptions.NotFound;
47
import org.dataone.service.exceptions.NotImplemented;
48
import org.dataone.service.exceptions.ServiceFailure;
49
import org.dataone.service.exceptions.SynchronizationFailed;
50
import org.dataone.service.exceptions.UnsupportedType;
51
import org.dataone.service.mn.tier1.v1.MNCore;
52
import org.dataone.service.mn.tier1.v1.MNRead;
53
import org.dataone.service.mn.tier2.v1.MNAuthorization;
54
import org.dataone.service.mn.tier3.v1.MNStorage;
55
import org.dataone.service.mn.tier4.v1.MNReplication;
56
import org.dataone.service.types.v1.Checksum;
57
import org.dataone.service.types.v1.DescribeResponse;
58
import org.dataone.service.types.v1.Event;
59
import org.dataone.service.types.v1.Group;
60
import org.dataone.service.types.v1.Identifier;
61
import org.dataone.service.types.v1.Log;
62
import org.dataone.service.types.v1.LogEntry;
63
import org.dataone.service.types.v1.MonitorInfo;
64
import org.dataone.service.types.v1.MonitorList;
65
import org.dataone.service.types.v1.Node;
66
import org.dataone.service.types.v1.NodeReference;
67
import org.dataone.service.types.v1.NodeState;
68
import org.dataone.service.types.v1.NodeType;
69
import org.dataone.service.types.v1.ObjectFormatIdentifier;
70
import org.dataone.service.types.v1.ObjectList;
71
import org.dataone.service.types.v1.Permission;
72
import org.dataone.service.types.v1.Ping;
73
import org.dataone.service.types.v1.Schedule;
74
import org.dataone.service.types.v1.Service;
75
import org.dataone.service.types.v1.Services;
76
import org.dataone.service.types.v1.Session;
77
import org.dataone.service.types.v1.Subject;
78
import org.dataone.service.types.v1.Synchronization;
79
import org.dataone.service.types.v1.SystemMetadata;
80
import org.dataone.service.types.v1.util.ChecksumUtil;
81

    
82
import edu.ucsb.nceas.metacat.DocumentImpl;
83
import edu.ucsb.nceas.metacat.EventLog;
84
import edu.ucsb.nceas.metacat.IdentifierManager;
85
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
86
import edu.ucsb.nceas.metacat.MetacatHandler;
87
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
88
import edu.ucsb.nceas.metacat.database.DBConnection;
89
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
90
import edu.ucsb.nceas.metacat.properties.PropertyService;
91
import edu.ucsb.nceas.metacat.util.SystemUtil;
92
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
93

    
94
/**
95
 * Represents Metacat's implementation of the DataONE Member Node 
96
 * service API. Methods implement the various MN* interfaces, and methods common
97
 * to both Member Node and Coordinating Node interfaces are found in the
98
 * D1NodeService base class.
99
 * 
100
 * Implements:
101
 * MNCore.ping()
102
 * MNCore.getLogRecords()
103
 * MNCore.getObjectStatistics()
104
 * MNCore.getOperationStatistics()
105
 * MNCore.getStatus()
106
 * MNCore.getCapabilities()
107
 * MNRead.get()
108
 * MNRead.getSystemMetadata()
109
 * MNRead.describe()
110
 * MNRead.getChecksum()
111
 * MNRead.listObjects()
112
 * MNRead.synchronizationFailed()
113
 * MNAuthorization.isAuthorized()
114
 * MNAuthorization.setAccessPolicy()
115
 * MNStorage.create()
116
 * MNStorage.update()
117
 * MNStorage.delete()
118
 * MNReplication.replicate()
119
 * 
120
 */
121
public class MNodeService extends D1NodeService implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage {
122

    
123
    /* the instance of the MNodeService object */
124
    private static MNodeService instance = null;
125

    
126
    /* the logger instance */
127
    private Logger logMetacat = null;
128

    
129
    /**
130
     * Singleton accessor to get an instance of MNodeService.
131
     * 
132
     * @return instance - the instance of MNodeService
133
     */
134
    public static MNodeService getInstance() {
135
        if (instance == null) {
136
            instance = new MNodeService();
137
        }
138
        return instance;
139
    }
140

    
141
    /**
142
     * Constructor, private for singleton access
143
     */
144
    private MNodeService() {
145
        super();
146
        logMetacat = Logger.getLogger(MNodeService.class);
147
    }
148

    
149
    /**
150
     * Deletes an object from the Member Node, where the object is either a 
151
     * data object or a science metadata object.
152
     * 
153
     * @param session - the Session object containing the credentials for the Subject
154
     * @param pid - The object identifier to be deleted
155
     * 
156
     * @return pid - the identifier of the object used for the deletion
157
     * 
158
     * @throws InvalidToken
159
     * @throws ServiceFailure
160
     * @throws NotAuthorized
161
     * @throws NotFound
162
     * @throws NotImplemented
163
     * @throws InvalidRequest
164
     */
165
    @Override
166
    public Identifier delete(Session session, Identifier pid) 
167
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest {
168

    
169
        String localId = null;
170
        boolean allowed = false;
171
        String username = Constants.PUBLIC_SUBJECT;
172
        String[] groupnames = null;
173
        if (session != null) {
174
            username = session.getSubject().getValue();
175
            if (session.getSubjectList() != null) {
176
                List<Group> groupList = session.getSubjectList().getGroupList();
177
                if (groupList != null) {
178
                    groupnames = new String[groupList.size()];
179
                    for (int i = 0; i > groupList.size(); i++) {
180
                        groupnames[i] = groupList.get(i).getGroupName();
181
                    }
182
                }
183
            }
184
        }
185

    
186
        // do we have a valid pid?
187
        if (pid == null || pid.getValue().trim().equals("")) {
188
            throw new InvalidRequest("1322", "The provided identifier was invalid.");
189
        }
190

    
191
        // check for the existing identifier
192
        try {
193
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
194
        } catch (McdbDocNotFoundException e) {
195
            throw new InvalidRequest("1322", "The object with the provided " + "identifier was not found.");
196
        }
197

    
198
        // does the subject have DELETE (a D1 CHANGE_PERMISSION level) priveleges on the pid?
199
        allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
200

    
201
        if (allowed) {
202
            try {
203
                // delete the document
204
                DocumentImpl.delete(localId, username, groupnames, null);
205
                EventLog.getInstance().log(metacatUrl, username, localId, Event.DELETE.xmlValue());
206

    
207
            } catch (McdbDocNotFoundException e) {
208
                throw new InvalidRequest("1322", "The provided identifier was invalid.");
209

    
210
            } catch (SQLException e) {
211
                throw new ServiceFailure("1350", "There was a problem deleting the object." + "The error message was: " + e.getMessage());
212

    
213
            } catch (InsufficientKarmaException e) {
214
                throw new NotAuthorized("1320", "The provided identity does not have " + "permission to DELETE objects on the Member Node.");
215

    
216
            } catch (Exception e) { // for some reason DocumentImpl throws a general Exception
217
                throw new ServiceFailure("1350", "There was a problem deleting the object." + "The error message was: " + e.getMessage());
218
            }
219

    
220
        } else {
221
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to DELETE objects on the Member Node.");
222
        }
223

    
224
        return pid;
225
    }
226

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

    
256
        // check if the pid has been reserved
257
        try {
258
            boolean hasReservation = D1Client.getCN().hasReservation(session, pid);
259
            if (!hasReservation) {
260
                throw new NotAuthorized("", "No reservation for pid: " + pid.getValue());
261
            }
262
        } catch (NotFound e) {
263
            // okay to continue
264
        }
265

    
266
        String localId = null;
267
        boolean allowed = false;
268
        boolean isScienceMetadata = false;
269
        Subject subject = session.getSubject();
270

    
271
        // do we have a valid pid?
272
        if (pid == null || pid.getValue().trim().equals("")) {
273
            throw new InvalidRequest("1202", "The provided identifier was invalid.");
274
        }
275

    
276
        // check for the existing identifier
277
        try {
278
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
279
        } catch (McdbDocNotFoundException e) {
280
            throw new InvalidRequest("1202", "The object with the provided " + "identifier was not found.");
281
        }
282

    
283
        // does the subject have WRITE ( == update) priveleges on the pid?
284
        allowed = isAuthorized(session, pid, Permission.WRITE);
285

    
286
        if (allowed) {
287

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

    
291
            // add the newPid to the obsoletedBy list for the existing sysmeta
292
            existingSysMeta.setObsoletedBy(newPid);
293

    
294
            // then update the existing system metadata
295
            updateSystemMetadata(existingSysMeta);
296

    
297
            // prep the new system metadata, add pid to the affected lists
298
            sysmeta.setObsoletes(pid);
299
            //sysmeta.addDerivedFrom(pid);
300

    
301
            isScienceMetadata = isScienceMetadata(sysmeta);
302

    
303
            // do we have XML metadata or a data object?
304
            if (isScienceMetadata) {
305

    
306
                // update the science metadata XML document
307
                // TODO: handle non-XML metadata/data documents (like netCDF)
308
                // TODO: don't put objects into memory using stream to string
309
                String objectAsXML = "";
310
                try {
311
                    objectAsXML = IOUtils.toString(object, "UTF-8");
312
                    localId = insertOrUpdateDocument(objectAsXML, newPid, session, "update");
313
                    // register the newPid and the generated localId
314
                    if (newPid != null) {
315
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
316

    
317
                    }
318

    
319
                } catch (IOException e) {
320
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
321
                    logMetacat.info(msg);
322
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
323

    
324
                }
325

    
326
            } else {
327

    
328
                // update the data object
329
                localId = insertDataObject(object, newPid, session);
330

    
331
            }
332

    
333
            // and insert the new system metadata
334
            insertSystemMetadata(sysmeta);
335

    
336
            // log the update event
337
            EventLog.getInstance().log(metacatUrl, subject.getValue(), localId, "update");
338

    
339
        } else {
340
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
341
                    + " on the Member Node.");
342
        }
343

    
344
        return newPid;
345
    }
346

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

    
350
        // check if the pid has been reserved
351
        try {
352
            boolean hasReservation = D1Client.getCN().hasReservation(session, pid);
353
            if (!hasReservation) {
354
                throw new NotAuthorized("", "No reservation for pid: " + pid.getValue());
355
            }
356
        } catch (NotFound e) {
357
            // okay to continue
358
        }
359

    
360
        return super.create(session, pid, object, sysmeta);
361
    }
362

    
363
    /**
364
     * Called by a Coordinating Node to request that the Member Node create a 
365
     * copy of the specified object by retrieving it from another Member 
366
     * Node and storing it locally so that it can be made accessible to 
367
     * the DataONE system.
368
     * 
369
     * @param session - the Session object containing the credentials for the Subject
370
     * @param sysmeta - Copy of the CN held system metadata for the object
371
     * @param sourceNode - A reference to node from which the content should be 
372
     *                     retrieved. The reference should be resolved by 
373
     *                     checking the CN node registry.
374
     * 
375
     * @return true if the replication succeeds
376
     * 
377
     * @throws ServiceFailure
378
     * @throws NotAuthorized
379
     * @throws NotImplemented
380
     * @throws UnsupportedType
381
     * @throws InsufficientResources
382
     * @throws InvalidRequest
383
     */
384
    @Override
385
    public boolean replicate(Session session, SystemMetadata sysmeta, NodeReference sourceNode) throws NotImplemented, ServiceFailure, NotAuthorized,
386
            InvalidRequest, InsufficientResources, UnsupportedType {
387

    
388
        boolean result = false;
389

    
390
        // TODO: check credentials
391

    
392
        // get the referenced object
393
        Identifier pid = sysmeta.getIdentifier();
394

    
395
        // get from the membernode
396
        // TODO: switch credentials for the server retrieval?
397
        MNode mn = D1Client.getMN(sourceNode);
398
        InputStream object = null;
399

    
400
        try {
401
            object = mn.get(session, pid);
402
        } catch (InvalidToken e) {
403
            e.printStackTrace();
404
            throw new ServiceFailure("2151", "Could not retrieve object to replicate (InvalidToken): " + e.getMessage());
405
        } catch (NotFound e) {
406
            e.printStackTrace();
407
            throw new ServiceFailure("2151", "Could not retrieve object to replicate (NotFound): " + e.getMessage());
408
        }
409

    
410
        // add it to local store
411
        Identifier retPid;
412
        try {
413
            retPid = create(session, pid, object, sysmeta);
414
            result = (retPid.getValue().equals(pid.getValue()));
415
        } catch (InvalidToken e) {
416
            e.printStackTrace();
417
            throw new ServiceFailure("2151", "Could not save object to local store (InvalidToken): " + e.getMessage());
418
        } catch (IdentifierNotUnique e) {
419
            e.printStackTrace();
420
            throw new ServiceFailure("2151", "Could not save object to local store (IdentifierNotUnique): " + e.getMessage());
421
        } catch (InvalidSystemMetadata e) {
422
            e.printStackTrace();
423
            throw new ServiceFailure("2151", "Could not save object to local store (InvalidSystemMetadata): " + e.getMessage());
424
        }
425

    
426
        return result;
427

    
428
    }
429

    
430
    /**
431
     * This method provides a lighter weight mechanism than 
432
     * MN_read.getSystemMetadata() for a client to determine basic 
433
     * properties of the referenced object.
434
     * 
435
     * @param session - the Session object containing the credentials for the Subject
436
     * @param pid - the identifier of the object to be described
437
     * 
438
     * @return describeResponse - A set of values providing a basic description 
439
     *                            of the object.
440
     * 
441
     * @throws InvalidToken
442
     * @throws ServiceFailure
443
     * @throws NotAuthorized
444
     * @throws NotFound
445
     * @throws NotImplemented
446
     * @throws InvalidRequest
447
     */
448
    @Override
449
    public DescribeResponse describe(Session session, Identifier pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented,
450
            InvalidRequest {
451

    
452
        if (session == null) {
453
            throw new InvalidToken("1370", "The session object is null");
454
        }
455

    
456
        if (pid == null || pid.getValue().trim().equals("")) {
457
            throw new InvalidRequest("1362", "The object identifier is null. " + "A valid identifier is required.");
458
        }
459

    
460
        SystemMetadata sysmeta = getSystemMetadata(session, pid);
461
        DescribeResponse describeResponse = new DescribeResponse(sysmeta.getFmtid(), sysmeta.getSize(), sysmeta.getDateSysMetadataModified(),
462
                sysmeta.getChecksum());
463

    
464
        return describeResponse;
465

    
466
    }
467

    
468
    /**
469
     * Return the object identified by the given object identifier
470
     * 
471
     * @param session - the Session object containing the credentials for the Subject
472
     * @param pid - the object identifier for the given object
473
     * 
474
     * @return inputStream - the input stream of the given object
475
     * 
476
     * @throws InvalidToken
477
     * @throws ServiceFailure
478
     * @throws NotAuthorized
479
     * @throws InvalidRequest
480
     * @throws NotImplemented
481
     */
482
    @Override
483
    public InputStream get(Session session, Identifier pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest {
484

    
485
        return super.get(session, pid);
486

    
487
    }
488

    
489
    /**
490
     * Returns a Checksum for the specified object using an accepted hashing algorithm
491
     * 
492
     * @param session - the Session object containing the credentials for the Subject
493
     * @param pid - the object identifier for the given object
494
     * @param algorithm -  the name of an algorithm that will be used to compute 
495
     *                     a checksum of the bytes of the object
496
     * 
497
     * @return checksum - the checksum of the given object
498
     * 
499
     * @throws InvalidToken
500
     * @throws ServiceFailure
501
     * @throws NotAuthorized
502
     * @throws NotFound
503
     * @throws InvalidRequest
504
     * @throws NotImplemented
505
     */
506
    @Override
507
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
508
            InvalidRequest, NotImplemented {
509

    
510
        Checksum checksum = null;
511

    
512
        InputStream inputStream = get(session, pid);
513

    
514
        try {
515
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
516

    
517
        } catch (NoSuchAlgorithmException e) {
518
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
519
                    + e.getMessage());
520
        } catch (IOException e) {
521
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
522
                    + e.getMessage());
523
        }
524

    
525
        if (checksum == null) {
526
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
527
        }
528

    
529
        return checksum;
530
    }
531

    
532
    /**
533
     * Return the system metadata for a given object
534
     * 
535
     * @param session - the Session object containing the credentials for the Subject
536
     * @param pid - the object identifier for the given object
537
     * 
538
     * @return inputStream - the input stream of the given system metadata object
539
     * 
540
     * @throws InvalidToken
541
     * @throws ServiceFailure
542
     * @throws NotAuthorized
543
     * @throws NotFound
544
     * @throws InvalidRequest
545
     * @throws NotImplemented
546
     */
547
    @Override
548
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest,
549
            NotImplemented {
550

    
551
        return super.getSystemMetadata(session, pid);
552
    }
553

    
554
    /**
555
     * Retrieve the list of objects present on the MN that match the calling parameters
556
     * 
557
     * @param session - the Session object containing the credentials for the Subject
558
     * @param startTime - Specifies the beginning of the time range from which 
559
     *                    to return object (>=)
560
     * @param endTime - Specifies the beginning of the time range from which 
561
     *                  to return object (>=)
562
     * @param objectFormat - Restrict results to the specified object format
563
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
564
     * @param start - The zero-based index of the first value, relative to the 
565
     *                first record of the resultset that matches the parameters.
566
     * @param count - The maximum number of entries that should be returned in 
567
     *                the response. The Member Node may return less entries 
568
     *                than specified in this value.
569
     * 
570
     * @return objectList - the list of objects matching the criteria
571
     * 
572
     * @throws InvalidToken
573
     * @throws ServiceFailure
574
     * @throws NotAuthorized
575
     * @throws InvalidRequest
576
     * @throws NotImplemented
577
     */
578
    @Override
579
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
580
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
581

    
582
        ObjectList objectList = null;
583

    
584
        try {
585
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
586
        } catch (Exception e) {
587
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
588
        }
589

    
590
        return objectList;
591
    }
592

    
593
    /**
594
     * Retrieve the list of objects present on the MN that match the calling parameters
595
     * 
596
     * @return node - the technical capabilities of the Member Node
597
     * 
598
     * @throws ServiceFailure
599
     * @throws NotAuthorized
600
     * @throws InvalidRequest
601
     * @throws NotImplemented
602
     */
603
    @Override
604
    public Node getCapabilities() throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest {
605

    
606
        String nodeName = null;
607
        String nodeId = null;
608
        String nodeUrl = null;
609
        String nodeDesc = null;
610
        String nodeType = null;
611
        String mnCoreServiceVersion = null;
612
        String mnReadServiceVersion = null;
613
        String mnAuthorizationServiceVersion = null;
614
        String mnStorageServiceVersion = null;
615
        String mnReplicationServiceVersion = null;
616

    
617
        boolean nodeSynchronize = false;
618
        boolean nodeReplicate = false;
619
        boolean mnCoreServiceAvailable = false;
620
        boolean mnReadServiceAvailable = false;
621
        boolean mnAuthorizationServiceAvailable = false;
622
        boolean mnStorageServiceAvailable = false;
623
        boolean mnReplicationServiceAvailable = false;
624

    
625
        try {
626
            // get the properties of the node based on configuration information
627
            nodeId = PropertyService.getProperty("dataone.memberNodeId");
628
            nodeName = PropertyService.getProperty("dataone.nodeName");
629
            nodeUrl = SystemUtil.getContextURL() + "/d1/";
630
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
631
            nodeType = PropertyService.getProperty("dataone.nodeType");
632
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
633
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
634

    
635
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
636
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
637
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
638
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
639
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
640

    
641
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
642
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
643
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
644
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
645
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
646

    
647
        } catch (PropertyNotFoundException pnfe) {
648
            logMetacat.error("MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage());
649

    
650
        }
651

    
652
        // Set the properties of the node based on configuration information and
653
        // calls to current status methods
654
        Node node = new Node();
655
        node.setBaseURL(metacatUrl + "/" + nodeType);
656
        node.setDescription(nodeDesc);
657

    
658
        // set the node's health information
659
        NodeState state = NodeState.UP;
660
        node.setState(state);
661
        // set the ping response to the current value
662
        Ping canPing = new Ping();
663
        canPing.setSuccess(false);
664
        try {
665
            canPing.setSuccess(ping());
666
        } catch (InsufficientResources e) {
667
            e.printStackTrace();
668

    
669
        } catch (UnsupportedType e) {
670
            e.printStackTrace();
671

    
672
        }
673
        node.setPing(canPing);
674

    
675
        NodeReference identifier = new NodeReference();
676
        identifier.setValue(nodeId);
677
        node.setIdentifier(identifier);
678
        node.setName(nodeName + " -- WAR version WARVERSION");
679
        node.setReplicate(new Boolean(nodeReplicate).booleanValue());
680
        node.setSynchronize(new Boolean(nodeSynchronize).booleanValue());
681

    
682
        // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
683
        Services services = new Services();
684

    
685
        Service sMNCore = new Service();
686
        sMNCore.setName("MNCore");
687
        sMNCore.setVersion(mnCoreServiceVersion);
688
        sMNCore.setAvailable(mnCoreServiceAvailable);
689

    
690
        Service sMNRead = new Service();
691
        sMNRead.setName("MNRead");
692
        sMNRead.setVersion(mnReadServiceVersion);
693
        sMNRead.setAvailable(mnReadServiceAvailable);
694

    
695
        Service sMNAuthorization = new Service();
696
        sMNAuthorization.setName("MNAuthorization");
697
        sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
698
        sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
699

    
700
        Service sMNStorage = new Service();
701
        sMNStorage.setName("MNStorage");
702
        sMNStorage.setVersion(mnStorageServiceVersion);
703
        sMNStorage.setAvailable(mnStorageServiceAvailable);
704

    
705
        Service sMNReplication = new Service();
706
        sMNReplication.setName("MNReplication");
707
        sMNReplication.setVersion(mnReplicationServiceVersion);
708
        sMNReplication.setAvailable(mnReplicationServiceAvailable);
709

    
710
        services.addService(sMNRead);
711
        services.addService(sMNCore);
712
        services.addService(sMNAuthorization);
713
        services.addService(sMNStorage);
714
        services.addService(sMNReplication);
715
        node.setServices(services);
716

    
717
        // TODO: Determine the synchronization info without mock values
718
        Synchronization synchronization = new Synchronization();
719
        Schedule schedule = new Schedule();
720
        Date now = new Date();
721
        schedule.setYear(new SimpleDateFormat("yyyy").format(now));
722
        schedule.setMon(new SimpleDateFormat("MM").format(now));
723
        schedule.setMday(new SimpleDateFormat("dd").format(now));
724
        schedule.setWday(new SimpleDateFormat("dd").format(now));
725
        schedule.setHour(new SimpleDateFormat("HH").format(now));
726
        schedule.setMin(new SimpleDateFormat("mm").format(now));
727
        schedule.setSec(new SimpleDateFormat("ss").format(now));
728
        synchronization.setSchedule(schedule);
729
        synchronization.setLastHarvested(now);
730
        synchronization.setLastCompleteHarvest(now);
731
        node.setSynchronization(synchronization);
732
        node.setSynchronize(false);
733
        node.setType(NodeType.MN);
734

    
735
        return node;
736
    }
737

    
738
    /**
739
     * Returns the number of operations that have been serviced by the node 
740
     * over time periods of one and 24 hours.
741
     * 
742
     * @param session - the Session object containing the credentials for the Subject
743
     * @param period - An ISO8601 compatible DateTime range specifying the time 
744
     *                 range for which to return operation statistics.
745
     * @param requestor - Limit to operations performed by given requestor identity.
746
     * @param event -  Enumerated value indicating the type of event being examined
747
     * @param format - Limit to events involving objects of the specified format
748
     * 
749
     * @return the desired log records
750
     * 
751
     * @throws InvalidToken
752
     * @throws ServiceFailure
753
     * @throws NotAuthorized
754
     * @throws InvalidRequest
755
     * @throws NotImplemented
756
     */
757
    @Override
758
    public MonitorList getOperationStatistics(Session session, Date startTime, Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
759
            throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, InsufficientResources, UnsupportedType {
760

    
761
        MonitorList monitorList = new MonitorList();
762

    
763
        try {
764

    
765
            // get log records first
766
            Log logs = getLogRecords(session, startTime, endTime, event, 0, null);
767

    
768
            // TODO: aggregate by day or hour -- needs clarification
769
            int count = 1;
770
            for (LogEntry logEntry : logs.getLogEntryList()) {
771
                Identifier pid = logEntry.getIdentifier();
772
                Date logDate = logEntry.getDateLogged();
773
                // if we are filtering by format
774
                if (formatId != null) {
775
                    SystemMetadata sysmeta = IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
776
                    if (!sysmeta.getFmtid().getValue().equals(formatId.getValue())) {
777
                        // does not match
778
                        continue;
779
                    }
780
                }
781
                MonitorInfo item = new MonitorInfo();
782
                item.setCount(count);
783
                item.setDate(new java.sql.Date(logDate.getTime()));
784
                monitorList.addMonitorInfo(item);
785

    
786
            }
787
        } catch (Exception e) {
788
            e.printStackTrace();
789
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
790
        }
791

    
792
        return monitorList;
793

    
794
    }
795

    
796
    /**
797
     * Low level “are you alive” operation. A valid ping response is 
798
     * indicated by a HTTP status of 200.
799
     * 
800
     * @return true if the service is alive
801
     * 
802
     * @throws InvalidToken
803
     * @throws ServiceFailure
804
     * @throws NotAuthorized
805
     * @throws InvalidRequest
806
     * @throws NotImplemented
807
     */
808
    @Override
809
    public boolean ping() throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, InsufficientResources, UnsupportedType {
810

    
811
        // test if we can get a database connection
812
        boolean alive = false;
813
        int serialNumber = -1;
814
        DBConnection dbConn = null;
815
        try {
816
            dbConn = DBConnectionPool.getDBConnection("MNodeService.ping");
817
            serialNumber = dbConn.getCheckOutSerialNumber();
818
            alive = true;
819
        } catch (SQLException e) {
820
            return alive;
821
        } finally {
822
            // Return the database connection
823
            DBConnectionPool.returnDBConnection(dbConn, serialNumber);
824
        }
825

    
826
        return alive;
827
    }
828

    
829
    /**
830
     * A callback method used by a CN to indicate to a MN that it cannot 
831
     * complete synchronization of the science metadata identified by pid.  Log
832
     * the event in the metacat event log.
833
     * 
834
     * @param session
835
     * @param syncFailed
836
     * 
837
     * @throws ServiceFailure
838
     * @throws NotAuthorized
839
     * @throws InvalidRequest
840
     * @throws NotImplemented
841
     */
842
    @Override
843
    public void synchronizationFailed(Session session, SynchronizationFailed syncFailed) throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest {
844

    
845
        String localId;
846

    
847
        try {
848
            localId = IdentifierManager.getInstance().getLocalId(syncFailed.getPid());
849
        } catch (McdbDocNotFoundException e) {
850
            throw new InvalidRequest("2163", "The identifier specified by " + syncFailed.getPid() + " was not found on this node.");
851

    
852
        }
853
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
854
        // method is changed to include the URL as a parameter
855
        logMetacat.debug("Synchronization for the object identified by " + syncFailed.getPid() + " failed from " + syncFailed.getNodeId()
856
                + " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
857
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
858
        EventLog.getInstance().log(syncFailed.getNodeId(), session.getSubject().getValue(), localId, "synchronization_failed");
859
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
860
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
861

    
862
    }
863

    
864
    /**
865
     * Essentially a get() but with different logging behavior
866
     */
867
    @Override
868
    public InputStream getReplica(Session session, Identifier pid) throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented, ServiceFailure, NotFound {
869

    
870
        InputStream inputStream = null; // bytes to be returned
871
        handler = new MetacatHandler(new Timer());
872
        boolean allowed = false;
873
        String localId; // the metacat docid for the pid
874

    
875
        // get the local docid from Metacat
876
        try {
877
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
878
        } catch (McdbDocNotFoundException e) {
879
            throw new NotFound("1020", "The object specified by " + pid.getValue() + " does not exist at this node.");
880
        }
881

    
882
        Node node = this.getCapabilities();
883
        Subject targetNodeSubject = node.getSubject(0);
884

    
885
        // check for authorization to replicate
886
        allowed = D1Client.getCN().isNodeAuthorized(session, targetNodeSubject, pid, Permission.REPLICATE);
887

    
888
        // if the person is authorized, perform the read
889
        if (allowed) {
890
            try {
891
                inputStream = handler.read(localId);
892
            } catch (Exception e) {
893
                throw new ServiceFailure("1020", "The object specified by " + pid.getValue() + "could not be returned due to error: " + e.getMessage());
894
            }
895
        }
896

    
897
        // if we fail to set the input stream
898
        if (inputStream == null) {
899
            throw new NotFound("1020", "The object specified by " + pid.getValue() + "does not exist at this node.");
900
        }
901

    
902
        // log the replica event
903
        String principal = null;
904
        if (session.getSubject() != null) {
905
            principal = session.getSubject().getValue();
906
        }
907
        EventLog.getInstance().log(null, principal, localId, "getreplica");
908

    
909
        return inputStream;
910
    }
911

    
912
}
(3-3/6)