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.util.ArrayList;
31
import java.util.Calendar;
32
import java.util.Date;
33
import java.util.List;
34
import java.util.Timer;
35

    
36
import javax.servlet.http.HttpServletRequest;
37

    
38
import org.apache.commons.io.IOUtils;
39
import org.apache.log4j.Logger;
40
import org.dataone.client.CNode;
41
import org.dataone.client.D1Client;
42
import org.dataone.client.MNode;
43
import org.dataone.client.auth.CertificateManager;
44
import org.dataone.configuration.Settings;
45
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
import org.dataone.service.exceptions.SynchronizationFailed;
55
import org.dataone.service.exceptions.UnsupportedType;
56
import org.dataone.service.mn.tier1.v1.MNCore;
57
import org.dataone.service.mn.tier1.v1.MNRead;
58
import org.dataone.service.mn.tier2.v1.MNAuthorization;
59
import org.dataone.service.mn.tier3.v1.MNStorage;
60
import org.dataone.service.mn.tier4.v1.MNReplication;
61
import org.dataone.service.types.v1.AccessPolicy;
62
import org.dataone.service.types.v1.Checksum;
63
import org.dataone.service.types.v1.DescribeResponse;
64
import org.dataone.service.types.v1.Event;
65
import org.dataone.service.types.v1.Group;
66
import org.dataone.service.types.v1.Identifier;
67
import org.dataone.service.types.v1.Log;
68
import org.dataone.service.types.v1.LogEntry;
69
import org.dataone.service.types.v1.MonitorInfo;
70
import org.dataone.service.types.v1.MonitorList;
71
import org.dataone.service.types.v1.Node;
72
import org.dataone.service.types.v1.NodeList;
73
import org.dataone.service.types.v1.NodeReference;
74
import org.dataone.service.types.v1.NodeState;
75
import org.dataone.service.types.v1.NodeType;
76
import org.dataone.service.types.v1.ObjectFormatIdentifier;
77
import org.dataone.service.types.v1.ObjectList;
78
import org.dataone.service.types.v1.Permission;
79
import org.dataone.service.types.v1.Ping;
80
import org.dataone.service.types.v1.ReplicationStatus;
81
import org.dataone.service.types.v1.Schedule;
82
import org.dataone.service.types.v1.Service;
83
import org.dataone.service.types.v1.Services;
84
import org.dataone.service.types.v1.Session;
85
import org.dataone.service.types.v1.Subject;
86
import org.dataone.service.types.v1.SubjectList;
87
import org.dataone.service.types.v1.Synchronization;
88
import org.dataone.service.types.v1.SystemMetadata;
89
import org.dataone.service.types.v1.util.ChecksumUtil;
90
import org.dataone.service.util.Constants;
91

    
92
import edu.ucsb.nceas.metacat.DocumentImpl;
93
import edu.ucsb.nceas.metacat.EventLog;
94
import edu.ucsb.nceas.metacat.IdentifierManager;
95
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
96
import edu.ucsb.nceas.metacat.MetacatHandler;
97
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
98
import edu.ucsb.nceas.metacat.database.DBConnection;
99
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
100
import edu.ucsb.nceas.metacat.properties.PropertyService;
101
import edu.ucsb.nceas.metacat.util.SystemUtil;
102
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
103

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

    
134
    /* the logger instance */
135
    private Logger logMetacat = null;
136

    
137
    /**
138
     * Singleton accessor to get an instance of MNodeService.
139
     * 
140
     * @return instance - the instance of MNodeService
141
     */
142
    public static MNodeService getInstance(HttpServletRequest request) {
143
        return new MNodeService(request);
144
    }
145

    
146
    /**
147
     * Constructor, private for singleton access
148
     */
149
    private MNodeService(HttpServletRequest request) {
150
        super(request);
151
        logMetacat = Logger.getLogger(MNodeService.class);
152
        
153
        // set the Member Node certificate file location
154
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
155
    }
156

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

    
177
        String localId = null;
178
        boolean allowed = false;
179
        String username = Constants.SUBJECT_PUBLIC;
180
        String[] groupnames = null;
