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.ChecksumAlgorithm;
58
import org.dataone.service.types.v1.DescribeResponse;
59
import org.dataone.service.types.v1.Event;
60
import org.dataone.service.types.v1.Group;
61
import org.dataone.service.types.v1.Identifier;
62
import org.dataone.service.types.v1.Log;
63
import org.dataone.service.types.v1.LogEntry;
64
import org.dataone.service.types.v1.MonitorInfo;
65
import org.dataone.service.types.v1.MonitorList;
66
import org.dataone.service.types.v1.Node;
67
import org.dataone.service.types.v1.NodeHealth;
68
import org.dataone.service.types.v1.NodeReference;
69
import org.dataone.service.types.v1.NodeState;
70
import org.dataone.service.types.v1.NodeType;
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.MetacatHandler;
90
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
91
import edu.ucsb.nceas.metacat.database.DBConnection;
92
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
93
import edu.ucsb.nceas.metacat.properties.PropertyService;
94
import edu.ucsb.nceas.metacat.util.SystemUtil;
95
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
96

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

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

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

    
141
      instance = new MNodeService();
142
      
143
    }
144
    
145
    return instance;
146
  }
147
  
148
  /**
149
   * Constructor, private for singleton access
150
   */
151
  private MNodeService() {
152
    super();
153
    logMetacat = Logger.getLogger(MNodeService.class);
154
        
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, 
176
    NotImplemented, InvalidRequest {
177

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

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

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

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

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

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

    
234
      }
235

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

    
245

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

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

    
296
    }
297

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

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

    
306
    }
307

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

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

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

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

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

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

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

    
421
    boolean result = false;
422

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

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

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

    
505
  }
506

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
918
    return alive;
919
  }
920

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

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

    
961
  }
962

    
963
  /**
964
   * Essentially a get() but with different logging behavior
965
   */
966
	@Override
967
  public InputStream getReplica(Session session, Identifier pid)
968
    throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
969
    ServiceFailure, NotFound {
970
		
971
		InputStream inputStream = null; // bytes to be returned
972
	    handler = new MetacatHandler(new Timer());
973
	    boolean allowed = false;
974
	    String localId; // the metacat docid for the pid
975
	    
976
	    // get the local docid from Metacat
977
	    try {
978
	      localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
979
	    } catch (McdbDocNotFoundException e) {
980
	      throw new NotFound("1020", "The object specified by " + 
981
	                         pid.getValue() +
982
	                         " does not exist at this node.");
983
	    }
984
	    
985
	    Node node = this.getCapabilities();
986
	    Subject targetNodeSubject = node.getSubject(0);
987
	    
988
		// check for authorization to replicate
989
	    allowed = D1Client.getCN().isReplicationAuthorized(session, targetNodeSubject , pid, Permission.REPLICATE);
990
	    
991
	    // if the person is authorized, perform the read
992
	    if (allowed) {
993
	      try {
994
	        inputStream = handler.read(localId);
995
	      } catch (Exception e) {
996
	        throw new ServiceFailure("1020", "The object specified by " + 
997
	            pid.getValue() +
998
	            "could not be returned due to error: " +
999
	            e.getMessage());
1000
	      }
1001
	    }
1002

    
1003
	    // if we fail to set the input stream
1004
	    if ( inputStream == null ) {
1005
	      throw new NotFound("1020", "The object specified by " + 
1006
	                         pid.getValue() +
1007
	                         "does not exist at this node.");
1008
	    }
1009
	    
1010
		// log the replica event
1011
	    String principal = null;
1012
	    if (session.getSubject() != null) {
1013
	    	principal = session.getSubject().getValue();
1014
	    }
1015
	    EventLog.getInstance().log(null, principal, localId, "getreplica");
1016
	    
1017
	    return inputStream;
1018
  }
1019

    
1020
}
(3-3/6)