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.InputStream;
27
import java.math.BigInteger;
28
import java.util.Calendar;
29
import java.util.Date;
30
import java.util.List;
31
import java.util.Set;
32
import java.util.concurrent.locks.Lock;
33

    
34
import javax.servlet.http.HttpServletRequest;
35

    
36
import org.apache.log4j.Logger;
37
import org.dataone.client.CNode;
38
import org.dataone.client.D1Client;
39
import org.dataone.service.cn.v1.CNAuthorization;
40
import org.dataone.service.cn.v1.CNCore;
41
import org.dataone.service.cn.v1.CNRead;
42
import org.dataone.service.cn.v1.CNReplication;
43
import org.dataone.service.exceptions.BaseException;
44
import org.dataone.service.exceptions.IdentifierNotUnique;
45
import org.dataone.service.exceptions.InsufficientResources;
46
import org.dataone.service.exceptions.InvalidRequest;
47
import org.dataone.service.exceptions.InvalidSystemMetadata;
48
import org.dataone.service.exceptions.InvalidToken;
49
import org.dataone.service.exceptions.NotAuthorized;
50
import org.dataone.service.exceptions.NotFound;
51
import org.dataone.service.exceptions.NotImplemented;
52
import org.dataone.service.exceptions.ServiceFailure;
53
import org.dataone.service.exceptions.UnsupportedType;
54
import org.dataone.service.types.v1.AccessPolicy;
55
import org.dataone.service.types.v1.Checksum;
56
import org.dataone.service.types.v1.ChecksumAlgorithmList;
57
import org.dataone.service.types.v1.Identifier;
58
import org.dataone.service.types.v1.Node;
59
import org.dataone.service.types.v1.NodeList;
60
import org.dataone.service.types.v1.NodeReference;
61
import org.dataone.service.types.v1.NodeType;
62
import org.dataone.service.types.v1.ObjectFormat;
63
import org.dataone.service.types.v1.ObjectFormatIdentifier;
64
import org.dataone.service.types.v1.ObjectFormatList;
65
import org.dataone.service.types.v1.ObjectList;
66
import org.dataone.service.types.v1.ObjectLocationList;
67
import org.dataone.service.types.v1.Permission;
68
import org.dataone.service.types.v1.Replica;
69
import org.dataone.service.types.v1.ReplicationPolicy;
70
import org.dataone.service.types.v1.ReplicationStatus;
71
import org.dataone.service.types.v1.Session;
72
import org.dataone.service.types.v1.Subject;
73
import org.dataone.service.types.v1.SystemMetadata;
74

    
75
import edu.ucsb.nceas.metacat.EventLog;
76
import edu.ucsb.nceas.metacat.IdentifierManager;
77
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
78

    
79
/**
80
 * Represents Metacat's implementation of the DataONE Coordinating Node 
81
 * service API. Methods implement the various CN* interfaces, and methods common
82
 * to both Member Node and Coordinating Node interfaces are found in the
83
 * D1NodeService super class.
84
 *
85
 */
86
public class CNodeService extends D1NodeService implements CNAuthorization,
87
    CNCore, CNRead, CNReplication {
88

    
89
  /* the logger instance */
90
  private Logger logMetacat = null;
91

    
92
  /**
93
   * singleton accessor
94
   */
95
  public static CNodeService getInstance(HttpServletRequest request) { 
96
    return new CNodeService(request);
97
  }
98
  
99
  /**
100
   * Constructor, private for singleton access
101
   */
102
  private CNodeService(HttpServletRequest request) {
103
    super(request);
104
    logMetacat = Logger.getLogger(CNodeService.class);
105
        
106
  }
107
    
108
  /**
109
   * Set the replication policy for an object given the object identifier
110
   * 
111
   * @param session - the Session object containing the credentials for the Subject
112
   * @param pid - the object identifier for the given object
113
   * @param policy - the replication policy to be applied
114
   * 
115
   * @return true or false
116
   * 
117
   * @throws NotImplemented
118
   * @throws NotAuthorized
119
   * @throws ServiceFailure
120
   * @throws InvalidRequest
121
   * 
122
   */
123
  @Override
124
  public boolean setReplicationPolicy(Session session, Identifier pid,
125
      ReplicationPolicy policy, long serialVersion) 
126
      throws NotImplemented, NotFound, NotAuthorized, ServiceFailure, 
127
      InvalidRequest, InvalidToken {
128
      
129
      // The lock to be used for this identifier
130
      Lock lock = null;
131
      
132
      // get the subject
133
      Subject subject = session.getSubject();
134
      
135
      // are we allowed to do this?
136
      if (!isAdminAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
137
        if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
138
            throw new NotAuthorized("4881", Permission.CHANGE_PERMISSION
139
                    + " not allowed by " + subject.getValue() + " on "
140
                    + pid.getValue());
141
        }
142
    }
143
    SystemMetadata systemMetadata = null;
144
      try {
145
          lock = HazelcastService.getInstance().getLock(pid.getValue());
146
          lock.lock();
147

    
148
          try {
149
              if ( HazelcastService.getInstance().getSystemMetadataMap().containsKey(pid) ) {
150
                  systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
151
                  
152
              }
153
            
154

    
155
              // does the request have the most current system metadata?
156
              if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
157
                 String msg = "The requested system metadata version number " + 
158
                     serialVersion + " differs from the current version at " +
159
                     systemMetadata.getSerialVersion().longValue() +
160
                     ". Please get the latest copy in order to modify it.";
161
                 throw new InvalidRequest("4883", msg);
162
              }
163
              
164
          } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
165
              throw new NotFound("4884", "No record found for: " + pid.getValue());
166
            
167
          }
168
          
169
          // set the new policy
170
          systemMetadata.setReplicationPolicy(policy);
171
          
172
          // update the metadata