181
        if (session != null) {
182
            username = session.getSubject().getValue();
183
            if (session.getSubjectInfo() != null) {
184
                List<Group> groupList = session.getSubjectInfo().getGroupList();
185
                if (groupList != null) {
186
                    groupnames = new String[groupList.size()];
187
                    for (int i = 0; i > groupList.size(); i++) {
188
                        groupnames[i] = groupList.get(i).getGroupName();
189
                    }
190
                }
191
            }
192
        }
193

    
194
        // do we have a valid pid?
195
        if (pid == null || pid.getValue().trim().equals("")) {
196
            throw new ServiceFailure("1350", "The provided identifier was invalid.");
197
        }
198

    
199
        // check for the existing identifier
200
        try {
201
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
202
        } catch (McdbDocNotFoundException e) {
203
            throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
204
        }
205

    
206
        // does the subject have DELETE (a D1 CHANGE_PERMISSION level) priveleges on the pid?
207
        allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
208
            
209

    
210
        if (allowed) {
211
            try {
212
                // delete the document
213
                DocumentImpl.delete(localId, username, groupnames, null);
214
                EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
215

    
216
            } catch (McdbDocNotFoundException e) {
217
                throw new NotFound("1340", "The provided identifier was invalid.");
218

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

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

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

    
229
        } else {
230
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to DELETE objects on the Member Node.");
231
        }
232

    
233
        return pid;
234
    }
235

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

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

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

    
278
        // check for the existing identifier
279
        try {
280
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
281
            
282
        } catch (McdbDocNotFoundException e) {
283
            throw new InvalidRequest("1202", "The object with the provided " + 
284
                "identifier was not found.");
285
            
286
        }
287
        
288
        // set the originating node
289
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
290
        sysmeta.setOriginMemberNode(originMemberNode);
291
        
292
        // set the submitter to match the certificate
293
        sysmeta.setSubmitter(subject);
294
        // set the dates
295
        Date now = Calendar.getInstance().getTime();
296
        sysmeta.setDateSysMetadataModified(now);
297
        sysmeta.setDateUploaded(now);
298

    
299
        // does the subject have WRITE ( == update) priveleges on the pid?
300
        allowed = isAuthorized(session, pid, Permission.WRITE);
301

    
302
        if (allowed) {
303

    
304
            // get the existing system metadata for the object
305
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
306

    
307
            // add the newPid to the obsoletedBy list for the existing sysmeta
308
            existingSysMeta.setObsoletedBy(newPid);
309

    
310
            // then update the existing system metadata
311
            updateSystemMetadata(existingSysMeta);
312

    
313
            // prep the new system metadata, add pid to the affected lists
314
            sysmeta.setObsoletes(pid);
315
            //sysmeta.addDerivedFrom(pid);
316

    
317
            isScienceMetadata = isScienceMetadata(sysmeta);
318

    
319
            // do we have XML metadata or a data object?
320
            if (isScienceMetadata) {
321

    
322
                // update the science metadata XML document
323
                // TODO: handle non-XML metadata/data documents (like netCDF)
324
                // TODO: don't put objects into memory using stream to string
325
                String objectAsXML = "";
326
                try {
327
                    objectAsXML = IOUtils.toString(object, "UTF-8");
328
                    localId = insertOrUpdateDocument(objectAsXML, newPid, session, "update");
329
                    // register the newPid and the generated localId
330
                    if (newPid != null) {
331
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
332

    
333
                    }
334

    
335
                } catch (IOException e) {
336
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
337
                    logMetacat.info(msg);
338
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
339

    
340
                }
341

    
342
            } else {
343

    
344
                // update the data object
345
                localId = insertDataObject(object, newPid, session);
346

    
347
            }
348

    
349
            // and insert the new system metadata
350
            insertSystemMetadata(sysmeta);
351

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

    
355
        } else {
356
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
357
                    + " on the Member Node.");
358
        }
359

    
360
        return newPid;
361
    }
362

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

    
366
      // check for null session
367
        if (session == null) {
368
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
369
        }
370
        // set the submitter to match the certificate
371
        sysmeta.setSubmitter(session.getSubject());
372
        // set the originating node
373
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
374
        sysmeta.setOriginMemberNode(originMemberNode);
