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

    
34
import org.apache.commons.io.IOUtils;
35
import org.apache.log4j.Logger;
36
import org.dataone.client.D1Client;
37
import org.dataone.client.MNode;
38
import org.dataone.service.util.Constants;
39
import org.dataone.service.exceptions.IdentifierNotUnique;
40
import org.dataone.service.exceptions.InsufficientResources;
41
import org.dataone.service.exceptions.InvalidRequest;
42
import org.dataone.service.exceptions.InvalidSystemMetadata;
43
import org.dataone.service.exceptions.InvalidToken;
44
import org.dataone.service.exceptions.NotAuthorized;
45
import org.dataone.service.exceptions.NotFound;
46
import org.dataone.service.exceptions.NotImplemented;
47
import org.dataone.service.exceptions.ServiceFailure;
48
import org.dataone.service.exceptions.SynchronizationFailed;
49
import org.dataone.service.exceptions.UnsupportedType;
50
import org.dataone.service.mn.tier1.v1.MNCore;
51
import org.dataone.service.mn.tier1.v1.MNRead;
52
import org.dataone.service.mn.tier2.v1.MNAuthorization;
53
import org.dataone.service.mn.tier3.v1.MNStorage;
54
import org.dataone.service.mn.tier4.v1.MNReplication;
55
import org.dataone.service.types.v1.Checksum;
56
import org.dataone.service.types.v1.ChecksumAlgorithm;
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.NodeHealth;
67
import org.dataone.service.types.v1.NodeReference;
68
import org.dataone.service.types.v1.NodeState;
69
import org.dataone.service.types.v1.NodeType;
70
import org.dataone.service.types.v1.ObjectFormat;
71
import org.dataone.service.types.v1.ObjectFormatIdentifier;
72
import org.dataone.service.types.v1.ObjectList;
73
import org.dataone.service.types.v1.Permission;
74
import org.dataone.service.types.v1.Ping;
75
import org.dataone.service.types.v1.Schedule;
76
import org.dataone.service.types.v1.Service;
77
import org.dataone.service.types.v1.Services;
78
import org.dataone.service.types.v1.Session;
79
import org.dataone.service.types.v1.Status;
80
import org.dataone.service.types.v1.Subject;
81
import org.dataone.service.types.v1.Synchronization;
82
import org.dataone.service.types.v1.SystemMetadata;
83
import org.dataone.service.types.v1.util.ChecksumUtil;
84

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

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

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

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

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

    
177
    String localId = null;
178
    boolean allowed = false;
179
    String username = Constants.PUBLIC_SUBJECT;
180
    String[] groupnames = null;
181
    if (session != null ) {
182
    	username = session.getSubject().getValue();
183
    	if (session.getSubjectList() != null) {
184
    		List<Group> groupList = session.getSubjectList().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 InvalidRequest("1322", "The provided identifier was invalid.");
197
    }
198

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

    
207
    }
208
    
209
    // does the subject have DELETE (a D1 CHANGE_PERMISSION level) priveleges on the pid?
210
    allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
211
    
212
    if ( allowed ) {
213
      try {
214
        // delete the document
215
        DocumentImpl.delete(localId, username, groupnames, null);
216
        EventLog.getInstance().log(metacatUrl, username, localId, Event.DELETE.xmlValue());
217

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

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

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

    
233
      }
234

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

    
244

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

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

    
295
    }
296

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

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

    
305
    }
306

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

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

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

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

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

    
390
		return super.create(session, pid, object, sysmeta);
391
	}
392

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

    
420
    boolean result = false;
421

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

    
432
	try {
433
		object = mn.get(session, pid);
434
	} catch (InvalidToken e) {
435
		e.printStackTrace();
436
		throw new ServiceFailure("2151", "Could not retrieve object to replicate (InvalidToken): " + e.getMessage());
437
	} catch (NotFound e) {
438
		e.printStackTrace();
439
		throw new ServiceFailure("2151", "Could not retrieve object to replicate (NotFound): " + e.getMessage());
440
	}