173
          try {
174
              systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
175
              systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
176
              HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
177
            
178
          } catch (Exception e) {
179
              throw new ServiceFailure("4882", e.getMessage());
180
          
181
          }
182
          
183
    } catch (Exception e) {
184
        throw new ServiceFailure("4882", e.getMessage());
185
        
186
    } finally {
187
        lock.unlock();
188
        logMetacat.debug("Unlocked identifier " + pid.getValue());
189
        
190
    }
191
    
192
      return true;
193
  }
194

    
195
  /**
196
   * Set the replication status for an object given the object identifier
197
   * 
198
   * @param session - the Session object containing the credentials for the Subject
199
   * @param pid - the object identifier for the given object
200
   * @param status - the replication status to be applied
201
   * 
202
   * @return true or false
203
   * 
204
   * @throws NotImplemented
205
   * @throws NotAuthorized
206
   * @throws ServiceFailure
207
   * @throws InvalidRequest
208
   * @throws InvalidToken
209
   * @throws NotFound
210
   * 
211
   */
212
  @Override
213
  public boolean setReplicationStatus(Session session, Identifier pid,
214
      NodeReference targetNode, ReplicationStatus status, BaseException failure) 
215
      throws ServiceFailure, NotImplemented, InvalidToken, NotAuthorized, 
216
      InvalidRequest, NotFound {
217
      
218
      // The lock to be used for this identifier
219
      Lock lock = null;
220
      
221
      boolean allowed = false;
222
      int replicaEntryIndex = -1;
223
      List<Replica> replicas = null;
224
      // get the subject
225
      Subject subject = session.getSubject();
226
      logMetacat.debug("ReplicationStatus for identifier " + pid.getValue() +
227
          " is " + status.toString());
228
      
229
      SystemMetadata systemMetadata = null;
230

    
231
      try {
232
          lock = HazelcastService.getInstance().getLock(pid.getValue());
233
          lock.lock();
234
          try {      
235
              systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
236

    
237
              if ( systemMetadata == null ) {
238
                  logMetacat.debug("systemMetadata is null for " + pid.getValue());
239
                  
240
              }
241
              replicas = systemMetadata.getReplicaList();
242
              int count = 0;
243
              
244
              if ( replicas == null || replicas.size() < 1 ) {
245
                  logMetacat.debug("no replicas to evaluate");
246
                  throw new InvalidRequest("4730", "There are no replicas to update.");
247
                  
248
              }
249

    
250
              // find the target replica index in the replica list
251
              for (Replica replica: replicas) {
252
                  String replicaNodeStr = replica.getReplicaMemberNode().getValue();
253
                  String targetNodeStr = targetNode.getValue();
254
                  logMetacat.debug("Comparing " + replicaNodeStr + " to " + targetNodeStr);
255
                  
256
                  if (replicaNodeStr.equals(targetNodeStr)) {
257
                      replicaEntryIndex = count;
258
                      logMetacat.debug("replica entry index is: " + replicaEntryIndex);
259
                      break;
260
                  }
261
                  count++;
262
                  
263
              }
264

    
265
              // are we allowed to do this? only CNs and target MNs are allowed
266
              CNode cn = D1Client.getCN();
267
              List<Node> nodes = cn.listNodes().getNodeList();
268
              
269
              // find the node in the node list
270
              for ( Node node : nodes ) {
271
                  
272
                  NodeReference nodeReference = node.getIdentifier();
273
                  logMetacat.debug("In setReplicationStatus(), Node reference is: " + nodeReference.getValue());
274
                  
275
                  // allow target MN certs and CN certs
276
                  if (targetNode.getValue().equals(nodeReference.getValue()) ||
277
                      node.getType() == NodeType.CN) {
278
                      List<Subject> nodeSubjects = node.getSubjectList();
279
                      
280
                      // check if the session subject is in the node subject list
281
                      for (Subject nodeSubject : nodeSubjects) {
282
                          if ( nodeSubject.equals(subject) ) {
283
                              allowed = true; // subject of session == target node subject
284
                              break;
285
                              
286
                          }
287
                      }                 
288
                  }
289
              }
290

    
291
              if ( !allowed && !isAdminAuthorized(session, pid, Permission.WRITE)) {
292
                  String msg = "The subject identified by " + subject.getValue() +
293
                    " does not have permission to set the replication status for " +
294
                    "the replica identified by " + targetNode.getValue() + ".";
295
                  logMetacat.info(msg);
296
                  throw new NotAuthorized("4720", msg);
297
                  
298
              }
299
              
300
              // was there a failure? log it
301
              if ( failure != null && status == ReplicationStatus.FAILED ) {
302
                 String msg = "The replication request of the object identified by " + 
303
                     pid.getValue() + " failed.  The error message was " +
304
                     failure.getMessage() + ".";
305
              }
306
              
307
          } catch (RuntimeException e) { // Catch is generic since HZ throws RuntimeException
308
            throw new NotFound("4740", "No record found for: " + pid.getValue() +
309
                " : " + e.getMessage());
310
            
311
          }
312
          
313
          // set the status for the replica
314
          if ( replicaEntryIndex != -1 ) {
315
              Replica targetReplica = replicas.get(replicaEntryIndex);
316
              targetReplica.setReplicationStatus(status);
317
              logMetacat.debug("Set the replication status for " + 
318
                  targetReplica.getReplicaMemberNode().getValue() + " to " +
319
                  targetReplica.getReplicationStatus());
320
              
321
          } else {
322
              throw new InvalidRequest("4730", "There are no replicas to update.");
323

    
324
          }
325
          
326
          systemMetadata.setReplicaList(replicas);
327
                
328
          // update the metadata
329
          try {
330
              systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
331
              systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
332
              HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
333
              
334
              if ( status == ReplicationStatus.FAILED && failure != null ) {
335
                  logMetacat.warn("Replication failed for identifier " + pid.getValue() +
336
                      " on target node " + targetNode + ". The exception was: " +
337
                      failure.getMessage());
338
              }
339
          } catch (Exception e) {
340
              throw new ServiceFailure("4700", e.getMessage());
341
          
342
          }
343
          
344
    } catch (Exception e) {
345
        lock.unlock();
346
        logMetacat.debug("Unlocked identifier " + pid.getValue());
347
        
348
    }
349
      
350
      return true;
351
  }
