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.util.Date;
31
import java.util.List;
32

    
33
import org.apache.log4j.Logger;
34
import org.dataone.service.exceptions.IdentifierNotUnique;
35
import org.dataone.service.exceptions.InsufficientResources;
36
import org.dataone.service.exceptions.InvalidRequest;
37
import org.dataone.service.exceptions.InvalidSystemMetadata;
38
import org.dataone.service.exceptions.InvalidToken;
39
import org.dataone.service.exceptions.NotAuthorized;
40
import org.dataone.service.exceptions.NotFound;
41
import org.dataone.service.exceptions.NotImplemented;
42
import org.dataone.service.exceptions.ServiceFailure;
43
import org.dataone.service.exceptions.SynchronizationFailed;
44
import org.dataone.service.exceptions.UnsupportedType;
45
import org.dataone.service.mn.tier1.MNCore;
46
import org.dataone.service.mn.tier1.MNRead;
47
import org.dataone.service.mn.tier2.MNAuthorization;
48
import org.dataone.service.mn.tier3.MNStorage;
49
import org.dataone.service.mn.tier4.MNReplication;
50
import org.dataone.service.types.AccessPolicy;
51
import org.dataone.service.types.Checksum;
52
import org.dataone.service.types.ChecksumAlgorithm;
53
import org.dataone.service.types.DescribeResponse;
54
import org.dataone.service.types.Event;
55
import org.dataone.service.types.Group;
56
import org.dataone.service.types.Identifier;
57
import org.dataone.service.types.Log;
58
import org.dataone.service.types.MonitorList;
59
import org.dataone.service.types.Node;
60
import org.dataone.service.types.NodeReference;
61
import org.dataone.service.types.ObjectFormat;
62
import org.dataone.service.types.ObjectList;
63
import org.dataone.service.types.Permission;
64
import org.dataone.service.types.Session;
65
import org.dataone.service.types.Subject;
66
import org.dataone.service.types.SystemMetadata;
67
import org.dataone.service.types.util.ServiceTypeUtil;
68

    
69
import edu.ucsb.nceas.metacat.DocumentImpl;
70
import edu.ucsb.nceas.metacat.EventLog;
71
import edu.ucsb.nceas.metacat.IdentifierManager;
72
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
73
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
74
import edu.ucsb.nceas.metacat.properties.PropertyService;
75
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
76

    
77
/**
78
 * Represents Metacat's implementation of the DataONE Member Node 
79
 * service API. Methods implement the various MN* interfaces, and methods common
80
 * to both Member Node and Coordinating Node interfaces are found in the
81
 * D1NodeService base class.
82
 */