441
	
442
    // add it to local store
443
	Identifier retPid;
444
	try {
445
		retPid = 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
    return result;
459
	  
460
  }
461

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

    
504
  }
505

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

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

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

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

    
603
    return super.getSystemMetadata(session, pid);
604
  }
605

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

    
636
    ObjectList objectList = null;
637
    
638
    try {
639
	    objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime,
640
	        objectFormatId, replicaStatus, start, count);
641
    } catch (Exception e) {
642
		throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
643
	}
644
    
645
    return objectList;
646
  }
647

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

    
673
    boolean nodeSynchronize = false;
674
    boolean nodeReplicate = false;
675
    boolean mnCoreServiceAvailable = false;
676
    boolean mnReadServiceAvailable = false;
677
    boolean mnAuthorizationServiceAvailable = false;
678
    boolean mnStorageServiceAvailable = false;
679
    boolean mnReplicationServiceAvailable = false;
680

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

    
722
    } catch(PropertyNotFoundException pnfe) {
723
        logMetacat.error("MNodeService.getCapabilities(): " +
724
          "property not found: " + pnfe.getMessage());
725
        
726
    }
727

    
728
  	// Set the properties of the node based on configuration information and
729
    // calls to current status methods
730
	  Node node = new Node();
731
	  node.setBaseURL(metacatUrl + "/" + nodeType);
732
	  node.setDescription(nodeDesc);
733
	  
734
	  // set the node's health information
735
	  NodeHealth health = new NodeHealth();
736
	  NodeState state = NodeState.UP;
737
	  health.setState(state);
738
	  // set the ping response to the current value
739
	  Ping canPing = new Ping();
740
	  canPing.setSuccess(false);
741
	  try {
742
	    canPing.setSuccess(ping());
743
    } catch (InsufficientResources e) {
744
	    e.printStackTrace();
745
	    
746
    } catch (UnsupportedType e) {
747
	    e.printStackTrace();
748
	    
749
    }
750
	  health.setPing(canPing);
751
	  // TODO: getStatus() should be consulted here when it's implemented
752
	  Status nodeStatus = new Status();
753
	  nodeStatus.setSuccess(true); 
754
	  nodeStatus.setDateChecked(new Date());
755
	  health.setStatus(nodeStatus);
756
	  node.setHealth(health);
757
	  
758
	  NodeReference identifier = new NodeReference();
759
	  identifier.setValue(nodeId);
760
	  node.setIdentifier(identifier);
761
	  node.setName(nodeName + " -- WAR version WARVERSION");
762
	  node.setReplicate(new Boolean(nodeReplicate).booleanValue());
763
	  node.setSynchronize(new Boolean(nodeSynchronize).booleanValue());
764
	  
765
	  // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
766
	  Services services = new Services();
767

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

    
821
  /**
822
   * Returns the number of operations that have been serviced by the node 
823
   * over time periods of one and 24 hours.
824
   * 
825
   * @param session - the Session object containing the credentials for the Subject
826
   * @param period - An ISO8601 compatible DateTime range specifying the time 
827
   *                 range for which to return operation statistics.
828
   * @param requestor - Limit to operations performed by given requestor identity.
829
   * @param event -  Enumerated value indicating the type of event being examined
830
   * @param format - Limit to events involving objects of the specified format
831
   * 
832
   * @return the desired log records
833
   * 
834
   * @throws InvalidToken
835
   * @throws ServiceFailure
836
   * @throws NotAuthorized
837
   * @throws InvalidRequest
838
   * @throws NotImplemented
839
   */
840
  @Override
841
  public MonitorList getOperationStatistics(Session session, Date startTime,
842
  		Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId) 
843
    throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