375
        // set the dates
376
        Date now = Calendar.getInstance().getTime();
377
    sysmeta.setDateSysMetadataModified(now);
378
    sysmeta.setDateUploaded(now);
379
        // call the shared impl
380
        return super.create(session, pid, object, sysmeta);
381
    }
382

    
383
    /**
384
     * Called by a Coordinating Node to request that the Member Node create a 
385
     * copy of the specified object by retrieving it from another Member 
386
     * Node and storing it locally so that it can be made accessible to 
387
     * the DataONE system.
388
     * 
389
     * @param session - the Session object containing the credentials for the Subject
390
     * @param sysmeta - Copy of the CN held system metadata for the object
391
     * @param sourceNode - A reference to node from which the content should be 
392
     *                     retrieved. The reference should be resolved by 
393
     *                     checking the CN node registry.
394
     * 
395
     * @return true if the replication succeeds
396
     * 
397
     * @throws ServiceFailure
398
     * @throws NotAuthorized
399
     * @throws NotImplemented
400
     * @throws UnsupportedType
401
     * @throws InsufficientResources
402
     * @throws InvalidRequest
403
     */
404
    @Override
405
    public boolean replicate(Session session, SystemMetadata sysmeta, NodeReference sourceNode) 
406
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
407
        InsufficientResources, UnsupportedType {
408

    
409
        logMetacat.info("MNodeService.replicate() called with parameters: \n" +
410
            "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
411
            "\tSystemMetadata       = " + sysmeta.toString()              + "\n" +
412
            "\tSource NodeReference ="  + sourceNode.getValue());
413
        
414
        boolean result = false;
415

    
416
        // TODO: check credentials
417

    
418
        // get the referenced object
419
        Identifier pid = sysmeta.getIdentifier();
420

    
421
        // get from the membernode
422
        // TODO: switch credentials for the server retrieval?
423
        MNode mn = D1Client.getMN(sourceNode);
424
        CNode cn = D1Client.getCN();
425
        long serialVersion = sysmeta.getSerialVersion().longValue();
426
        InputStream object = null;
427
        
428
        try {
429
          // session should be null to use the default certificate location set in the Certificate manager
430
            object = mn.getReplica(null, pid);
431
            logMetacat.info("MNodeService.replicate() called for identifier " + pid.getValue());
432

    
433
        } catch (InvalidToken e) {
434
            e.printStackTrace();
435
            throw new ServiceFailure("2151", "Could not retrieve object to replicate (InvalidToken): " + e.getMessage());
436
        } catch (NotFound e) {
437
            e.printStackTrace();
438
            throw new ServiceFailure("2151", "Could not retrieve object to replicate (NotFound): " + e.getMessage());
439
        }
440

    
441
        // add it to local store
442
        Identifier retPid;
443
        try {
444
          // skip the MN.create -- this mutates the system metadata and we dont want it to
445
            retPid = super.create(session, pid, object, sysmeta);
446
            result = (retPid.getValue().equals(pid.getValue()));
447
        } catch (InvalidToken e) {
448
            e.printStackTrace();
449
            throw new ServiceFailure("2151", "Could not save object to local store (InvalidToken): " + e.getMessage());
450
        } catch (IdentifierNotUnique e) {
451
            e.printStackTrace();
452
            throw new ServiceFailure("2151", "Could not save object to local store (IdentifierNotUnique): " + e.getMessage());
453
        } catch (InvalidSystemMetadata e) {
454
            e.printStackTrace();
455
            throw new ServiceFailure("2151", "Could not save object to local store (InvalidSystemMetadata): " + e.getMessage());
456
        }
457

    
458
        try {
459
          // call the CN as the MN to set the replication status
460
            cn.setReplicationStatus(null, pid, sourceNode, ReplicationStatus.COMPLETED, serialVersion);
461
            
462
        } catch (InvalidToken e) {
463
            // TODO Auto-generated catch block
464
            e.printStackTrace();
465
        } catch (NotFound e) {
466
            // TODO Auto-generated catch block
467
            e.printStackTrace();
468
        }
469
        return result;
470

    
471
    }