83
public class MNodeService extends D1NodeService implements MNAuthorization,
84
  MNCore, MNRead, MNReplication, MNStorage {
85

    
86
	/* the instance of the MNodeService object */
87
  private static MNodeService instance = null;
88
  
89
  /* the logger instance */
90
  private Logger logMetacat = null;
91

    
92
  /**
93
   * Singleton accessor to get an instance of MNodeService.
94
   * 
95
   * @return instance - the instance of MNodeService
96
   */
97
  public static MNodeService getInstance() {
98
    if (instance == null) {
99

    
100
      instance = new MNodeService();
101
      
102
    }
103
    
104
    return instance;
105
  }
106
  
107
  /**
108
   * Constructor, private for singleton access
109
   */
110
  private MNodeService() {
111
  	super();
112
    logMetacat = Logger.getLogger(MNodeService.class);
113
        
114
  }
115
    
116
	/**
117
	 * Deletes an object from the Member Node, where the object is either a 
118
	 * data object or a science metadata object.
119
	 * 
120
	 * @param session - the Session object containing the credentials for the Subject
121
	 * @param pid - The object identifier to be deleted
122
	 * 
123
	 * @return pid - the identifier of the object used for the deletion
124
	 * 
125
	 * @throws InvalidToken
126
	 * @throws ServiceFailure
127
	 * @throws NotAuthorized
128
	 * @throws NotFound
129
	 * @throws NotImplemented
130
	 * @throws InvalidRequest
131
	 */
132
  @Override
133
	public Identifier delete(Session session, Identifier pid) 
134
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
135
    NotImplemented, InvalidRequest {
136

    
137
    String localId = null;
138
    boolean allowed = false;
139
    Subject subject = session.getSubject();
140
    List<Group> groupList = session.getSubjectList().getGroupList();
141
    String[] groups = new String[groupList.size()];
142
    IdentifierManager im = IdentifierManager.getInstance();
143
    
144
    // put the group names into a string array
145
    if( session != null ) {
146
      for ( int i = 0; i > groupList.size(); i++ ) {
147
      	groups[i] = groupList.get(i).getGroupName();
148
      	
149
      }
150
    }
151

    
152
    // be sure the user is authenticated for delete()
153
    if (subject.getValue() == null || 
154
        subject.getValue().toLowerCase().equals("public") ) {
155
      throw new NotAuthorized("1320", "The provided identity does not have " +
156
        "permission to DELETE objects on the Member Node.");
157
      
158
    }
159
    
160
    // do we have a valid pid?
161
    if ( pid == null || pid.getValue().trim().equals("") ) {
162
      throw new InvalidRequest("1322", "The provided identifier was invalid.");
163

    
164
    }
165

    
166
    // check for the existing identifier
167
    try {
168
	    localId = im.getLocalId(pid.getValue());
169
    
170
    } catch (McdbDocNotFoundException e) {
171
      throw new InvalidRequest("1322", "The object with the provided " +
172
      	"identifier was not found.");
173

    
174
    }
175
    
176
    // does the subject have DELETE (a D1 CHANGE_PERMISSION level) priveleges on the pid?
177
    allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
178
    
179
    if ( allowed ) {
180
      try {
181
        // delete the document
182
      	DocumentImpl.delete(localId, subject.getValue(), groups, null);
183
        EventLog.getInstance().log(metacatUrl, subject.getValue(), localId, "create");
184

    
185
      } catch (McdbDocNotFoundException e) {
186
        throw new InvalidRequest("1322", "The provided identifier was invalid.");
187

    
188
      } catch (SQLException e) {
189
        throw new ServiceFailure("1350", "There was a problem deleting the object." +
190
        	"The error message was: " + e.getMessage());
191

    
192
      } catch (InsufficientKarmaException e) {
193
        throw new NotAuthorized("1320", "The provided identity does not have " +
194
        "permission to DELETE objects on the Member Node.");
195
 
196
      } catch (Exception e) { // for some reason DocumentImpl throws a general Exception
197
        throw new ServiceFailure("1350", "There was a problem deleting the object." +
198
          	"The error message was: " + e.getMessage());
199

    
200
      }
201

    
202
    } else {
203
      throw new NotAuthorized("1320", "The provided identity does not have " +
204
      "permission to DELETE objects on the Member Node.");
205
    	
206
    }
207
    
208
		return pid;
209
	}
210

    
211

    
212
  /**
213
   * Updates an existing object by creating a new object identified by 
214
   * newPid on the Member Node which explicitly obsoletes the object 
215
   * identified by pid through appropriate changes to the SystemMetadata 
216
   * of pid and newPid
217
   * 
218
	 * @param session - the Session object containing the credentials for the Subject
219
	 * @param pid - The identifier of the object to be updated
220
	 * @param object - the new object bytes
221
	 * @param sysmeta - the new system metadata describing the object
222
	 * 
223
	 * @return newPid - the identifier of the new object
224
	 * 
225
	 * @throws InvalidToken
226
	 * @throws ServiceFailure
227
	 * @throws NotAuthorized
228
	 * @throws NotFound
229
	 * @throws NotImplemented
230
	 * @throws IdentifierNotUnique
231
	 * @throws UnsupportedType
232
	 * @throws InsufficientResources
233
	 * @throws InvalidSystemMetadata
234
	 * @throws InvalidRequest
235
   */
236
  @Override
237
	public Identifier update(Session session, Identifier pid, InputStream object,
238
	  Identifier newPid, SystemMetadata sysmeta) 
239
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
240
    UnsupportedType, InsufficientResources, NotFound, InvalidSystemMetadata, 
241
    NotImplemented, InvalidRequest {
242

    
243
    
244
		return pid;
245
	}
246

    
247
  /**
248
   * Called by a Coordinating Node to request that the Member Node create a 
249
   * copy of the specified object by retrieving it from another Member 
250
   * Node and storing it locally so that it can be made accessible to 
251
   * the DataONE system.
252
   * 
253
	 * @param session - the Session object containing the credentials for the Subject
254
	 * @param sysmeta - Copy of the CN held system metadata for the object
255
	 * @param sourceNode - A reference to node from which the content should be 
256
	 *                     retrieved. The reference should be resolved by 
257
	 *                     checking the CN node registry.
258
	 * 
259
	 * @return true if the replication succeeds
260
	 * 
261
	 * @throws ServiceFailure
262
	 * @throws NotAuthorized
263
	 * @throws NotImplemented
264
	 * @throws UnsupportedType
265
	 * @throws InsufficientResources
266
	 * @throws InvalidRequest
267
   */
268
  @Override
269
	public boolean replicate(Session session, SystemMetadata sysmeta, 
270
	  NodeReference sourceNode)
271
	  throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
272
	  InsufficientResources, UnsupportedType {
273

    
274
		return false;
275
	}
276

    
277
  /**
278
   * This method provides a lighter weight mechanism than 
279
   * MN_read.getSystemMetadata() for a client to determine basic 
280
   * properties of the referenced object.
281
   * 
282
	 * @param session - the Session object containing the credentials for the Subject
283
	 * @param pid - the identifier of the object to be described
284
	 * 
285
	 * @return describeResponse - A set of values providing a basic description 
286
	 *                            of the object.
287
	 * 
288
	 * @throws InvalidToken
289
	 * @throws ServiceFailure
290
	 * @throws NotAuthorized
291
	 * @throws NotFound
292
	 * @throws NotImplemented
293
	 * @throws InvalidRequest
294
   */
295
  @Override
296
	public DescribeResponse describe(Session session, Identifier pid)
297
	  throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
298
	  NotImplemented, InvalidRequest {
299
    
300
  	if(session == null) {
301
      throw new InvalidToken("1370", "The session object is null");
302
      
303
    }
304
    
305
    if(pid == null || pid.getValue().trim().equals(""))
306
    {
307
      throw new InvalidRequest("1362", "The object identifier is null. " +
308
        "A valid identifier is required.");
309
        
310
    }
311
    
312
    SystemMetadata sysmeta = getSystemMetadata(session, pid);
313
    DescribeResponse describeResponse = 
314
    	new DescribeResponse(sysmeta.getObjectFormat(), 
315
      sysmeta.getSize(), sysmeta.getDateSysMetadataModified(), sysmeta.getChecksum());
316
    
317
    return describeResponse;
318

    
319
	}
320

    
321
	/**
322
	 * Return the object identified by the given object identifier
323
	 * 
324
	 * @param session - the Session object containing the credentials for the Subject
325
	 * @param pid - the object identifier for the given object
326
	 * 
327
	 * @return inputStream - the input stream of the given object
328
	 * 
329
	 * @throws InvalidToken
330
	 * @throws ServiceFailure
331
	 * @throws NotAuthorized
332
	 * @throws InvalidRequest
333
	 * @throws NotImplemented
334
	 */
335
  @Override
336
	public InputStream get(Session session, Identifier pid) 
337
	  throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
338
	  NotImplemented, InvalidRequest {
339
  	
340
		return super.get(session, pid);
341
		
342
	}
343

    
344
	/**
345
	 * Returns a Checksum for the specified object using an accepted hashing algorithm
346
	 * 
347
	 * @param session - the Session object containing the credentials for the Subject
348
	 * @param pid - the object identifier for the given object
349
	 * @param algorithm -  the name of an algorithm that will be used to compute 
350
	 *                     a checksum of the bytes of the object
351
	 * 
352
	 * @return checksum - the checksum of the given object
353
	 * 
354
	 * @throws InvalidToken
355
	 * @throws ServiceFailure
356
	 * @throws NotAuthorized
357
	 * @throws NotFound
358
	 * @throws InvalidRequest
359
	 * @throws NotImplemented
360
	 */
361
  @Override
362
	public Checksum getChecksum(Session session, Identifier pid, String algorithm)
363
	  throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
364
	  InvalidRequest, NotImplemented {
365

    
366
  	Checksum checksum = null;
367
  	
368
  	InputStream inputStream = get(session, pid);
369
  	
370
  	try {
371
	    checksum = 
372
	    	ServiceTypeUtil.checksum(inputStream, ChecksumAlgorithm.convert(algorithm));
373
    
374
  	} catch (NoSuchAlgorithmException e) {
375
      throw new ServiceFailure("1410", "The checksum for the object specified by " + 
376
        pid.getValue() +
377
        "could not be returned due to an internal error: " +
378
        e.getMessage());
379
      
380
    } catch (IOException e) {
381
      throw new ServiceFailure("1410", "The checksum for the object specified by " + 
382
        pid.getValue() +
383
        "could not be returned due to an internal error: " +
384
        e.getMessage());
385
      
386
    }
387
  	
388
    if ( checksum == null ) {
389
      throw new ServiceFailure("1410", "The checksum for the object specified by " + 
390
        pid.getValue() +
391
        "could not be returned.");
392
      
393
    }
394
    
395
		return checksum;
396
	}
397

    
398
	/**
399
	 * Return the system metadata for a given object
400
	 * 
401
	 * @param session - the Session object containing the credentials for the Subject
402
	 * @param pid - the object identifier for the given object
403
	 * 
404
	 * @return inputStream - the input stream of the given system metadata object
405
	 * 
406
	 * @throws InvalidToken
407
	 * @throws ServiceFailure
408
	 * @throws NotAuthorized
409
	 * @throws NotFound
410
	 * @throws InvalidRequest
411
	 * @throws NotImplemented
412
	 */
413
  @Override
414
	public SystemMetadata getSystemMetadata(Session session, Identifier pid)
415
	    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
416
	    InvalidRequest, NotImplemented {
417

    
418
		return super.getSystemMetadata(session, pid);
419
	}
420

    
421
	/**
422
	 * Retrieve the list of objects present on the MN that match the calling parameters
423
	 * 
424
	 * @param session - the Session object containing the credentials for the Subject
425
	 * @param startTime - Specifies the beginning of the time range from which 
426
	 *                    to return object (>=)
427
	 * @param endTime - Specifies the beginning of the time range from which 
428
	 *                  to return object (>=)
429
	 * @param objectFormat - Restrict results to the specified object format
430
	 * @param replicaStatus - Indicates if replicated objects should be returned in the list
431
	 * @param start - The zero-based index of the first value, relative to the 
432
	 *                first record of the resultset that matches the parameters.
433
	 * @param count - The maximum number of entries that should be returned in 
434
	 *                the response. The Member Node may return less entries 
435
	 *                than specified in this value.
436
	 * 
437
	 * @return objectList - the list of objects matching the criteria
438
	 * 
439
	 * @throws InvalidToken
440
	 * @throws ServiceFailure
441
	 * @throws NotAuthorized
442
	 * @throws InvalidRequest
443
	 * @throws NotImplemented
444
	 */
445
  @Override
446
	public ObjectList listObjects(Session session, Date startTime, Date endTime,
447
	  ObjectFormat objectFormat, Boolean replicaStatus, Integer start, Integer count)
448
	  throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure,
449
	  InvalidToken {
450

    
451
  	ObjectList objectList = null;
452
  	
453
  	objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime,
454
        objectFormat, replicaStatus, start, count);
455
  	
456
  	if ( objectList == null ) {
457
  		throw new ServiceFailure("1580", "The object list was null.");
458
  	}
459
  	
460
		return objectList;
461
	}
