Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000-2011 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author:  $'
7
 *     '$Date:  $'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23

    
24
package edu.ucsb.nceas.metacat.dataone;
25

    
26
import java.io.IOException;
27
import java.io.InputStream;
28
import java.security.NoSuchAlgorithmException;
29
import java.sql.SQLException;
30
import java.text.SimpleDateFormat;
31
import java.util.Date;
32
import java.util.List;
33
import java.util.Timer;
34

    
35
import org.apache.commons.io.IOUtils;
36
import org.apache.log4j.Logger;
37
import org.dataone.client.D1Client;
38
import org.dataone.client.MNode;
39
import org.dataone.service.util.Constants;
40
import org.dataone.service.exceptions.IdentifierNotUnique;
41
import org.dataone.service.exceptions.InsufficientResources;
42
import org.dataone.service.exceptions.InvalidRequest;
43
import org.dataone.service.exceptions.InvalidSystemMetadata;
44
import org.dataone.service.exceptions.InvalidToken;
45
import org.dataone.service.exceptions.NotAuthorized;
46
import org.dataone.service.exceptions.NotFound;
47
import org.dataone.service.exceptions.NotImplemented;
48
import org.dataone.service.exceptions.ServiceFailure;
49
import org.dataone.service.exceptions.SynchronizationFailed;
50
import org.dataone.service.exceptions.UnsupportedType;
51
import org.dataone.service.mn.tier1.v1.MNCore;
52
import org.dataone.service.mn.tier1.v1.MNRead;
53
import org.dataone.service.mn.tier2.v1.MNAuthorization;
54
import org.dataone.service.mn.tier3.v1.MNStorage;
55
import org.dataone.service.mn.tier4.v1.MNReplication;
56
import org.dataone.service.types.v1.Checksum;
57
import org.dataone.service.types.v1.DescribeResponse;
58
import org.dataone.service.types.v1.Event;
59
import org.dataone.service.types.v1.Group;
60
import org.dataone.service.types.v1.Identifier;
61
import org.dataone.service.types.v1.Log;
62
import org.dataone.service.types.v1.LogEntry;
63
import org.dataone.service.types.v1.MonitorInfo;
64
import org.dataone.service.types.v1.MonitorList;
65
import org.dataone.service.types.v1.Node;
66
import org.dataone.service.types.v1.NodeReference;
67
import org.dataone.service.types.v1.NodeState;
68
import org.dataone.service.types.v1.NodeType;
69
import org.dataone.service.types.v1.ObjectFormatIdentifier;
70
import org.dataone.service.types.v1.ObjectList;
71
import org.dataone.service.types.v1.Permission;
72
import org.dataone.service.types.v1.Ping;
73
import org.dataone.service.types.v1.Schedule;
74
import org.dataone.service.types.v1.Service;
75
import org.dataone.service.types.v1.Services;
76
import org.dataone.service.types.v1.Session;
77
import org.dataone.service.types.v1.Subject;
78
import org.dataone.service.types.v1.Synchronization;
79
import org.dataone.service.types.v1.SystemMetadata;
80
import org.dataone.service.types.v1.util.ChecksumUtil;
81

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

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

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

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

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

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

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

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

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

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

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

    
231
      }
232

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

    
242

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

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

    
293
    }
294

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

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

    
303
    }
304

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

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

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

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

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

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

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

    
418
    boolean result = false;
419

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

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

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

    
502
  }
503

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

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

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

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

    
601
    return super.getSystemMetadata(session, pid);
602
  }
603

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

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

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

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

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

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

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

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

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

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

    
872
  /**
873
   * Low level “are you alive” operation. A valid ping response is 
874
   * indicated by a HTTP status of 200.
875
   * 
876
   * @return true if the service is alive
877
   * 
878
   * @throws InvalidToken
879
   * @throws ServiceFailure
880
   * @throws NotAuthorized
881
   * @throws InvalidRequest
882
   * @throws NotImplemented
883
   */
884
  @Override
885
  public boolean ping() 
886
    throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
887
    InsufficientResources, UnsupportedType {
888

    
889
    // test if we can get a database connection
890
    boolean alive = false;
891
    int serialNumber = -1;
892
    DBConnection dbConn = null;
893
    try {
894
      dbConn = DBConnectionPool
895
      .getDBConnection("MNodeService.ping");
896
      serialNumber = dbConn.getCheckOutSerialNumber();
897
      alive = true;
898
      
899
    } catch (SQLException e) {
900
      return alive;
901
      
902
    } finally {
903
      // Return the database connection
904
      DBConnectionPool.returnDBConnection(dbConn, serialNumber);
905
    
906
    }
907

    
908
    return alive;
909
  }
910

    
911
  /**
912
   * A callback method used by a CN to indicate to a MN that it cannot 
913
   * complete synchronization of the science metadata identified by pid.  Log
914
   * the event in the metacat event log.
915
   * 
916
   * @param session
917
   * @param syncFailed
918
   * 
919
   * @throws ServiceFailure
920
   * @throws NotAuthorized
921
   * @throws InvalidRequest
922
   * @throws NotImplemented
923
   */
924
  @Override
925
  public void synchronizationFailed(Session session, SynchronizationFailed syncFailed)
926
      throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest {
927

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

    
951
  }
952

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

    
993
	    // if we fail to set the input stream
994
	    if ( inputStream == null ) {
995
	      throw new NotFound("1020", "The object specified by " + 
996
	                         pid.getValue() +
997
	                         "does not exist at this node.");
998
	    }
999
	    
1000
		// log the replica event
1001
	    String principal = null;
1002
	    if (session.getSubject() != null) {
1003
	    	principal = session.getSubject().getValue();
1004
	    }
1005
	    EventLog.getInstance().log(null, principal, localId, "getreplica");
1006
	    
1007
	    return inputStream;
1008
  }
1009

    
1010
}
(3-3/6)