352

    
353
  /**
354
   * Test that the specified relationship between pidOfSubject and pidOfObject exists
355
   * 
356
   * @param session - the Session object containing the credentials for the Subject
357
   * @param node - the node information for the given node be modified
358
   * 
359
   * @return true if the relationship exists
360
   * 
361
   * @throws InvalidToken
362
   * @throws ServiceFailure
363
   * @throws NotAuthorized
364
   * @throws NotFound
365
   * @throws InvalidRequest
366
   * @throws NotImplemented
367
   */
368
  @Override
369
  public boolean assertRelation(Session session, Identifier pidOfSubject, 
370
    String relationship, Identifier pidOfObject) 
371
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
372
    InvalidRequest, NotImplemented {
373
    
374
    // The lock to be used for thyis identifier
375
    Lock lock = null;
376

    
377
    boolean asserted = false;
378
        
379
    // are we allowed to do this?
380
    if (!isAuthorized(session, pidOfSubject, Permission.READ)) {
381
      throw new NotAuthorized("4881", Permission.READ + " not allowed on " + pidOfSubject.getValue());  
382
    }
383
    
384
    SystemMetadata systemMetadata = null;
385
    try {
386
        lock = HazelcastService.getInstance().getLock(pidOfSubject.getValue());
387
        lock.lock();
388
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pidOfSubject);
389
        
390
        
391
        // check relationships
392
        // TODO: use ORE map
393
        if (relationship.equalsIgnoreCase("describes")) {
394
          
395
        }
396
        
397
        if (relationship.equalsIgnoreCase("describedBy")) {
398
          
399
        }
400
        
401
        if (relationship.equalsIgnoreCase("derivedFrom")) {
402
          
403
        }
404
        
405
        if (relationship.equalsIgnoreCase("obsoletes")) {
406
            Identifier pid = systemMetadata.getObsoletes();
407
            if (pid.getValue().equals(pidOfObject.getValue())) {
408
              asserted = true;
409
            
410
        }
411
          //return systemMetadata.getObsoleteList().contains(pidOfObject);
412
        }
413
        if (relationship.equalsIgnoreCase("obsoletedBy")) {
414
            Identifier pid = systemMetadata.getObsoletedBy();
415
            if (pid.getValue().equals(pidOfObject.getValue())) {
416
              asserted = true;
417
            
418
        }
419
          //return systemMetadata.getObsoletedByList().contains(pidOfObject);
420
        }
421
              
422
    } catch (Exception e) {
423
        throw new ServiceFailure("4270", "Could not assert relation for: " + 
424
            pidOfSubject.getValue() +
425
            ". The error message was: " + e.getMessage());
426
      
427
    } finally {
428
        lock.unlock();
429
        logMetacat.debug("Unlocked identifier " + pidOfSubject.getValue());
430

    
431
    }
432
        
433
    return asserted;
434
  }
435
  
436
  /**
437
   * Return the checksum of the object given the identifier 
438
   * 
439
   * @param session - the Session object containing the credentials for the Subject
440
   * @param pid - the object identifier for the given object
441
   * 
442
   * @return checksum - the checksum of the object
443
   * 
444
   * @throws InvalidToken
445
   * @throws ServiceFailure
446
   * @throws NotAuthorized
447
   * @throws NotFound
448
   * @throws NotImplemented
449
   */
450
  @Override
451
  public Checksum getChecksum(Session session, Identifier pid)
452
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
453
    NotImplemented {
454
        
455
    // The lock to be used for thyis identifier
456
    Lock lock = null;
457
    
458
    if (!isAuthorized(session, pid, Permission.READ)) {
459
        throw new NotAuthorized("1400", Permission.READ + " not allowed on " + pid.getValue());  
460
    }
461
    
462
    SystemMetadata systemMetadata = null;
463
    Checksum checksum = null;
464
    
465
    try {
466
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
467
        checksum = systemMetadata.getChecksum();
468

    
469
    } catch (Exception e) {
470
        throw new ServiceFailure("1410", "An error occurred getting the checksum for " + 
471
            pid.getValue() + ". The error message was: " + e.getMessage());
472
      
473
    }
474
    
475
    return checksum;
476
  }
477

    
478
  /**
479
   * Resolve the location of a given object
480
   * 
481
   * @param session - the Session object containing the credentials for the Subject
482
   * @param pid - the object identifier for the given object
483
   * 
484
   * @return objectLocationList - the list of nodes known to contain the object
485
   * 
486
   * @throws InvalidToken
487
   * @throws ServiceFailure
488
   * @throws NotAuthorized
489
   * @throws NotFound
490
   * @throws NotImplemented
491
   */
492
  @Override
493
  public ObjectLocationList resolve(Session session, Identifier pid)
494
    throws InvalidToken, ServiceFailure, NotAuthorized,
495
    NotFound, NotImplemented {
496

    
497
    throw new NotImplemented("4131", "resolve not implemented");
498

    
499
  }