462

    
463
	/**
464
	 * Retrieve the list of objects present on the MN that match the calling parameters
465
	 * 
466
	 * @return node - the technical capabilities of the Member Node
467
	 * 
468
	 * @throws ServiceFailure
469
	 * @throws NotAuthorized
470
	 * @throws InvalidRequest
471
	 * @throws NotImplemented
472
	 */
473
  @Override
474
	public Node getCapabilities() throws NotImplemented, NotAuthorized,
475
	    ServiceFailure, InvalidRequest {
476

    
477
		return null;
478
	}
479

    
480
	/**
481
	 * Returns the number of operations that have been serviced by the node 
482
	 * over time periods of one and 24 hours.
483
	 * 
484
	 * @param session - the Session object containing the credentials for the Subject
485
	 * @param period - An ISO8601 compatible DateTime range specifying the time 
486
	 *                 range for which to return operation statistics.
487
	 * @param requestor - Limit to operations performed by given requestor identity.
488
	 * @param event -  Enumerated value indicating the type of event being examined
489
	 * @param format - Limit to events involving objects of the specified format
490
	 * 
491
	 * @return the desired log records
492
	 * 
493
	 * @throws InvalidToken
494
	 * @throws ServiceFailure
495
	 * @throws NotAuthorized
496
	 * @throws InvalidRequest
497
	 * @throws NotImplemented
498
	 */
