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,
122
  MNCore, MNRead, MNReplication, MNStorage {
123

    
124
  /* the instance of the MNodeService object */
125
  private static MNodeService instance = null;
126
  
127
  /* the logger instance */
128
  private Logger logMetacat = null;
129

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

    
138
      instance = new MNodeService();
139
      
140
    }
141
    
142
    return instance;
143
  }
144
  
145
  /**
146
   * Constructor, private for singleton access
147
   */
148
  private MNodeService() {
149
    super();
150
    logMetacat = Logger.getLogger(MNodeService.class);
151
        
152
  }
153
    
154
  /**
155
   * Deletes an object from the Member Node, where the object is either a 
156
   * data object or a science metadata object.
157
   * 
158
   * @param session - the Session object containing the credentials for the Subject
159
   * @param pid - The object identifier to be deleted
160
   * 
161
   * @return pid - the identifier of the object used for the deletion
162
   * 
163
   * @throws InvalidToken
164
   * @throws ServiceFailure
165
   * @throws NotAuthorized
166
   * @throws NotFound
167
   * @throws NotImplemented
168
   * @throws InvalidRequest
169
   */
170
  //@Override
171
  public Identifier delete(Session session, Identifier pid) 
172
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
173
    NotImplemented, InvalidRequest {
174

    
175
    String localId = null;
176
    boolean allowed = false;
177
    String username = Constants.PUBLIC_SUBJECT;
178
    String[] groupnames = null;
179
    if (session != null ) {
180
    	username = session.getSubject().getValue();
181
    	if (session.getSubjectList() != null) {
182
    		List<Group> groupList = session.getSubjectList().getGroupList();
183
    		if (groupList != null) {
184
    			groupnames = new String[groupList.size()];
185
    			for (int i = 0; i > groupList.size(); i++ ) {
186
    				groupnames[i] = groupList.get(i).getGroupName();
187
    			}
188
    		}
189
    	}
190
    }
191
    
192
    // do we have a valid pid?
193
    if ( pid == null || pid.getValue().trim().equals("") ) {
194
      throw new InvalidRequest("1322", "The provided identifier was invalid.");
195
    }
196

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

    
205
    }
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
    
210
    if ( allowed ) {
211
      try {
212
        // delete the document
213
        DocumentImpl.delete(localId, username, groupnames, null);
214
        EventLog.getInstance().log(metacatUrl, username, localId, Event.DELETE.xmlValue());
215

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

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

    
223
      } catch (InsufficientKarmaException e) {
224
        throw new NotAuthorized("1320", "The provided identity does not have " +
225
        "permission to DELETE objects on the Member Node.");
226
 
227
      } catch (Exception e) { // for some reason DocumentImpl throws a general Exception
228
        throw new ServiceFailure("1350", "There was a problem deleting the object." +
229
            "The error message was: " + e.getMessage());
230

    
231
      }
232

    
233
    } else {
234
      throw new NotAuthorized("1320", "The provided identity does not have " +
235
      "permission to DELETE objects on the Member Node.");
236
      
237
    }
238
    
239
    return pid;
240
  }
241

    
242

    
243
  /**
244
   * Updates an existing object by creating a new object identified by 
245
   * newPid on the Member Node which explicitly obsoletes the object 
246
   * identified by pid through appropriate changes to the SystemMetadata 
247
   * of pid and newPid
248
   * 
249
   * @param session - the Session object containing the credentials for the Subject
250
   * @param pid - The identifier of the object to be updated
251
   * @param object - the new object bytes
252
   * @param sysmeta - the new system metadata describing the object
253
   * 
254
   * @return newPid - the identifier of the new object
255
   * 
256
   * @throws InvalidToken
257
   * @throws ServiceFailure
258
   * @throws NotAuthorized
259
   * @throws NotFound
260
   * @throws NotImplemented
261
   * @throws IdentifierNotUnique
262
   * @throws UnsupportedType
263
   * @throws InsufficientResources
264
   * @throws InvalidSystemMetadata
265
   * @throws InvalidRequest
266
   */