500

    
501
  /**
502
   * Search the metadata catalog for identifiers that match the criteria
503
   * 
504
   * @param session - the Session object containing the credentials for the Subject
505
   * @param queryType - An identifier for the type of query expression 
506
   *                    provided in the query
507
   * @param query -  The criteria for matching the characteristics of the 
508
   *                 metadata objects of interest
509
   * 
510
   * @return objectList - the list of objects matching the criteria
511
   * 
512
   * @throws InvalidToken
513
   * @throws ServiceFailure
514
   * @throws NotAuthorized
515
   * @throws InvalidRequest
516
   * @throws NotImplemented
517
   */
518
  @Override
519
  public ObjectList search(Session session, String queryType, String query)
520
    throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
521
    NotImplemented {
522

    
523
    ObjectList objectList = null;
524
    try {
525
        objectList = 
526
          IdentifierManager.getInstance().querySystemMetadata(
527
              null, //startTime, 
528
              null, //endTime,
529
              null, //objectFormat, 
530
              false, //replicaStatus, 
531
              0, //start, 
532
              -1 //count
533
              );
534
        
535
    } catch (Exception e) {
536
      throw new ServiceFailure("4310", "Error querying system metadata: " + e.getMessage());
537
    }
538

    
539
      return objectList;
540
      
541
    //throw new NotImplemented("4281", "search not implemented");
542
    
543
    // the code block below is from an older implementation
544
    
545
    /*  This block commented out because of the EcoGrid circular dependency.
546
         *  For now, query will not be supported until the circularity can be
547
         *  resolved, probably by moving the ecogrid query syntax transformers
548
         *  directly into the Metacat codebase.  MBJ 2010-02-03
549
         
550
        try {
551
            EcogridQueryParser parser = new EcogridQueryParser(request
552
                    .getReader());
553
            parser.parseXML();
554
            QueryType queryType = parser.getEcogridQuery();
555
            EcogridJavaToMetacatJavaQueryTransformer queryTransformer = 
556
                new EcogridJavaToMetacatJavaQueryTransformer();
557
            QuerySpecification metacatQuery = queryTransformer
558
                    .transform(queryType);
559

    
560
            DBQuery metacat = new DBQuery();
561

    
562
            boolean useXMLIndex = (new Boolean(PropertyService
563
                    .getProperty("database.usexmlindex"))).booleanValue();
564
            String xmlquery = "query"; // we don't care the query in resultset,
565
            // the query can be anything
566
            PrintWriter out = null; // we don't want metacat result, so set out null
567

    
568
            // parameter: queryspecification, user, group, usingIndexOrNot
569
            StringBuffer result = metacat.createResultDocument(xmlquery,
570
                    metacatQuery, out, username, groupNames, useXMLIndex);
571

    
572
            // create result set transfer       
573
            String saxparser = PropertyService.getProperty("xml.saxparser");
574
            MetacatResultsetParser metacatResultsetParser = new MetacatResultsetParser(
575
                    new StringReader(result.toString()), saxparser, queryType
576
                            .getNamespace().get_value());
577
            ResultsetType records = metacatResultsetParser.getEcogridResult();
578

    
579
            System.out
580
                    .println(EcogridResultsetTransformer.toXMLString(records));
581
            response.setContentType("text/xml");
582
            out = response.getWriter();
583
            out.print(EcogridResultsetTransformer.toXMLString(records));
584

    
585
        } catch (Exception e) {
586
            e.printStackTrace();
587
        }*/
588
    
589

    
590
  }
591
  
592
  /**
593
   * Returns the object format registered in the DataONE Object Format 
594
   * Vocabulary for the given format identifier
595
   * 
596
   * @param fmtid - the identifier of the format requested
597
   * 
598
   * @return objectFormat - the object format requested
599
   * 
600
   * @throws ServiceFailure
601
   * @throws NotFound
602
   * @throws InsufficientResources
603
   * @throws NotImplemented
604
   */
605
  @Override
606
  public ObjectFormat getFormat(ObjectFormatIdentifier fmtid)
607
    throws ServiceFailure, NotFound, NotImplemented {
608
     
609
      return ObjectFormatService.getInstance().getFormat(fmtid);
610
      
611
  }
612

    
613
  /**
614
   * Returns a list of all object formats registered in the DataONE Object 
615
   * Format Vocabulary
616
    * 
617
   * @return objectFormatList - The list of object formats registered in 
618
   *                            the DataONE Object Format Vocabulary
619
   * 
620
   * @throws ServiceFailure
621
   * @throws NotImplemented
622
   * @throws InsufficientResources
623
   */
624
  @Override
625
  public ObjectFormatList listFormats() 
626
    throws ServiceFailure, NotImplemented {
627

    
628
    return ObjectFormatService.getInstance().listFormats();
629
  }
630

    
631
  /**
632
   * Returns a list of nodes that have been registered with the DataONE infrastructure
633
    * 
634
   * @return nodeList - List of nodes from the registry
635
   * 
636
   * @throws ServiceFailure
637
   * @throws NotImplemented
638
   */
639
  @Override
640
  public NodeList listNodes() 
641
    throws NotImplemented, ServiceFailure {
642

    
643
    throw new NotImplemented("4800", "listNodes not implemented");
644
  }
645

    
646
  /**
647
   * Provides a mechanism for adding system metadata independently of its 
648
   * associated object, such as when adding system metadata for data objects.
649
    * 
650
   * @param session - the Session object containing the credentials for the Subject
651
   * @param pid - The identifier of the object to register the system metadata against
652
   * @param sysmeta - The system metadata to be registered
653
   * 
654
   * @return true if the registration succeeds
655
   * 
656
   * @throws NotImplemented
657
   * @throws NotAuthorized
658
   * @throws ServiceFailure
659
   * @throws InvalidRequest
660
   * @throws InvalidSystemMetadata
661
   */
662
  @Override
663
  public Identifier registerSystemMetadata(Session session, Identifier pid,
664
      SystemMetadata sysmeta) 