472

    
473
    /**
474
     * This method provides a lighter weight mechanism than 
475
     * MN_read.getSystemMetadata() for a client to determine basic 
476
     * properties of the referenced object.
477
     * 
478
     * @param session - the Session object containing the credentials for the Subject
479
     * @param pid - the identifier of the object to be described
480
     * 
481
     * @return describeResponse - A set of values providing a basic description 
482
     *                            of the object.
483
     * 
484
     * @throws InvalidToken
485
     * @throws ServiceFailure
486
     * @throws NotAuthorized
487
     * @throws NotFound
488
     * @throws NotImplemented
489
     * @throws InvalidRequest
490
     */
491
    @Override
492
    public DescribeResponse describe(Session session, Identifier pid) 
493
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
494

    
495
      // get system metadata and construct the describe response
496
        SystemMetadata sysmeta = getSystemMetadata(session, pid);
497
        DescribeResponse describeResponse = new DescribeResponse(sysmeta.getFormatId(), sysmeta.getSize(), sysmeta.getDateSysMetadataModified(),
498
                sysmeta.getChecksum());
499

    
500
        return describeResponse;
501

    
502
    }
503

    
504
    /**
505
     * Return the object identified by the given object identifier
506
     * 
507
     * @param session - the Session object containing the credentials for the Subject
508
     * @param pid - the object identifier for the given object
509
     * 
510
     * @return inputStream - the input stream of the given object
511
     * 
512
     * @throws InvalidToken
513
     * @throws ServiceFailure
514
     * @throws NotAuthorized
515
     * @throws InvalidRequest
516
     * @throws NotImplemented
517
     */
518
    @Override
519
    public InputStream get(Session session, Identifier pid) 
520
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
521

    
522
        return super.get(session, pid);
523

    
524
    }
525

    
526
    /**
527
     * Returns a Checksum for the specified object using an accepted hashing algorithm
528
     * 
529
     * @param session - the Session object containing the credentials for the Subject
530
     * @param pid - the object identifier for the given object
531
     * @param algorithm -  the name of an algorithm that will be used to compute 
532
     *                     a checksum of the bytes of the object
533
     * 
534
     * @return checksum - the checksum of the given object
535
     * 
536
     * @throws InvalidToken
537
     * @throws ServiceFailure
538
     * @throws NotAuthorized
539
     * @throws NotFound
540
     * @throws InvalidRequest
541
     * @throws NotImplemented
542
     */
543
    @Override
544
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
545
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
546
        InvalidRequest, NotImplemented {
547

    
548
        Checksum checksum = null;
549

    
550
        InputStream inputStream = get(session, pid);
551

    
552
        try {
553
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
554

    
555
        } catch (NoSuchAlgorithmException e) {
556
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
557
                    + e.getMessage());
558
        } catch (IOException e) {
559
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
560
                    + e.getMessage());
561
        }
562

    
563
        if (checksum == null) {
564
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
565
        }
566

    
567
        return checksum;
568
    }
569

    
570
    /**
571
     * Return the system metadata for a given object
572
     * 
573
     * @param session - the Session object containing the credentials for the Subject
574
     * @param pid - the object identifier for the given object
575
     * 
576
     * @return inputStream - the input stream of the given system metadata object
577
     * 
578
     * @throws InvalidToken
579
     * @throws ServiceFailure
580
     * @throws NotAuthorized
581
     * @throws NotFound
582
     * @throws InvalidRequest
583
     * @throws NotImplemented
584
     */
585
    @Override
586
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) 
587
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
588
        NotImplemented {
589

    
590
        return super.getSystemMetadata(session, pid);
591
    }
592

    
593
    /**
594
     * Retrieve the list of objects present on the MN that match the calling parameters
595
     * 
596
     * @param session - the Session object containing the credentials for the Subject
597
     * @param startTime - Specifies the beginning of the time range from which 
598
     *                    to return object (>=)
599
     * @param endTime - Specifies the beginning of the time range from which 
600
     *                  to return object (>=)
601
     * @param objectFormat - Restrict results to the specified object format
602
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
603
     * @param start - The zero-based index of the first value, relative to the 
604
     *                first record of the resultset that matches the parameters.
605
     * @param count - The maximum number of entries that should be returned in 
606
     *                the response. The Member Node may return less entries 
607
     *                than specified in this value.
608
     * 
609
     * @return objectList - the list of objects matching the criteria
610
     * 
611
     * @throws InvalidToken
612
     * @throws ServiceFailure
613
     * @throws NotAuthorized
614
     * @throws InvalidRequest
615
     * @throws NotImplemented
616
     */