267
  //@Override
268
  public Identifier update(Session session, Identifier pid, InputStream object,
269
    Identifier newPid, SystemMetadata sysmeta) 
270
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
271
    UnsupportedType, InsufficientResources, NotFound, InvalidSystemMetadata, 
272
    NotImplemented, InvalidRequest {
273
	  
274
	// check if the pid has been reserved
275
	try {
276
		boolean hasReservation = D1Client.getCN().hasReservation(session, pid);
277
		if (!hasReservation) {
278
			throw new NotAuthorized("", "No reservation for pid: " + pid.getValue());
279
		}
280
	} catch (NotFound e) {
281
		// okay to continue
282
	}
283

    
284
    String localId = null;
285
    boolean allowed = false;
286
    boolean isScienceMetadata = false;
287
    Subject subject = session.getSubject();
288
    
289
    // do we have a valid pid?
290
    if ( pid == null || pid.getValue().trim().equals("") ) {
291
      throw new InvalidRequest("1202", "The provided identifier was invalid.");
292

    
293
    }
294

    
295
    // check for the existing identifier
296
    try {
297
      localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
298

    
299
    } catch (McdbDocNotFoundException e) {
300
      throw new InvalidRequest("1202", "The object with the provided " +
301
        "identifier was not found.");
302

    
303
    }
304

    
305
    // does the subject have WRITE ( == update) priveleges on the pid?
306
    allowed = isAuthorized(session, pid, Permission.WRITE);
307

    
308
    if ( allowed ) {
309
      
310
      // get the existing system metadata for the object
311
      SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
312
      
313
      // add the newPid to the obsoletedBy list for the existing sysmeta
314
      existingSysMeta.setObsoletedBy(newPid);
315
      
316
      // then update the existing system metadata
317
      updateSystemMetadata(existingSysMeta);
318
            
319
      // prep the new system metadata, add pid to the affected lists
320
      sysmeta.setObsoletes(pid);
321
      //sysmeta.addDerivedFrom(pid);
322
      
323
      isScienceMetadata = isScienceMetadata(sysmeta);
324
      
325
      // do we have XML metadata or a data object?
326
      if ( isScienceMetadata ) {
327
        
328
        // update the science metadata XML document
329
        // TODO: handle non-XML metadata/data documents (like netCDF)
330
        // TODO: don't put objects into memory using stream to string
331
        String objectAsXML = "";
332
        try {
333
          objectAsXML = IOUtils.toString(object, "UTF-8");
334
          localId = insertOrUpdateDocument(objectAsXML, newPid, session, "update");
335
          // register the newPid and the generated localId
336
          if ( newPid != null ) {
337
        	  IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
338
            
339
          }
340
          
341
        } catch (IOException e) {
342
          String msg = "The Node is unable to create the object. " +
343
          "There was a problem converting the object to XML";
344
          logMetacat.info(msg);
345
          throw new ServiceFailure("1310", msg + ": " + e.getMessage());
346
        
347
        }
348
        
349
      } else {
350
        
351
        // update the data object
352
        localId = insertDataObject(object, newPid, session);
353
       
354
      }
355
      
356
      // and insert the new system metadata
357
      insertSystemMetadata(sysmeta);
358

    
359
      // log the update event
360
      EventLog.getInstance().log(metacatUrl, subject.getValue(), localId, "update");
361

    
362
    } else {
363
      throw new NotAuthorized("1200", "The provided identity does not have " +
364
      "permission to UPDATE the object identified by " +
365
      pid.getValue() + " on the Member Node.");
366
      
367
    }
368
    
369
    return newPid;
370
  }
371
  
372
  public Identifier create(Session session, Identifier pid,
373
			InputStream object, SystemMetadata sysmeta) throws InvalidToken,