665
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest, 
666
      InvalidSystemMetadata {
667

    
668
      // The lock to be used for this identifier
669
      Lock lock = null;
670

    
671
      // TODO: control who can call this?
672
      if (session == null) {
673
          //TODO: many of the thrown exceptions do not use the correct error codes
674
          //check these against the docs and correct them
675
          throw new NotAuthorized("4861", "No Session - could not authorize for registration." +
676
                  "  If you are not logged in, please do so and retry the request.");
677
      }
678
      
679
      // verify that guid == SystemMetadata.getIdentifier()
680
      logMetacat.debug("Comparing guid|sysmeta_guid: " + pid.getValue() + 
681
          "|" + sysmeta.getIdentifier().getValue());
682
      if (!pid.getValue().equals(sysmeta.getIdentifier().getValue())) {
683
          throw new InvalidRequest("4863", 
684
              "The identifier in method call (" + pid.getValue() + 
685
              ") does not match identifier in system metadata (" +
686
              sysmeta.getIdentifier().getValue() + ").");
687
      }
688

    
689
      try {
690
          lock = HazelcastService.getInstance().getLock(sysmeta.getIdentifier().getValue());
691
          lock.lock();
692
          logMetacat.debug("Checking if identifier exists...");
693
          // Check that the identifier does not already exist
694
          if (HazelcastService.getInstance().getSystemMetadataMap().containsKey(pid)) {
695
              throw new InvalidRequest("4863", 
696
                  "The identifier is already in use by an existing object.");
697
          
698
          }
699
          
700
          // insert the system metadata into the object store
701
          logMetacat.debug("Starting to insert SystemMetadata...");
702
          try {
703
              sysmeta.setSerialVersion(BigInteger.ONE);
704
              sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
705
              HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
706
              
707
          } catch (Exception e) {
708
            logMetacat.error("Problem registering system metadata: " + pid.getValue(), e);
709
              throw new ServiceFailure("4862", "Error inserting system metadata: " + 
710
                  e.getClass() + ": " + e.getMessage());
711
              
712
          }
713
          
714
      } catch (Exception e) {
715
          throw new ServiceFailure("4862", "Error inserting system metadata: " + 
716
                  e.getClass() + ": " + e.getMessage());
717
          
718
      }  finally {
719
          lock.unlock();
720
          logMetacat.debug("Unlocked identifier " + pid.getValue());
721
          
722
      }
723

    
724
      
725
      logMetacat.debug("Returning from registerSystemMetadata");
726
      EventLog.getInstance().log(request.getRemoteAddr(), 
727
          request.getHeader("User-Agent"), session.getSubject().getValue(), 
728
          pid.getValue(), "registerSystemMetadata");
729
      return pid;
730
  }
731
  
732
  /**
733
   * Given an optional scope and format, reserves and returns an identifier 
734
   * within that scope and format that is unique and will not be 
735
   * used by any other sessions. 
736
    * 
737
   * @param session - the Session object containing the credentials for the Subject
738
   * @param pid - The identifier of the object to register the system metadata against
739
   * @param scope - An optional string to be used to qualify the scope of 
740
   *                the identifier namespace, which is applied differently 
741
   *                depending on the format requested. If scope is not 
742
   *                supplied, a default scope will be used.
743
   * @param format - The optional name of the identifier format to be used, 
744
   *                  drawn from a DataONE-specific vocabulary of identifier 
745
   *                 format names, including several common syntaxes such 
746
   *                 as DOI, LSID, UUID, and LSRN, among others. If the 
747
   *                 format is not supplied by the caller, the CN service 
748
   *                 will use a default identifier format, which may change 
749
   *                 over time.
750
   * 
751
   * @return true if the registration succeeds
752
   * 
753
   * @throws InvalidToken
754
   * @throws ServiceFailure
755
   * @throws NotAuthorized
756
   * @throws IdentifierNotUnique
757
   * @throws NotImplemented
758
   */
759
  @Override
760
  public Identifier reserveIdentifier(Session session, Identifier pid)
761
  throws InvalidToken, ServiceFailure,
762
        NotAuthorized, IdentifierNotUnique, NotImplemented, InvalidRequest {
763

    
764
    throw new NotImplemented("4191", "reserveIdentifier not implemented on this node");
765
  }
766
  
767
  @Override
768
  public Identifier generateIdentifier(Session session, String scheme, String fragment)
769
  throws InvalidToken, ServiceFailure,
770
        NotAuthorized, NotImplemented, InvalidRequest {
771
    throw new NotImplemented("4191", "generateIdentifier not implemented on this node");
772
  }
773
  
774
  /**
775
    * Checks whether the pid is reserved by the subject in the session param
776
    * If the reservation is held on the pid by the subject, we return true.
777
    * 
778
   * @param session - the Session object containing the Subject
779
   * @param pid - The identifier to check
780
   * 
781
   * @return true if the reservation exists for the subject/pid
782
   * 
783
   * @throws InvalidToken
784
   * @throws ServiceFailure
785
   * @throws NotFound - when the pid is not found (in use or in reservation)
786
   * @throws NotAuthorized - when the subject does not hold a reservation on the pid
787
   * @throws IdentifierNotUnique - when the pid is in use
788
   * @throws NotImplemented
789
   */
790

    
791
  @Override
792
  public boolean hasReservation(Session session, Identifier pid) 
793
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, IdentifierNotUnique, 
794
      NotImplemented, InvalidRequest {
795
  
796
      throw new NotImplemented("4191", "hasReservation not implemented on this node");
797
  }
798

    
799
  /**
800
   * Changes ownership (RightsHolder) of the specified object to the 
801
   * subject specified by userId
802
    * 
803
   * @param session - the Session object containing the credentials for the Subject
804
   * @param pid - Identifier of the object to be modified
805
   * @param userId - The subject that will be taking ownership of the specified object.
806
   *
807
   * @return pid - the identifier of the modified object
808
   * 
809
   * @throws ServiceFailure
810
   * @throws InvalidToken
811
   * @throws NotFound
812
   * @throws NotAuthorized
813
   * @throws NotImplemented
814
   * @throws InvalidRequest
815
   */  