617
    @Override
618
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
619
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
620

    
621
        ObjectList objectList = null;
622

    
623
        try {
624
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
625
        } catch (Exception e) {
626
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
627
        }
628

    
629
        return objectList;
630
    }
631

    
632
    /**
633
     * Return a description of the node's capabilities and services.
634
     * 
635
     * @return node - the technical capabilities of the Member Node
636
     * 
637
     * @throws ServiceFailure
638
     * @throws NotAuthorized
639
     * @throws InvalidRequest
640
     * @throws NotImplemented
641
     */
642
    @Override
643
    public Node getCapabilities() 
644
        throws NotImplemented, ServiceFailure {
645

    
646
        String nodeName = null;
647
        String nodeId = null;
648
        String subject = null;
649
        String nodeDesc = null;
650
        String nodeTypeString = null;
651
        NodeType nodeType = null;
652
        String mnCoreServiceVersion = null;
653
        String mnReadServiceVersion = null;
654
        String mnAuthorizationServiceVersion = null;
655
        String mnStorageServiceVersion = null;
656
        String mnReplicationServiceVersion = null;
657

    
658
        boolean nodeSynchronize = false;
659
        boolean nodeReplicate = false;
660
        boolean mnCoreServiceAvailable = false;
661
        boolean mnReadServiceAvailable = false;
662
        boolean mnAuthorizationServiceAvailable = false;
663
        boolean mnStorageServiceAvailable = false;
664
        boolean mnReplicationServiceAvailable = false;
665

    
666
        try {
667
            // get the properties of the node based on configuration information
668
            nodeName = PropertyService.getProperty("dataone.nodeName");
669
            nodeId = PropertyService.getProperty("dataone.memberNodeId");
670
            subject = PropertyService.getProperty("dataone.subject");
671
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
672
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
673
            nodeType = NodeType.convert(nodeTypeString);
674
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
675
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
676

    
677
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
678
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
679
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
680
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
681
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
682

    
683
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
684
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
685
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
686
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
687
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
688

    
689
            // Set the properties of the node based on configuration information and
690
            // calls to current status methods
691
            String serviceName = SystemUtil.getContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
692
            Node node = new Node();
693
            node.setBaseURL(serviceName + "/" + nodeTypeString);
694
            node.setDescription(nodeDesc);
695

    
696
            // set the node's health information
697
            node.setState(NodeState.UP);
698
            
699
            // set the ping response to the current value
700
            Ping canPing = new Ping();
701
            canPing.setSuccess(false);
702
            try {
703
                canPing.setSuccess(ping());
704
            } catch (InsufficientResources e) {
705
                e.printStackTrace();
706
            }
707
            
708
            node.setPing(canPing);
709

    
710
            NodeReference identifier = new NodeReference();
711
            identifier.setValue(nodeId);
712
            node.setIdentifier(identifier);
713
            Subject s = new Subject();
714
            s.setValue(subject);
715
            node.addSubject(s);
716
            node.setName(nodeName);
717
            node.setReplicate(nodeReplicate);
718
            node.setSynchronize(nodeSynchronize);
719

    
720
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
721
            Services services = new Services();
722

    
723
            Service sMNCore = new Service();
724
            sMNCore.setName("MNCore");
725
            sMNCore.setVersion(mnCoreServiceVersion);
726
            sMNCore.setAvailable(mnCoreServiceAvailable);
727

    
728
            Service sMNRead = new Service();
729
            sMNRead.setName("MNRead");
730
            sMNRead.setVersion(mnReadServiceVersion);
731
            sMNRead.setAvailable(mnReadServiceAvailable);
732

    
733
            Service sMNAuthorization = new Service();
734
            sMNAuthorization.setName("MNAuthorization");
735
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
736
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
737

    
738
            Service sMNStorage = new Service();
739
            sMNStorage.setName("MNStorage");
740
            sMNStorage.setVersion(mnStorageServiceVersion);
741
            sMNStorage.setAvailable(mnStorageServiceAvailable);
742

    
743
            Service sMNReplication = new Service();
744
            sMNReplication.setName("MNReplication");
745
            sMNReplication.setVersion(mnReplicationServiceVersion);
746
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
747

    
748
            services.addService(sMNRead);
749
            services.addService(sMNCore);
750
            services.addService(sMNAuthorization);
751
            services.addService(sMNStorage);
752
            services.addService(sMNReplication);
753
            node.setServices(services);
754

    
755
            // TODO: Allow the metacat admin to determine the schedule
756
            // Set the schedule for synchronization
757
            Synchronization synchronization = new Synchronization();
758
            Schedule schedule = new Schedule();
759
            Date now = new Date();
760
            schedule.setYear("*");
761
            schedule.setMon("*");
762
            schedule.setMday("*");
763
            schedule.setWday("?");
764
            schedule.setHour("*");
765
            schedule.setMin("0/3");
766
            schedule.setSec("10");
767
            synchronization.setSchedule(schedule);
768
            synchronization.setLastHarvested(now);
769
            synchronization.setLastCompleteHarvest(now);
770
            node.setSynchronization(synchronization);
771

    
772
            node.setType(nodeType);
773
            return node;
774

    
775
        } catch (PropertyNotFoundException pnfe) {
776
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
777
            logMetacat.error(msg);
778
            throw new ServiceFailure("2162", msg);
779
        }
780
    }