374
			ServiceFailure, NotAuthorized, IdentifierNotUnique,
375
			UnsupportedType, InsufficientResources, InvalidSystemMetadata,
376
			NotImplemented, InvalidRequest {
377

    
378
		// check if the pid has been reserved
379
		try {
380
			boolean hasReservation = D1Client.getCN().hasReservation(session, pid);
381
			if (!hasReservation) {
382
				throw new NotAuthorized("", "No reservation for pid: " + pid.getValue());
383
			}
384
		} catch (NotFound e) {
385
			// okay to continue
386
		}
387

    
388
		return super.create(session, pid, object, sysmeta);
389
	}
390

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

    
417
    boolean result = false;
418

    
419
    // TODO: check credentials
420
    
421
    // get the referenced object
422
    Identifier pid = sysmeta.getIdentifier();
423
    
424
    // get from the membernode
425
    // TODO: switch credentials for the server retrieval?
426
    MNode mn = D1Client.getMN(sourceNode);
427
    InputStream object = null;
428

    
429
	try {
430
		object = mn.get(session, pid);
431
	} catch (InvalidToken e) {
432
		e.printStackTrace();
433
		throw new ServiceFailure("2151", "Could not retrieve object to replicate (InvalidToken): " + e.getMessage());
434
	} catch (NotFound e) {
435
		e.printStackTrace();
436
		throw new ServiceFailure("2151", "Could not retrieve object to replicate (NotFound): " + e.getMessage());
437
	}
438
	
439
    // add it to local store
440
	Identifier retPid;
441
	try {
442
		retPid = create(session, pid, object, sysmeta);
443
		result = (retPid.getValue().equals(pid.getValue()));
444
	} catch (InvalidToken e) {
445
		e.printStackTrace();
446
		throw new ServiceFailure("2151", "Could not save object to local store (InvalidToken): " + e.getMessage());
447
	} catch (IdentifierNotUnique e) {
448
		e.printStackTrace();
449
		throw new ServiceFailure("2151", "Could not save object to local store (IdentifierNotUnique): " + e.getMessage());
450
	} catch (InvalidSystemMetadata e) {
451
		e.printStackTrace();
452
		throw new ServiceFailure("2151", "Could not save object to local store (InvalidSystemMetadata): " + e.getMessage());
453
	}
454
	        
455
    return result;
456
	  
457
  }
458

    
459
  /**
460
   * This method provides a lighter weight mechanism than 
461
   * MN_read.getSystemMetadata() for a client to determine basic 
462
   * properties of the referenced object.
463
   * 
464
   * @param session - the Session object containing the credentials for the Subject
465
   * @param pid - the identifier of the object to be described
466
   * 
467
   * @return describeResponse - A set of values providing a basic description 
468
   *                            of the object.
469
   * 
470
   * @throws InvalidToken
471
   * @throws ServiceFailure
472
   * @throws NotAuthorized
473
   * @throws NotFound
474
   * @throws NotImplemented
475
   * @throws InvalidRequest
476
   */
477
  public DescribeResponse describe(Session session, Identifier pid)
478
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
479
    NotImplemented, InvalidRequest {
480
    
481
    if(session == null) {
482
      throw new InvalidToken("1370", "The session object is null");
483
      
484
    }
485
    
486
    if(pid == null || pid.getValue().trim().equals(""))
487
    {
488
      throw new InvalidRequest("1362", "The object identifier is null. " +
489
        "A valid identifier is required.");
490
        
491
    }
492
    
493
    SystemMetadata sysmeta = getSystemMetadata(session, pid);
494
    DescribeResponse describeResponse = 
495
      new DescribeResponse(sysmeta.getFmtid(), 
496
      sysmeta.getSize(), sysmeta.getDateSysMetadataModified(), sysmeta.getChecksum());
497
    
498
    return describeResponse;
499

    
500
  }