816
  @Override
817
  public Identifier setRightsHolder(Session session, Identifier pid, Subject userId,
818
      long serialVersion)
819
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized,
820
      NotImplemented, InvalidRequest {
821
      
822
      // The lock to be used for this identifier
823
      Lock lock = null;
824

    
825
      // get the subject
826
      Subject subject = session.getSubject();
827
      
828
      // are we allowed to do this?
829
      if (!isAdminAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
830
        if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
831
            throw new NotAuthorized("4440", "not allowed by "
832
                    + subject.getValue() + " on " + pid.getValue());
833
        }
834
    }
835
    SystemMetadata systemMetadata = null;
836
      try {
837
          lock = HazelcastService.getInstance().getLock(pid.getValue());
838
          
839
          try {
840
              systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
841
              
842
              // does the request have the most current system metadata?
843
              if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
844
                 String msg = "The requested system metadata version number " + 
845
                     serialVersion + " differs from the current version at " +
846
                     systemMetadata.getSerialVersion().longValue() +
847
                     ". Please get the latest copy in order to modify it.";
848
                 throw new InvalidRequest("4442", msg);
849
              }
850
              
851
          } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
852
              throw new NotFound("4460", "No record found for: " + pid.getValue());
853
              
854
          }
855
              
856
          // set the new rights holder
857
          systemMetadata.setRightsHolder(userId);
858
          
859
          // update the metadata
860
          try {
861
              systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
862
              systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
863
              HazelcastService.getInstance().getSystemMetadataMap().put(pid, systemMetadata);
864
              
865
          } catch (Exception e) {
866
          throw new ServiceFailure("4490", e.getMessage());
867
          
868
          }
869
          
870
      } catch (Exception e) {
871
          throw new ServiceFailure("4490", e.getMessage());
872
          
873
      } finally {
874
          lock.unlock();
875
          logMetacat.debug("Unlocked identifier " + pid.getValue());
876
      
877
      }
878
      
879
    return pid;
880
  }
881

    
882
  /**
883
   * Verify that a replication task is authorized by comparing the target node's
884
   * Subject (from the X.509 certificate-derived Session) with the list of 
885
   * subjects in the known, pending replication tasks map.
886
   * 
887
   * @param originatingNodeSession - Session information that contains the 
888
   *                                 identity of the calling user
889
   * @param targetNodeSubject - Subject identifying the target node
890
   * @param pid - the identifier of the object to be replicated
891
   * @param replicatePermission - the execute permission to be granted
892
   * 
893
   * @throws ServiceFailure
894
   * @throws NotImplemented
895
   * @throws InvalidToken
896
   * @throws NotAuthorized
897
   * @throws InvalidRequest
898
   * @throws NotFound
899
   */
900
  @Override
901
  public boolean isNodeAuthorized(Session originatingNodeSession, 
902
    Subject targetNodeSubject, Identifier pid) 
903
    throws NotImplemented, NotAuthorized, InvalidToken, ServiceFailure, 
904
    NotFound, InvalidRequest {
905

    
906
    // The lock to be used for this identifier
907
    Lock lock = null;
908
    
909
    boolean isAllowed = false;
910
    SystemMetadata sysmeta = null;
911
    NodeReference targetNode = null;
912
    
913
    try {
914
      // get the target node reference from the nodes list
915
      CNode cn = D1Client.getCN();
916
      List<Node> nodes = cn.listNodes().getNodeList();
917
      
918
      if ( nodes != null ) {
919
        for (Node node : nodes) {
920
            
921
            for (Subject nodeSubject : node.getSubjectList()) {
922
                
923
                if ( nodeSubject.equals(targetNodeSubject) ) {
924
                    targetNode = node.getIdentifier();
925
                    logMetacat.debug("targetNode is : " + targetNode.getValue());
926
                    break;
927
                }
928
            }
929
            
930
            if ( targetNode != null) { break; }
931
        }
932
        
933
      } else {
934
          String msg = "Couldn't get the node list from the CN";
935
          logMetacat.debug(msg);
936
          throw new ServiceFailure("4872", msg);
937
          
938
      }
939
      
940
      // can't find a node listed with the given subject
941
      if ( targetNode == null ) {
942
          String msg = "There is no Member Node registered with a node subject " +
943
              "matching " + targetNodeSubject.getValue();
944
          logMetacat.info(msg);
945
          throw new ServiceFailure("4872", msg);
946
          
947
      }
948
      
949
      //lock, get, and unlock the pid
950
      lock = HazelcastService.getInstance().getLock(pid.getValue());
951
      lock.lock();
952
      logMetacat.debug("Getting system metadata for identifier " + pid.getValue());
953
      
954
      sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
955

    
956
      if ( sysmeta != null ) {
957
          
958
          List<Replica> replicaList = sysmeta.getReplicaList();
959
          
960
          if ( replicaList != null ) {
961
              
962
              // find the replica with the status set to 'requested'
963
              for (Replica replica : replicaList) {
964
                  ReplicationStatus status = replica.getReplicationStatus();
965
                  NodeReference listedNode = replica.getReplicaMemberNode();
966
                  if ( listedNode != null && targetNode != null ) {
967
                      logMetacat.debug("Comparing " + listedNode.getValue()
968
                              + " to " + targetNode.getValue());
969
                      
970
                      if (listedNode.getValue().equals(targetNode.getValue())
971
                              && status.equals(ReplicationStatus.REQUESTED)) {
972
                          isAllowed = true;
973
                          break;
974

    
975
                      }
976
                  }
977
              }
978
          }
979
          logMetacat.debug("The " + targetNode.getValue() + " is allowed " +
980
              "to replicate: " + isAllowed + " for " + pid.getValue());
981

    
982
          
983
      } else {
984
          logMetacat.debug("System metadata for identifier " + pid.getValue() +
985
          " is null.");          
986
          
987
      }
988

    
989
    } catch (RuntimeException e) {
990
    	  ServiceFailure sf = new ServiceFailure("4872", 
991
                "Runtime Exception: Couldn't determine if node is allowed: " + 
992
                e.getCause().getMessage());
993
    	  sf.initCause(e);
994
        throw sf;
995
        
996
    } finally {
997
      // always unlock the pid
998
      lock.unlock();
999
      logMetacat.debug("Unlocked identifier " + pid.getValue());
1000

    
1001
    }
1002
      
1003
    return isAllowed;
1004
    
1005
  }
