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.ObjectFormat;
72
import org.dataone.service.types.v1.ObjectFormatIdentifier;
73
import org.dataone.service.types.v1.ObjectList;
74
import org.dataone.service.types.v1.Permission;
75
import org.dataone.service.types.v1.Ping;
76
import org.dataone.service.types.v1.Schedule;
77
import org.dataone.service.types.v1.Service;
78
import org.dataone.service.types.v1.Services;
79
import org.dataone.service.types.v1.Session;
80
import org.dataone.service.types.v1.Status;
81
import org.dataone.service.types.v1.Subject;
82
import org.dataone.service.types.v1.Synchronization;
83
import org.dataone.service.types.v1.SystemMetadata;
84
import org.dataone.service.types.v1.util.ChecksumUtil;
85

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

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

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

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

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

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

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

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

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

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

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

    
235
      }
236

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

    
246

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

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

    
297
    }
298

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

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

    
307
    }
308

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

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

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

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

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

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

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

    
422
    boolean result = false;
423

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

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

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

    
506
  }
507

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
919
    return alive;
920
  }
921

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

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

    
962
  }
963

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

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

    
1018
}
(3-3/6)