501

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

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

    
546
    Checksum checksum = null;
547
    
548
    InputStream inputStream = get(session, pid);
549
    
550
    try {
551
      checksum = 
552
    	  ChecksumUtil.checksum(inputStream, algorithm);
553
    
554
    } catch (NoSuchAlgorithmException e) {
555
      throw new ServiceFailure("1410", "The checksum for the object specified by " + 
556
        pid.getValue() +
557
        "could not be returned due to an internal error: " +
558
        e.getMessage());
559
      
560
    } catch (IOException e) {
561
      throw new ServiceFailure("1410", "The checksum for the object specified by " + 
562
        pid.getValue() +
563
        "could not be returned due to an internal error: " +
564
        e.getMessage());
565
      
566
    }
567
    
568
    if ( checksum == null ) {
569
      throw new ServiceFailure("1410", "The checksum for the object specified by " + 
570
        pid.getValue() +
571
        "could not be returned.");
572
      
573
    }
574
    
575
    return checksum;
576
  }
577

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

    
598
    return super.getSystemMetadata(session, pid);
599
  }
600

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

    
630
    ObjectList objectList = null;
631
    
632
    try {
633
	    objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime,
634
	        objectFormatId, replicaStatus, start, count);
635
    } catch (Exception e) {
636
		throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
637
	}
638
    
639
    return objectList;
640
  }
641

    
642
  /**
643
   * Retrieve the list of objects present on the MN that match the calling parameters
644
   * 
645
   * @return node - the technical capabilities of the Member Node
646
   * 
647
   * @throws ServiceFailure
648
   * @throws NotAuthorized
649
   * @throws InvalidRequest
650
   * @throws NotImplemented
651
   */
652
  public Node getCapabilities() throws NotImplemented, NotAuthorized,