1006

    
1007
  /**
1008
   * Adds a new object to the Node, where the object is a science metadata object.
1009
   * 
1010
   * @param session - the Session object containing the credentials for the Subject
1011
   * @param pid - The object identifier to be created
1012
   * @param object - the object bytes
1013
   * @param sysmeta - the system metadata that describes the object  
1014
   * 
1015
   * @return pid - the object identifier created
1016
   * 
1017
   * @throws InvalidToken
1018
   * @throws ServiceFailure
1019
   * @throws NotAuthorized
1020
   * @throws IdentifierNotUnique
1021
   * @throws UnsupportedType
1022
   * @throws InsufficientResources
1023
   * @throws InvalidSystemMetadata
1024
   * @throws NotImplemented
1025
   * @throws InvalidRequest
1026
   */
1027
  public Identifier create(Session session, Identifier pid, InputStream object,
1028
    SystemMetadata sysmeta) 
1029
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
1030
    UnsupportedType, InsufficientResources, InvalidSystemMetadata, 
1031
    NotImplemented, InvalidRequest {
1032
      
1033
      
1034
      // The lock to be used for this identifier
1035
      Lock lock = null;
1036
      
1037
      try {
1038
        // are we allowed?
1039
          boolean isAllowed = false;
1040
          CNode cn = D1Client.getCN();
1041
          NodeList nodeList = cn.listNodes();
1042
          
1043
          for (Node node : nodeList.getNodeList()) {
1044
              if ( node.getType().equals(NodeType.CN) ) {
1045
                  
1046
                  List<Subject> subjects = node.getSubjectList();
1047
                  for (Subject subject : subjects) {
1048
                     if (subject.equals(session.getSubject())) {
1049
                         isAllowed = true;
1050
                         break;
1051
                     }
1052
                  }
1053
              }
1054
          }
1055

    
1056
          // proceed if we're called by a CN
1057
          if ( isAllowed ) {
1058
              // create the coordinating node version of the document      
1059
              lock = HazelcastService.getInstance().getLock(pid.getValue());
1060
              lock.lock();
1061
              sysmeta.setSerialVersion(BigInteger.ONE);
1062
              sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
1063
              pid = super.create(session, pid, object, sysmeta);
1064

    
1065
          } else {
1066
              String msg = "The subject listed as " + session.getSubject().getValue() + 
1067
                  " isn't allowed to call create() on a Coordinating Node.";
1068
              logMetacat.info(msg);
1069
              throw new NotAuthorized("1100", msg);
1070
          }
1071
          
1072
      } catch (RuntimeException e) {
1073
          // Convert Hazelcast runtime exceptions to service failures
1074
          String msg = "There was a problem creating the object identified by " +
1075
              pid.getValue() + ". There error message was: " + e.getMessage();
1076
          throw new ServiceFailure("4893", msg);
1077
          
1078
      } finally {
1079
    	  if (lock != null) {
1080
	          lock.unlock();
1081
	          logMetacat.debug("Unlocked identifier " + pid.getValue());
1082
    	  }
1083
      }
1084
      
1085
      return pid;
1086

    
1087
  }
1088

    
1089
  /**
1090
   * Set access for a given object using the object identifier and a Subject
1091
   * under a given Session.
1092
   * 
1093
   * @param session - the Session object containing the credentials for the Subject
1094
   * @param pid - the object identifier for the given object to apply the policy
1095
   * @param policy - the access policy to be applied
1096
   * 
1097
   * @return true if the application of the policy succeeds
1098
   * @throws InvalidToken
1099
   * @throws ServiceFailure
1100
   * @throws NotFound
1101
   * @throws NotAuthorized
1102
   * @throws NotImplemented
1103
   * @throws InvalidRequest
1104
   */
1105
  public boolean setAccessPolicy(Session session, Identifier pid, 
1106
      AccessPolicy accessPolicy, long serialVersion) 
1107
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, 
1108
      NotImplemented, InvalidRequest {
1109
      
1110
      // The lock to be used for this identifier
1111
      Lock lock = null;
1112
      
1113
      boolean success = false;
1114
      
1115
      // get the subject
1116
      Subject subject = session.getSubject();
1117
      
1118
      // are we allowed to do this?
1119
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
1120
          throw new NotAuthorized("4420", "not allowed by " + subject.getValue() + 
1121
          " on " + pid.getValue());  
1122
      }
1123
      
1124
      SystemMetadata systemMetadata = null;
1125
      try {
1126
          lock = HazelcastService.getInstance().getLock(pid.getValue());
1127
          lock.lock();
1128
          
1129
          try {
1130
              systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1131

    
1132
              // does the request have the most current system metadata?
1133
              if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
1134
                 String msg = "The requested system metadata version number " + 
1135
                     serialVersion + " differs from the current version at " +
1136
                     systemMetadata.getSerialVersion().longValue() +
1137
                     ". Please get the latest copy in order to modify it.";
1138
                 throw new InvalidRequest("4402", msg);
1139
              }
1140
              
1141
          } catch (Exception e) {
1142
              // convert Hazelcast RuntimeException to NotFound
1143
              throw new NotFound("4400", "No record found for: " + pid);
1144
            
1145
          }