781

    
782
    /**
783
     * Returns the number of operations that have been serviced by the node 
784
     * over time periods of one and 24 hours.
785
     * 
786
     * @param session - the Session object containing the credentials for the Subject
787
     * @param period - An ISO8601 compatible DateTime range specifying the time 
788
     *                 range for which to return operation statistics.
789
     * @param requestor - Limit to operations performed by given requestor identity.
790
     * @param event -  Enumerated value indicating the type of event being examined
791
     * @param format - Limit to events involving objects of the specified format
792
     * 
793
     * @return the desired log records
794
     * 
795
     * @throws InvalidToken
796
     * @throws ServiceFailure
797
     * @throws NotAuthorized
798
     * @throws InvalidRequest
799
     * @throws NotImplemented
800
     */
801
    public MonitorList getOperationStatistics(Session session, Date startTime, 
802
        Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
803
        throws NotImplemented, ServiceFailure, NotAuthorized, InsufficientResources, UnsupportedType {
804

    
805
        MonitorList monitorList = new MonitorList();
806

    
807
        try {
808

    
809
            // get log records first
810
            Log logs = getLogRecords(session, startTime, endTime, event, 0, null);
811

    
812
            // TODO: aggregate by day or hour -- needs clarification
813
            int count = 1;
814
            for (LogEntry logEntry : logs.getLogEntryList()) {
815
                Identifier pid = logEntry.getIdentifier();
816
                Date logDate = logEntry.getDateLogged();
817
                // if we are filtering by format
818
                if (formatId != null) {
819
                    SystemMetadata sysmeta = IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
820
                    if (!sysmeta.getFormatId().getValue().equals(formatId.getValue())) {
821
                        // does not match
822
                        continue;
823
                    }
824
                }
825
                MonitorInfo item = new MonitorInfo();
826
                item.setCount(count);
827
                item.setDate(new java.sql.Date(logDate.getTime()));
828
                monitorList.addMonitorInfo(item);
829

    
830
            }
831
        } catch (Exception e) {
832
            e.printStackTrace();
833
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
834
        }
835

    
836
        return monitorList;
837

    
838
    }
839

    
840
    /**
841
     * Low level “are you alive” operation. A valid ping response is 
842
     * indicated by a HTTP status of 200.
843
     * 
844
     * @return true if the service is alive
845
     * 
846
     * @throws InvalidToken
847
     * @throws ServiceFailure
848
     * @throws NotImplemented
849
     */
850
    @Override
851
    public boolean ping() 
852
        throws NotImplemented, ServiceFailure, InsufficientResources {
853

    
854
        // test if we can get a database connection
855
        boolean alive = false;
856
        int serialNumber = -1;
857
        DBConnection dbConn = null;
858
        try {
859
            dbConn = DBConnectionPool.getDBConnection("MNodeService.ping");
860
            serialNumber = dbConn.getCheckOutSerialNumber();
861
            alive = true;
862
        } catch (SQLException e) {
863
            return alive;
864
        } finally {
865
            // Return the database connection
866
            DBConnectionPool.returnDBConnection(dbConn, serialNumber);
867
        }
868

    
869
        return alive;
870
    }
871

    
872
    /**
873
     * A callback method used by a CN to indicate to a MN that it cannot 
874
     * complete synchronization of the science metadata identified by pid.  Log
875
     * the event in the metacat event log.
876
     * 
877
     * @param session
878
     * @param syncFailed
879
     * 
880
     * @throws ServiceFailure
881
     * @throws NotAuthorized
882
     * @throws NotImplemented
883
     */
884
    @Override
885
    public void synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
886
        throws NotImplemented, ServiceFailure, NotAuthorized {
887

    
888
        String localId;
889

    
890
        try {
891
            localId = IdentifierManager.getInstance().getLocalId(syncFailed.getPid());
892
        } catch (McdbDocNotFoundException e) {
893
            throw new ServiceFailure("2161", "The identifier specified by " + syncFailed.getPid() + " was not found on this node.");
894

    
895
        }
896
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
897
        // method is changed to include the URL as a parameter
898
        logMetacat.debug("Synchronization for the object identified by " + syncFailed.getPid() + " failed from " + syncFailed.getNodeId()
899
                + " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
900
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
901
        String principal = Constants.SUBJECT_PUBLIC;
902
        if (session != null && session.getSubject() != null) {
903
          principal = session.getSubject().getValue();
904
        }
905
        try {
906
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
907
        } catch (Exception e) {
908
            throw new ServiceFailure("2161", "Could not log the error for: " + syncFailed.getPid());
909
    }
910
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
911
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
912

    
913
    }
914

    
915
    /**
916
     * Essentially a get() but with different logging behavior
917
     */
918
    @Override
919
    public InputStream getReplica(Session session, Identifier pid) 
920
        throws NotAuthorized, NotImplemented, ServiceFailure {
921

    
922
        logMetacat.info("MNodeService.getReplica() called.");
923

    
924
        InputStream inputStream = null; // bytes to be returned
925
        handler = new MetacatHandler(new Timer());
926
        boolean allowed = false;
927
        String localId; // the metacat docid for the pid
928

    
929
        // get the local docid from Metacat
930
        try {
931
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
932
        } catch (McdbDocNotFoundException e) {
933
            throw new ServiceFailure("2181", "The object specified by " + 
934
                    pid.getValue() + " does not exist at this node.");
935
            
936
        }
937

    
938
        Subject targetNodeSubject = session.getSubject();
939

    
940
        // check for authorization to replicate, null session to act as this source MN
941
        try {
942
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid, Permission.REPLICATE);
943
        } catch (InvalidToken e1) {
944
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
945
                + e1.getMessage());
946
            
947
        } catch (NotFound e1) {
948
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
949
                    + e1.getMessage());
950

    
951
        } catch (InvalidRequest e1) {
952
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
953
                    + e1.getMessage());