653
      ServiceFailure, InvalidRequest {
654
    
655
  	String nodeName = null;
656
    String nodeId = null;
657
    String nodeUrl = null;
658
    String nodeDesc = null;
659
    String nodeType = null;
660
    String mnCoreServiceVersion = null;
661
    String mnReadServiceVersion = null;
662
    String mnAuthorizationServiceVersion = null;
663
    String mnStorageServiceVersion = null;
664
    String mnReplicationServiceVersion = null;
665

    
666
    boolean nodeSynchronize = false;
667
    boolean nodeReplicate = false;
668
    boolean mnCoreServiceAvailable = false;
669
    boolean mnReadServiceAvailable = false;
670
    boolean mnAuthorizationServiceAvailable = false;
671
    boolean mnStorageServiceAvailable = false;
672
    boolean mnReplicationServiceAvailable = false;
673

    
674
    try
675
    {
676
    	// get the properties of the node based on configuration information
677
      nodeId = PropertyService.getProperty("dataone.memberNodeId");
678
      nodeName = PropertyService.getProperty("dataone.nodeName");
679
      nodeUrl = SystemUtil.getContextURL() + "/d1/";
680
      nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
681
      nodeType = PropertyService.getProperty("dataone.nodeType");
682
      nodeSynchronize = 
683
      	new Boolean(PropertyService.getProperty(
684
      		"dataone.nodeSynchronize")).booleanValue();
685
      nodeReplicate = 
686
      	new Boolean(PropertyService.getProperty(
687
      		"dataone.nodeReplicate")).booleanValue();
688
      
689
      mnCoreServiceVersion = 
690
      	PropertyService.getProperty("dataone.mnCore.serviceVersion");
691
      mnReadServiceVersion = 
692
      	PropertyService.getProperty("dataone.mnRead.serviceVersion");
693
      mnAuthorizationServiceVersion = 
694
      	PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
695
      mnStorageServiceVersion = 
696
      	PropertyService.getProperty("dataone.mnStorage.serviceVersion");
697
      mnReplicationServiceVersion = 
698
      	PropertyService.getProperty("dataone.mnReplication.serviceVersion");
699
      
700
      mnCoreServiceAvailable = new Boolean(
701
      	PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
702
      mnReadServiceAvailable =  new Boolean(
703
      	PropertyService.getProperty(
704
      		"dataone.mnRead.serviceAvailable")).booleanValue();
705
      mnAuthorizationServiceAvailable =  new Boolean(
706
      	PropertyService.getProperty(
707
      		"dataone.mnAuthorization.serviceAvailable")).booleanValue();
708
      mnStorageServiceAvailable =  new Boolean(
709
      	PropertyService.getProperty(
710
      	  "dataone.mnStorage.serviceAvailable")).booleanValue();
711
      mnReplicationServiceAvailable =  new Boolean(
712
      	PropertyService.getProperty(
713
      	  "dataone.mnReplication.serviceAvailable")).booleanValue();
714

    
715
    } catch(PropertyNotFoundException pnfe) {
716
        logMetacat.error("MNodeService.getCapabilities(): " +
717
          "property not found: " + pnfe.getMessage());
718
        
719
    }
720

    
721
  	// Set the properties of the node based on configuration information and
722
    // calls to current status methods
723
	  Node node = new Node();
724
	  node.setBaseURL(metacatUrl + "/" + nodeType);
725
	  node.setDescription(nodeDesc);
726
	  
727
	  // set the node's health information
728
	  NodeState state = NodeState.UP;
729
	  node.setState(state);
730
	  // set the ping response to the current value
731
	  Ping canPing = new Ping();
732
	  canPing.setSuccess(false);
733
	  try {
734
	    canPing.setSuccess(ping());
735
    } catch (InsufficientResources e) {
736
	    e.printStackTrace();
737
	    
738
    } catch (UnsupportedType e) {
739
	    e.printStackTrace();
740
	    
741
    }
742
	  node.setPing(canPing);
743
	  
744
	  NodeReference identifier = new NodeReference();
745
	  identifier.setValue(nodeId);
746
	  node.setIdentifier(identifier);
747
	  node.setName(nodeName + " -- WAR version WARVERSION");
748
	  node.setReplicate(new Boolean(nodeReplicate).booleanValue());
749
	  node.setSynchronize(new Boolean(nodeSynchronize).booleanValue());
750
	  
751
	  // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
752
	  Services services = new Services();
753

    
754
	  Service sMNCore = new Service();
755
	  sMNCore.setName("MNCore");
756
	  sMNCore.setVersion(mnCoreServiceVersion);
757
	  sMNCore.setAvailable(mnCoreServiceAvailable);
758
	  
759
	  Service sMNRead = new Service();
760
	  sMNRead.setName("MNRead");
761
	  sMNRead.setVersion(mnReadServiceVersion);
762
	  sMNRead.setAvailable(mnReadServiceAvailable);
763
	  
764
	  Service sMNAuthorization = new Service();
765
	  sMNAuthorization.setName("MNAuthorization");
766
	  sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
767
	  sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
768
	  
769
	  Service sMNStorage = new Service();
770
	  sMNStorage.setName("MNStorage");
771
	  sMNStorage.setVersion(mnStorageServiceVersion);
772
	  sMNStorage.setAvailable(mnStorageServiceAvailable);
773
	  
774
	  Service sMNReplication = new Service();
775
	  sMNReplication.setName("MNReplication");
776
	  sMNReplication.setVersion(mnReplicationServiceVersion);
777
	  sMNReplication.setAvailable(mnReplicationServiceAvailable);
778
	  
779
	  services.addService(sMNRead);
780
	  services.addService(sMNCore);
781
	  services.addService(sMNAuthorization);
782
	  services.addService(sMNStorage);
783
	  services.addService(sMNReplication);
784
	  node.setServices(services);
785
	  
786
	  // TODO: Determine the synchronization info without mock values
787
	  Synchronization synchronization = new Synchronization();
788
	  Schedule schedule = new Schedule();
789
	  Date now = new Date();
790
	  schedule.setYear(new SimpleDateFormat("yyyy").format(now));
791
	  schedule.setMon(new SimpleDateFormat("MM").format(now));
792
	  schedule.setMday(new SimpleDateFormat("dd").format(now));
793
	  schedule.setWday(new SimpleDateFormat("dd").format(now));
794
	  schedule.setHour(new SimpleDateFormat("HH").format(now));
795
	  schedule.setMin(new SimpleDateFormat("mm").format(now));
796
	  schedule.setSec(new SimpleDateFormat("ss").format(now));
797
	  synchronization.setSchedule(schedule);
798
	  synchronization.setLastHarvested(now);
799
	  synchronization.setLastCompleteHarvest(now);
800
	  node.setSynchronization(synchronization);
801
	  node.setSynchronize(false);
802
	  node.setType(NodeType.MN);
803
	  
804
	  return node;
805
  }
806

    
807
  /**
808
   * Returns the number of operations that have been serviced by the node 
809
   * over time periods of one and 24 hours.
810
   * 
811
   * @param session - the Session object containing the credentials for the Subject
812
   * @param period - An ISO8601 compatible DateTime range specifying the time 
813
   *                 range for which to return operation statistics.
814
   * @param requestor - Limit to operations performed by given requestor identity.
815
   * @param event -  Enumerated value indicating the type of event being examined
816
   * @param format - Limit to events involving objects of the specified format
817
   * 
818
   * @return the desired log records
819
   * 
820
   * @throws InvalidToken
821
   * @throws ServiceFailure
822
   * @throws NotAuthorized
823
   * @throws InvalidRequest
824
   * @throws NotImplemented
825
   */
826
  public MonitorList getOperationStatistics(Session session, Date startTime,
827
  		Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId) 
828
    throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
829
    InsufficientResources, UnsupportedType {
830
    
831
	  MonitorList monitorList = new MonitorList();
832
	  
833
	  try {
834

    
835
		  // get log records first
836
		  Log logs = getLogRecords(session, startTime, endTime, event, 0, null);
837
		  
838
		  // TODO: aggregate by day or hour -- needs clarification
839
		  int count = 1;
840
		  for (LogEntry logEntry: logs.getLogEntryList()) {
841
			  Identifier pid = logEntry.getIdentifier();
842
			  Date logDate = logEntry.getDateLogged();
843
			  // if we are filtering by format
844
			  if (formatId != null) {
845
				  SystemMetadata sysmeta = IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
846
				  if (!sysmeta.getFmtid().getValue().equals(formatId.getValue())) {
847
					  // does not match
848
					  continue;
849
				  }
850
			  }
851
			  MonitorInfo item = new MonitorInfo();
852
			  item.setCount(count);
853
			  item.setDate(new java.sql.Date(logDate.getTime()));
854
			  monitorList.addMonitorInfo(item);
855
			  
856
		  }
857
	} catch (Exception e) {
858
		e.printStackTrace();
859
		throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
860
	}
861
	  
862
	return monitorList;
863
	  
864
  }
865

    
866
  /**
867
   * Low level “are you alive” operation. A valid ping response is 
868
   * indicated by a HTTP status of 200.
869
   * 
870
   * @return true if the service is alive
871
   * 
872
   * @throws InvalidToken
873
   * @throws ServiceFailure
874
   * @throws NotAuthorized
875
   * @throws InvalidRequest
876
   * @throws NotImplemented
877
   */
878
  public boolean ping() 
879
    throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
880
    InsufficientResources, UnsupportedType {
881

    
882
    // test if we can get a database connection
883
    boolean alive = false;
884
    int serialNumber = -1;
885
    DBConnection dbConn = null;
886
    try {
887
      dbConn = DBConnectionPool
888
      .getDBConnection("MNodeService.ping");
889
      serialNumber = dbConn.getCheckOutSerialNumber();
890
      alive = true;
891
      
892
    } catch (SQLException e) {
893
      return alive;
894
      
895
    } finally {
896
      // Return the database connection
897
      DBConnectionPool.returnDBConnection(dbConn, serialNumber);
898
    
899
    }
900

    
901
    return alive;
902
  }
903

    
904
  /**
905
   * A callback method used by a CN to indicate to a MN that it cannot 
906
   * complete synchronization of the science metadata identified by pid.  Log
907
   * the event in the metacat event log.
908
   * 
909
   * @param session
910
   * @param syncFailed
911
   * 
912
   * @throws ServiceFailure
913
   * @throws NotAuthorized
914
   * @throws InvalidRequest
915
   * @throws NotImplemented
916
   */
917
  public void synchronizationFailed(Session session, SynchronizationFailed syncFailed)
918
      throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest {
919

    
920
    String localId;
921
    
922
    try {
923
      localId = IdentifierManager.getInstance().getLocalId(syncFailed.getPid());
924
    } catch (McdbDocNotFoundException e) {
925
      throw new InvalidRequest("2163", "The identifier specified by " +
926
          syncFailed.getPid() + 
927
          " was not found on this node.");
928
      
929
    }
930
    // TODO: update the CN URL below when the CNRead.SynchronizationFailed
931
    // method is changed to include the URL as a parameter
932
    logMetacat.debug("Synchronization for the object identified by " +
933
      syncFailed.getPid() + 
934
      " failed from " +
935
      syncFailed.getNodeId() +
936
      " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
937
    // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
938
    EventLog.getInstance().log(syncFailed.getNodeId(), 
939
      session.getSubject().getValue(), localId, "synchronization_failed");
940
    //EventLog.getInstance().log("CN URL WILL GO HERE", 
941
    //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
942

    
943
  }
944

    
945
  /**
946
   * Essentially a get() but with different logging behavior and different access control.
947
   */
948
  public InputStream getReplica(Session session, Identifier pid)
949
    throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
950
    ServiceFailure, NotFound {
951
		
952
		InputStream inputStream = null; // bytes to be returned
953
	    handler = new MetacatHandler(new Timer());
954
	    boolean allowed = false;
955
	    String localId; // the metacat docid for the pid
956
	    
957
	    // get the local docid from Metacat
958
	    try {
959
	      localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
960
	    } catch (McdbDocNotFoundException e) {
961
	      throw new NotFound("1020", "The object specified by " + 
962
	                         pid.getValue() +
963
	                         " does not exist at this node.");
964
	    }
965
	    
966
	    Node node = this.getCapabilities();
967
	    Subject targetNodeSubject = node.getSubject(0);
968
	    
969
		// check for authorization to replicate
970
	    allowed = D1Client.getCN().isReplicationAuthorized(session, targetNodeSubject , pid, Permission.REPLICATE);
971
	    
972
	    // if the person is authorized, perform the read
973
	    if (allowed) {
974
	      try {
975
	        inputStream = handler.read(localId);
976
	      } catch (Exception e) {
977
	        throw new ServiceFailure("1020", "The object specified by " + 
978
	            pid.getValue() +
979
	            "could not be returned due to error: " +
980
	            e.getMessage());
981
	      }
982
	    }
983

    
984
	    // if we fail to set the input stream
985
	    if ( inputStream == null ) {
986
	      throw new NotFound("1020", "The object specified by " + 
987
	                         pid.getValue() +
988
	                         "does not exist at this node.");
989
	    }
990
	    
991
		// log the replica event
992
	    String principal = null;
993
	    if (session.getSubject() != null) {
994
	    	principal = session.getSubject().getValue();
995
	    }
996
	    EventLog.getInstance().log(null, principal, localId, "getreplica");
997
	    
998
	    return inputStream;
999
  }
1000

    
1001
}
(3-3/6)