1146
              
1147
          // set the access policy
1148
          systemMetadata.setAccessPolicy(accessPolicy);
1149
          
1150
          // update the system metadata
1151
          try {
1152
              systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1153
              systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1154
              HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1155
            
1156
          } catch (Exception e) {
1157
              // convert Hazelcast RuntimeException to ServiceFailure
1158
              throw new ServiceFailure("4430", e.getMessage());
1159
            
1160
          }
1161
          
1162
      } catch (Exception e) {
1163
          throw new ServiceFailure("4430", e.getMessage());
1164
          
1165
      } finally {
1166
          lock.unlock();
1167
          logMetacat.debug("Unlocked identifier " + pid.getValue());
1168
        
1169
      }
1170

    
1171
    
1172
    // TODO: how do we know if the map was persisted?
1173
    success = true;
1174
    
1175
    return success;
1176
  }
1177

    
1178
  /**
1179
   * Full replacement of replication metadata in the system metadata for the 
1180
   * specified object, changes date system metadata modified
1181
   * 
1182
   * @param session - the Session object containing the credentials for the Subject
1183
   * @param pid - the object identifier for the given object to apply the policy
1184
   * @param replica - the replica to be updated
1185
   * @return
1186
   * @throws NotImplemented
1187
   * @throws NotAuthorized
1188
   * @throws ServiceFailure
1189
   * @throws InvalidRequest
1190
   * @throws NotFound
1191
   */
1192
  public boolean updateReplicationMetadata(Session session, Identifier pid,
1193
      Replica replica, long serialVersion) 
1194
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest,
1195
      NotFound {
1196
      
1197
      // The lock to be used for this identifier
1198
      Lock lock = null;
1199
      
1200
      // get the subject
1201
      Subject subject = session.getSubject();
1202
      
1203
      // are we allowed to do this?
1204
      try {
1205
        if (!isAdminAuthorized(session, pid, Permission.WRITE)) {
1206
            // what is the controlling permission?
1207
            if (!isAuthorized(session, pid, Permission.WRITE)) {
1208
                throw new NotAuthorized("4851", "not allowed by "
1209
                        + subject.getValue() + " on " + pid.getValue());
1210
            }
1211
        }
1212
        
1213
      } catch (InvalidToken e) {
1214
          throw new NotAuthorized("4851", "not allowed by " + subject.getValue() + 
1215
                  " on " + pid.getValue());  
1216
          
1217
      }
1218

    
1219
      SystemMetadata systemMetadata = null;
1220
      try {
1221
          lock = HazelcastService.getInstance().getLock(pid.getValue());
1222
          lock.lock();
1223

    
1224
          try {      
1225
              systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1226

    
1227
              // does the request have the most current system metadata?
1228
              if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
1229
                 String msg = "The requested system metadata version number " + 
1230
                     serialVersion + " differs from the current version at " +
1231
                     systemMetadata.getSerialVersion().longValue() +
1232
                     ". Please get the latest copy in order to modify it.";
1233
                 throw new InvalidRequest("4853", msg);
1234
              }
1235
              
1236
          } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
1237
            throw new NotFound("4854", "No record found for: " + pid.getValue() +
1238
                " : " + e.getMessage());
1239
            
1240
          }
1241
              
1242
          // set the status for the replica
1243
          List<Replica> replicas = systemMetadata.getReplicaList();
1244
          NodeReference replicaNode = replica.getReplicaMemberNode();
1245
          int index = 0;
1246
          for (Replica listedReplica: replicas) {
1247
              
1248
              // remove the replica that we are replacing
1249
              if ( replicaNode.getValue().equals(listedReplica.getReplicaMemberNode().getValue())) {
1250
                  replicas.remove(index);
1251
                  break;
1252
                  
1253
              }
1254
              index++;
1255
          }
1256
          
1257
          // add the new replica item
1258
          replicas.add(replica);
1259
          systemMetadata.setReplicaList(replicas);
1260
          
1261
          // update the metadata
1262
          try {
1263
              systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1264
              systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1265
              HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1266
            
1267
          } catch (Exception e) {
1268
              throw new ServiceFailure("4852", e.getMessage());
1269
          
1270
          }
1271
          
1272
    } catch (Exception e) {
1273
        throw new ServiceFailure("4852", e.getMessage());
1274

    
1275
    } finally {
1276
        lock.unlock();
1277
        logMetacat.debug("Unlocked identifier " + pid.getValue());
1278
        
1279
    }
1280
    
1281
      return true;
1282
      
1283
  }
1284
  
1285
    @Override
1286
  public ObjectList listObjects(Session session, Date startTime, 
1287
      Date endTime, ObjectFormatIdentifier formatid, Boolean replicaStatus,
1288
      Integer start, Integer count)
1289
      throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1290
      ServiceFailure {
1291
      
1292
      ObjectList objectList = null;
1293
        try {
1294
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, formatid, replicaStatus, start, count);
1295
        } catch (Exception e) {
1296
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1297
        }
1298

    
1299
        return objectList;
1300
  }
1301

    
1302
	@Override
1303
	public ChecksumAlgorithmList listChecksumAlgorithms()
1304
			throws ServiceFailure, NotImplemented {
1305
		ChecksumAlgorithmList cal = new ChecksumAlgorithmList();
1306
		cal.addAlgorithm("MD5");
1307
		cal.addAlgorithm("SHA-1");
1308
		return null;
1309
	}
1310
    
1311
}
(1-1/5)