954

    
955
        }
956

    
957
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
958
            " for identifier " + pid.getValue());
959

    
960
        // if the person is authorized, perform the read
961
        if (allowed) {
962
            try {
963
                inputStream = handler.read(localId);
964
            } catch (Exception e) {
965
                throw new ServiceFailure("1020", "The object specified by " + 
966
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
967
            }
968
        }
969

    
970
        // if we fail to set the input stream
971
        if (inputStream == null) {
972
            throw new ServiceFailure("2181", "The object specified by " + 
973
                pid.getValue() + "does not exist at this node.");
974
        }
975

    
976
        // log the replica event
977
        String principal = null;
978
        if (session.getSubject() != null) {
979
            principal = session.getSubject().getValue();
980
        }
981
        EventLog.getInstance().log(request.getRemoteAddr(), 
982
            request.getHeader("User-Agent"), principal, localId, "replicate");
983

    
984
        return inputStream;
985
    }
986

    
987
    /**
988
     * Set the access policy
989
     */
990
    @Deprecated
991
    @Override
992
    public boolean setAccessPolicy(Session session, Identifier pid,
993
        AccessPolicy policy) 
994
        throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, 
995
        NotImplemented, InvalidRequest {
996
        
997
        throw new NotImplemented("4401", "This method is deprecated for Member Nodes.");
998
        
999
    }