844
    InsufficientResources, UnsupportedType {
845
    
846
	  MonitorList monitorList = new MonitorList();
847
	  
848
	  try {
849

    
850
		  // get log records first
851
		  Log logs = getLogRecords(session, startTime, endTime, event, 0, null);
852
		  
853
		  // TODO: aggregate by day or hour -- needs clarification
854
		  int count = 1;
855
		  for (LogEntry logEntry: logs.getLogEntryList()) {
856
			  Identifier pid = logEntry.getIdentifier();
857
			  Date logDate = logEntry.getDateLogged();
858
			  // if we are filtering by format
859
			  if (formatId != null) {
860
				  SystemMetadata sysmeta = IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
861
				  if (!sysmeta.getFmtid().getValue().equals(formatId.getValue())) {
862
					  // does not match
863
					  continue;
864
				  }
865
			  }
866
			  MonitorInfo item = new MonitorInfo();
867
			  item.setCount(count);
868
			  item.setDate(new java.sql.Date(logDate.getTime()));
869
			  monitorList.addMonitorInfo(item);
870
			  
871
		  }
872
	} catch (Exception e) {
873
		e.printStackTrace();
874
		throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
875
	}
876
	  
877
	return monitorList;
878
	  
879
  }
880

    
881
  /**
882
   * Low level “are you alive” operation. A valid ping response is 
883
   * indicated by a HTTP status of 200.
884
   * 
885
   * @return true if the service is alive
886
   * 
887
   * @throws InvalidToken
888
   * @throws ServiceFailure
889
   * @throws NotAuthorized
890
   * @throws InvalidRequest
891
   * @throws NotImplemented
892
   */
893
  @Override
894
  public boolean ping() 
895
    throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
896
    InsufficientResources, UnsupportedType {
897

    
898
    // test if we can get a database connection
899
    boolean alive = false;
900
    int serialNumber = -1;
901
    DBConnection dbConn = null;
902
    try {
903
      dbConn = DBConnectionPool
904
      .getDBConnection("MNodeService.ping");
905
      serialNumber = dbConn.getCheckOutSerialNumber();
906
      alive = true;
907
      
908
    } catch (SQLException e) {
909
      return alive;
910
      
911
    } finally {
912
      // Return the database connection
913
      DBConnectionPool.returnDBConnection(dbConn, serialNumber);
914
    
915
    }
916

    
917
    return alive;
918
  }
919

    
920
  /**
921
   * A callback method used by a CN to indicate to a MN that it cannot 
922
   * complete synchronization of the science metadata identified by pid.  Log
923
   * the event in the metacat event log.
924
   * 
925
   * @param session
926
   * @param syncFailed
927
   * 
928
   * @throws ServiceFailure
929
   * @throws NotAuthorized
930
   * @throws InvalidRequest
931
   * @throws NotImplemented
932
   */
933
  @Override
934
  public void synchronizationFailed(Session session, SynchronizationFailed syncFailed)
935
      throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest {
936

    
937
    String localId;
938
    
939
    try {
940
      localId = IdentifierManager.getInstance().getLocalId(syncFailed.getPid());
941
    } catch (McdbDocNotFoundException e) {
942
      throw new ServiceFailure("2161", "The identifier specified by " +
943
          syncFailed.getPid() + 
944
          " was not found on this node.");
945
      
946
    }
947
    // TODO: update the CN URL below when the CNRead.SynchronizationFailed
948
    // method is changed to include the URL as a parameter
949
    logMetacat.debug("Synchronization for the object identified by " +
950
      syncFailed.getPid() + 
951
      " failed from " +
952
      syncFailed.getNodeId() +
953
      " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
954
    // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
955
    EventLog.getInstance().log(syncFailed.getNodeId(), 
956
      session.getSubject().getValue(), localId, "synchronization_failed");
957
    //EventLog.getInstance().log("CN URL WILL GO HERE", 
958
    //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
959

    
960
  }
961

    
962
  /**
963
   * TODO: Implement this for D1 Tier 4 functionality
964
   */
965
	@Override
966
  public InputStream getReplica(Session session, Identifier pid)
967
    throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
968
    ServiceFailure, NotFound {
969

    
970
		throw new NotImplemented("4870", "isReplicationAuthorized not implemented");
971
  }
972

    
973
}
(3-3/6)