499
  @Override
500
	public MonitorList getOperationStatistics(Session session, Integer period,
501
	  Subject requestor, Event event, ObjectFormat format) 
502
    throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
503
    InsufficientResources, UnsupportedType {
504

    
505
		return null;
506
	}
507

    
508
  /**
509
   * Low level “are you alive” operation. A valid ping response is 
510
   * indicated by a HTTP status of 200.
511
   * 
512
   * @return true if the service is alive
513
   * 
514
	 * @throws InvalidToken
515
	 * @throws ServiceFailure
516
	 * @throws NotAuthorized
517
	 * @throws InvalidRequest
518
	 * @throws NotImplemented
519
   */
520
	@Override
521
	public boolean ping() 
522
	  throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
523
	  InsufficientResources, UnsupportedType {
524

    
525
		return true;
526
	}
527

    
528
  /**
529
   * A callback method used by a CN to indicate to a MN that it cannot 
530
   * complete synchronization of the science metadata identified by pid.  Log
531
   * the event in the metacat event log.
532
   * 
533
   * @param session
534
   * @param syncFailed
535
	 * 
536
	 * @throws ServiceFailure
537
	 * @throws NotAuthorized
538
	 * @throws InvalidRequest
539
	 * @throws NotImplemented
540
   */
541
	@Override
542
  public void synchronizationFailed(Session session, SynchronizationFailed syncFailed)
543
      throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest {
544

    
545
		String localId;
546
		
547
		try {
548
	    localId = IdentifierManager.getInstance().getLocalId(syncFailed.getPid().getValue());
549
    } catch (McdbDocNotFoundException e) {
550
      throw new ServiceFailure("2161", "The identifier specified by " +
551
      		syncFailed.getPid().getValue() + 
552
      		" was not found on this node.");
553
      
554
    }
555
		// TODO: update the CN URL below when the CNRead.SynchronizationFailed
556
    // method is changed to include the URL as a parameter
557
    logMetacat.debug("Synchronization for the object identified by " +
558
    	syncFailed.getPid().getValue() + 
559
    	" failed from " +
560
    	"CN URL WILL GO HERE." +
561
    	" Logging the event to the Metacat EventLog as a 'syncFailed' event.");
562
    EventLog.getInstance().log("CN URL WILL GO HERE", 
563
    		session.getSubject().getValue(), localId, "syncFailed");
564

    
565
  }
566

    
567
}
(5-5/8)