1000

    
1001
    /**
1002
     * A method to notify the Member Node that the authoritative copy of 
1003
     * system metadata on the Coordinating Nodes has changed.
1004
     * 
1005
     * @param session   Session information that contains the identity of the 
1006
     *                  calling user as retrieved from the X.509 certificate 
1007
     *                  which must be traceable to the CILogon service.
1008
     * @param serialVersion   The serialVersion of the system metadata
1009
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1010
     * @throws NotImplemented
1011
     * @throws ServiceFailure
1012
     * @throws NotAuthorized
1013
     * @throws InvalidRequest
1014
     * @throws InvalidToken
1015
     */
1016
    public void systemMetadataChanged(Session session, Identifier pid,
1017
        long serialVersion, Date dateSysMetaLastModified) 
1018
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1019
        InvalidToken {
1020
        
1021
        SystemMetadata currentLocalSysMeta = null;
1022
        SystemMetadata newSysMeta = null;
1023
        CNode cn = D1Client.getCN();
1024
        NodeList nodeList = null;
1025
        Subject callingSubject = null;
1026
        boolean allowed = false;
1027
        
1028
        // are we allowed to call this?
1029
        callingSubject = session.getSubject();
1030
        nodeList = cn.listNodes();
1031
        
1032
        for(Node node : nodeList.getNodeList()) {
1033
            // must be a CN
1034
            if ( node.getType().equals(NodeType.CN)) {
1035
               List<Subject> subjectList = node.getSubjectList();
1036
               // the calling subject must be in the subject list
1037
               if ( subjectList.contains(callingSubject)) {
1038
                   allowed = true;
1039
                   
1040
               }
1041
               
1042
            }
1043
        }
1044
        
1045
        if (!allowed ) {
1046
            String msg = "The subject identified by " + callingSubject.getValue() +
1047
              " is not authorized to call this service.";
1048
            throw new NotAuthorized("1331", msg);
1049
            
1050
        }
1051
        
1052
        // compare what we have locally to what is sent in the change notification
1053
        try {
1054
            currentLocalSysMeta = 
1055
                IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
1056
        
1057
        } catch (McdbDocNotFoundException e) {
1058
            String msg = "SystemMetadata for pid " + pid.getValue() +
1059
              " cpouldn't be updated because it couldn't be found locally: " +
1060
              e.getMessage();
1061
            logMetacat.warn(msg);
1062
            
1063
        }
1064
        
1065
        if (currentLocalSysMeta.getSerialVersion().longValue() < serialVersion ) {
1066
            try {
1067
                newSysMeta = cn.getSystemMetadata(null, pid);
1068
            } catch (NotFound e) {
1069
                // huh? you just said you had it
1070
                logMetacat.error("On updating the local copy of system metadata " + 
1071
                    "for pid " + pid.getValue() +", the CN reports it is not found." +
1072
                    " The error message was: " + e.getMessage());
1073
                
1074
            }
1075
            // update the local copy of system metadata for the pid
1076
            try {
1077
                IdentifierManager.getInstance().updateSystemMetadata(newSysMeta);
1078
                logMetacat.info("Updated local copy of system metadata for pid " +
1079
                    pid.getValue() + " after change notification from the CN.");
1080
                
1081
            } catch (McdbDocNotFoundException e) {
1082
                String msg = "SystemMetadata for pid " + pid.getValue() +
1083
                  " cpouldn't be updated because it couldn't be found: " +
1084
                  e.getMessage();
1085
                logMetacat.warn(msg);
1086
                
1087
            }
1088
        }
1089
        
1090
    }
1091
    
1092
}
(3-3/4)