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

    
33
import javax.servlet.http.HttpServletRequest;
34

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

    
73
import com.hazelcast.core.ILock;
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
      ILock lock = null;
131
      
132
      // get the subject
133
      Subject subject = session.getSubject();
134
      
135
      // are we allowed to do this?
136
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
137
        throw new NotAuthorized("4881", Permission.CHANGE_PERMISSION + 
138
            " not allowed by " + subject.getValue() + " on " + pid.getValue());  
139
      }
140
      
141
      SystemMetadata systemMetadata = null;
142
      try {
143
          if ( HazelcastService.getInstance().getSystemMetadataMap().containsKey(pid) ) {
144
              lock = HazelcastService.getInstance().getLock(pid.getValue());
145
              lock.lock();
146
              systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
147
              
148
          }
149
        
150

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

    
181
          
182
      }
183
    
184
      return true;
185
  }
186

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

    
227
          if ( systemMetadata == null ) {
228
              logMetacat.debug("systemMetadata is null for " + pid.getValue());
229
              
230
          }
231
          replicas = systemMetadata.getReplicaList();
232
          int count = 0;
233
          
234
          if ( replicas == null || replicas.size() < 1 ) {
235
              logMetacat.debug("no replicas to evaluate");
236
              throw new InvalidRequest("4730", "There are no replicas to update.");
237
              
238
          }
239

    
240
          // find the target replica index in the replica list
241
          for (Replica replica: replicas) {
242
              String replicaNodeStr = replica.getReplicaMemberNode().getValue();
243
              String targetNodeStr = targetNode.getValue();
244
              logMetacat.debug("Comparing " + replicaNodeStr + " to " + targetNodeStr);
245
              
246
              if (replicaNodeStr.equals(targetNodeStr)) {
247
                  replicaEntryIndex = count;
248
                  logMetacat.debug("replica entry index is: " + replicaEntryIndex);
249
                  break;
250
              }
251
              count++;
252
              
253
          }
254

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

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

    
314
      }
315
      
316
      systemMetadata.setReplicaList(replicas);
317
            
318
      // update the metadata
319
      try {
320
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
321
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
322
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
323
          
324
          if ( status == ReplicationStatus.FAILED && failure != null ) {
325
              logMetacat.warn("Replication failed for identifier " + pid.getValue() +
326
                  " on target node " + targetNode + ". The exception was: " +
327
                  failure.getMessage());
328
          }
329
      } catch (Exception e) {
330
          throw new ServiceFailure("4700", e.getMessage());
331
      
332
      } finally {
333
          lock.unlock();
334
          logMetacat.debug("Unlocked identifier " + pid.getValue());
335
         
336
      }
337
      
338
      return true;
339
  }
340

    
341
  /**
342
   * Test that the specified relationship between pidOfSubject and pidOfObject exists
343
   * 
344
   * @param session - the Session object containing the credentials for the Subject
345
   * @param node - the node information for the given node be modified
346
   * 
347
   * @return true if the relationship exists
348
   * 
349
   * @throws InvalidToken
350
   * @throws ServiceFailure
351
   * @throws NotAuthorized
352
   * @throws NotFound
353
   * @throws InvalidRequest
354
   * @throws NotImplemented
355
   */
356
  @Override
357
  public boolean assertRelation(Session session, Identifier pidOfSubject, 
358
    String relationship, Identifier pidOfObject) 
359
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
360
    InvalidRequest, NotImplemented {
361
    
362
    // The lock to be used for thyis identifier
363
    ILock lock = null;
364

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

    
419
    }
420
        
421
    return asserted;
422
  }
423
  
424
  /**
425
   * Return the checksum of the object given the identifier 
426
   * 
427
   * @param session - the Session object containing the credentials for the Subject
428
   * @param pid - the object identifier for the given object
429
   * 
430
   * @return checksum - the checksum of the object
431
   * 
432
   * @throws InvalidToken
433
   * @throws ServiceFailure
434
   * @throws NotAuthorized
435
   * @throws NotFound
436
   * @throws NotImplemented
437
   */
438
  @Override
439
  public Checksum getChecksum(Session session, Identifier pid)
440
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
441
    NotImplemented {
442
        
443
    // The lock to be used for thyis identifier
444
    ILock lock = null;
445
    
446
    if (!isAuthorized(session, pid, Permission.READ)) {
447
        throw new NotAuthorized("1400", Permission.READ + " not allowed on " + pid.getValue());  
448
    }
449
    
450
    SystemMetadata systemMetadata = null;
451
    Checksum checksum = null;
452
    
453
    try {
454
        lock = HazelcastService.getInstance().getLock(pid.getValue());
455
        lock.lock();
456
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
457
        checksum = systemMetadata.getChecksum();
458

    
459
    } catch (Exception e) {
460
        throw new ServiceFailure("1410", "An error occurred getting the checksum for " + 
461
            pid.getValue() + ". The error message was: " + e.getMessage());
462
      
463
    } finally {
464
        lock.unlock();
465
        logMetacat.debug("Unlocked identifier " + pid.getValue());
466
        
467
    }
468
    
469
    return checksum;
470
  }
471

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

    
491
    throw new NotImplemented("4131", "resolve not implemented");
492

    
493
  }
494

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

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

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

    
554
            DBQuery metacat = new DBQuery();
555

    
556
            boolean useXMLIndex = (new Boolean(PropertyService
557
                    .getProperty("database.usexmlindex"))).booleanValue();
558
            String xmlquery = "query"; // we don't care the query in resultset,
559
            // the query can be anything
560
            PrintWriter out = null; // we don't want metacat result, so set out null
561

    
562
            // parameter: queryspecification, user, group, usingIndexOrNot
563
            StringBuffer result = metacat.createResultDocument(xmlquery,
564
                    metacatQuery, out, username, groupNames, useXMLIndex);
565

    
566
            // create result set transfer       
567
            String saxparser = PropertyService.getProperty("xml.saxparser");
568
            MetacatResultsetParser metacatResultsetParser = new MetacatResultsetParser(
569
                    new StringReader(result.toString()), saxparser, queryType
570
                            .getNamespace().get_value());
571
            ResultsetType records = metacatResultsetParser.getEcogridResult();
572

    
573
            System.out
574
                    .println(EcogridResultsetTransformer.toXMLString(records));
575
            response.setContentType("text/xml");
576
            out = response.getWriter();
577
            out.print(EcogridResultsetTransformer.toXMLString(records));
578

    
579
        } catch (Exception e) {
580
            e.printStackTrace();
581
        }*/
582
    
583

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

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

    
624
    return ObjectFormatService.getInstance().listFormats();
625
  }
626

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

    
639
    throw new NotImplemented("4800", "listNodes not implemented");
640
  }
641

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

    
664
      // The lock to be used for this identifier
665
      ILock lock = null;
666

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

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

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

    
751
    throw new NotImplemented("4191", "reserveIdentifier not implemented on this node");
752
  }
753
  
754
  @Override
755
  public Identifier generateIdentifier(Session session, String scheme, String fragment)
756
  throws InvalidToken, ServiceFailure,
757
        NotAuthorized, NotImplemented, InvalidRequest {
758
    throw new NotImplemented("4191", "generateIdentifier not implemented on this node");
759
  }
760
  
761
  /**
762
    * Checks whether the pid is reserved by the subject in the session param
763
    * If the reservation is held on the pid by the subject, we return true.
764
    * 
765
   * @param session - the Session object containing the Subject
766
   * @param pid - The identifier to check
767
   * 
768
   * @return true if the reservation exists for the subject/pid
769
   * 
770
   * @throws InvalidToken
771
   * @throws ServiceFailure
772
   * @throws NotFound - when the pid is not found (in use or in reservation)
773
   * @throws NotAuthorized - when the subject does not hold a reservation on the pid
774
   * @throws IdentifierNotUnique - when the pid is in use
775
   * @throws NotImplemented
776
   */
777

    
778
  @Override
779
  public boolean hasReservation(Session session, Identifier pid) 
780
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, IdentifierNotUnique, 
781
      NotImplemented, InvalidRequest {
782
  
783
      throw new NotImplemented("4191", "hasReservation not implemented on this node");
784
  }
785

    
786
  /**
787
   * Changes ownership (RightsHolder) of the specified object to the 
788
   * subject specified by userId
789
    * 
790
   * @param session - the Session object containing the credentials for the Subject
791
   * @param pid - Identifier of the object to be modified
792
   * @param userId - The subject that will be taking ownership of the specified object.
793
   *
794
   * @return pid - the identifier of the modified object
795
   * 
796
   * @throws ServiceFailure
797
   * @throws InvalidToken
798
   * @throws NotFound
799
   * @throws NotAuthorized
800
   * @throws NotImplemented
801
   * @throws InvalidRequest
802
   */  
803
  @Override
804
  public Identifier setOwner(Session session, Identifier pid, Subject userId,
805
      long serialVersion)
806
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized,
807
      NotImplemented, InvalidRequest {
808
      
809
      // The lock to be used for this identifier
810
      ILock lock = null;
811

    
812
      // get the subject
813
      Subject subject = session.getSubject();
814
      
815
      // are we allowed to do this?
816
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
817
        throw new NotAuthorized("4440", "not allowed by " + subject.getValue() + " on " + pid.getValue());  
818
      }
819
      
820
      SystemMetadata systemMetadata = null;
821
      try {
822
          lock = HazelcastService.getInstance().getLock(pid.getValue());
823
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
824
          
825
          // does the request have the most current system metadata?
826
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
827
             String msg = "The requested system metadata version number " + 
828
                 serialVersion + " differs from the current version at " +
829
                 systemMetadata.getSerialVersion().longValue() +
830
                 ". Please get the latest copy in order to modify it.";
831
             throw new InvalidRequest("4442", msg);
832
          }
833
          
834
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
835
          throw new NotFound("4460", "No record found for: " + pid.getValue());
836
          
837
      }
838
          
839
      // set the new rights holder
840
      systemMetadata.setRightsHolder(userId);
841
      
842
      // update the metadata
843
      try {
844
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
845
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
846
          HazelcastService.getInstance().getSystemMetadataMap().put(pid, systemMetadata);
847
          
848
      } catch (Exception e) {
849
      throw new ServiceFailure("4490", e.getMessage());
850
      
851
      } finally {
852
          lock.unlock();
853
          logMetacat.debug("Unlocked identifier " + pid.getValue());
854

    
855
      }
856
      
857
      return pid;
858
  }
859

    
860
  /**
861
   * Verify that a replication task is authorized by comparing the target node's
862
   * Subject (from the X.509 certificate-derived Session) with the list of 
863
   * subjects in the known, pending replication tasks map.
864
   * 
865
   * @param originatingNodeSession - Session information that contains the 
866
   *                                 identity of the calling user
867
   * @param targetNodeSubject - Subject identifying the target node
868
   * @param pid - the identifier of the object to be replicated
869
   * @param replicatePermission - the execute permission to be granted
870
   * 
871
   * @throws ServiceFailure
872
   * @throws NotImplemented
873
   * @throws InvalidToken
874
   * @throws NotAuthorized
875
   * @throws InvalidRequest
876
   * @throws NotFound
877
   */
878
  @Override
879
  public boolean isNodeAuthorized(Session originatingNodeSession, 
880
    Subject targetNodeSubject, Identifier pid) 
881
    throws NotImplemented, NotAuthorized, InvalidToken, ServiceFailure, 
882
    NotFound, InvalidRequest {
883

    
884
    // The lock to be used for this identifier
885
    ILock lock = null;
886
    
887
    boolean isAllowed = false;
888
    SystemMetadata sysmeta = null;
889
    NodeReference targetNode = null;
890
    
891
    try {
892
      // get the target node reference from the nodes list
893
      CNode cn = D1Client.getCN();
894
      List<Node> nodes = cn.listNodes().getNodeList();
895
      
896
      if ( nodes != null ) {
897
        for (Node node : nodes) {
898
            
899
            for (Subject nodeSubject : node.getSubjectList()) {
900
                
901
                if ( nodeSubject.equals(targetNodeSubject) ) {
902
                    targetNode = node.getIdentifier();
903
                    logMetacat.debug("targetNode is : " + targetNode.getValue());
904
                    break;
905
                }
906
            }
907
            
908
            if ( targetNode != null) { break; }
909
        }
910
        
911
      } else {
912
          String msg = "Couldn't get the node list from the CN";
913
          logMetacat.debug(msg);
914
          throw new ServiceFailure("4872", msg);
915
          
916
      }
917
      
918
      // can't find a node listed with the given subject
919
      if ( targetNode == null ) {
920
          String msg = "There is no Member Node registered with a node subject " +
921
              "matching " + targetNodeSubject.getValue();
922
          logMetacat.info(msg);
923
          throw new ServiceFailure("4872", msg);
924
          
925
      }
926
      
927
      //lock, get, and unlock the pid
928
      lock = HazelcastService.getInstance().getLock(pid.getValue());
929
      lock.lock();
930
      logMetacat.debug("Getting system metadata for identifier " + pid.getValue());
931
      
932
      sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
933

    
934
      if ( sysmeta != null ) {
935
          
936
          List<Replica> replicaList = sysmeta.getReplicaList();
937
          
938
          if ( replicaList != null ) {
939
              
940
              // find the replica with the status set to 'requested'
941
              for (Replica replica : replicaList) {
942
                  ReplicationStatus status = replica.getReplicationStatus();
943
                  NodeReference listedNode = replica.getReplicaMemberNode();
944
                  if ( listedNode != null && targetNode != null ) {
945
                      logMetacat.debug("Comparing " + listedNode.getValue()
946
                              + " to " + targetNode.getValue());
947
                      
948
                      if (listedNode.getValue().equals(targetNode.getValue())
949
                              && status.equals(ReplicationStatus.REQUESTED)) {
950
                          isAllowed = true;
951
                          break;
952

    
953
                      }
954
                  }
955
              }
956
          }
957
          logMetacat.debug("The " + targetNode.getValue() + " is allowed " +
958
              "to replicate: " + isAllowed + " for " + pid.getValue());
959

    
960
          
961
      } else {
962
          logMetacat.debug("System metadata for identifier " + pid.getValue() +
963
          " is null.");          
964
          
965
      }
966

    
967
    } catch (RuntimeException e) {
968
    	  ServiceFailure sf = new ServiceFailure("4872", 
969
                "Runtime Exception: Couldn't determine if node is allowed: " + 
970
                e.getCause().getMessage());
971
    	  sf.initCause(e);
972
        throw sf;
973
        
974
    } finally {
975
      // always unlock the pid
976
      lock.unlock();
977
      logMetacat.debug("Unlocked identifier " + pid.getValue());
978

    
979
    }
980
      
981
    return isAllowed;
982
    
983
  }
984

    
985
  /**
986
   * Adds a new object to the Node, where the object is a science metadata object.
987
   * 
988
   * @param session - the Session object containing the credentials for the Subject
989
   * @param pid - The object identifier to be created
990
   * @param object - the object bytes
991
   * @param sysmeta - the system metadata that describes the object  
992
   * 
993
   * @return pid - the object identifier created
994
   * 
995
   * @throws InvalidToken
996
   * @throws ServiceFailure
997
   * @throws NotAuthorized
998
   * @throws IdentifierNotUnique
999
   * @throws UnsupportedType
1000
   * @throws InsufficientResources
1001
   * @throws InvalidSystemMetadata
1002
   * @throws NotImplemented
1003
   * @throws InvalidRequest
1004
   */
1005
  public Identifier create(Session session, Identifier pid, InputStream object,
1006
    SystemMetadata sysmeta) 
1007
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
1008
    UnsupportedType, InsufficientResources, InvalidSystemMetadata, 
1009
    NotImplemented, InvalidRequest {
1010
      
1011
      
1012
      // The lock to be used for this identifier
1013
      ILock lock = null;
1014
      
1015
      try {
1016
        // are we allowed?
1017
          boolean isAllowed = false;
1018
          CNode cn = D1Client.getCN();
1019
          NodeList nodeList = cn.listNodes();
1020
          
1021
          for (Node node : nodeList.getNodeList()) {
1022
              if ( node.getType().equals(NodeType.CN) ) {
1023
                  
1024
                  List<Subject> subjects = node.getSubjectList();
1025
                  for (Subject subject : subjects) {
1026
                     if (subject.equals(session.getSubject())) {
1027
                         isAllowed = true;
1028
                         break;
1029
                     }
1030
                  }
1031
              }
1032
          }
1033

    
1034
          // proceed if we're called by a CN
1035
          if ( isAllowed ) {
1036
              // create the coordinating node version of the document      
1037
              lock = HazelcastService.getInstance().getLock(pid.getValue());
1038
              lock.lock();
1039
              sysmeta.setSerialVersion(BigInteger.ONE);
1040
              sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
1041
              pid = super.create(session, pid, object, sysmeta);
1042

    
1043
          } else {
1044
              String msg = "The subject listed as " + session.getSubject().getValue() + 
1045
                  " isn't allowed to call create() on a Coordinating Node.";
1046
              logMetacat.info(msg);
1047
              throw new NotAuthorized("1100", msg);
1048
          }
1049
          
1050
      } catch (RuntimeException e) {
1051
          // Convert Hazelcast runtime exceptions to service failures
1052
          String msg = "There was a problem creating the object identified by " +
1053
              pid.getValue() + ". There error message was: " + e.getMessage();
1054
          throw new ServiceFailure("4893", msg);
1055
          
1056
      } finally {
1057
          lock.unlock();
1058
          logMetacat.debug("Unlocked identifier " + pid.getValue());
1059
     
1060
      }
1061
      
1062
      return pid;
1063

    
1064
  }
1065

    
1066
  /**
1067
   * Set access for a given object using the object identifier and a Subject
1068
   * under a given Session.
1069
   * 
1070
   * @param session - the Session object containing the credentials for the Subject
1071
   * @param pid - the object identifier for the given object to apply the policy
1072
   * @param policy - the access policy to be applied
1073
   * 
1074
   * @return true if the application of the policy succeeds
1075
   * @throws InvalidToken
1076
   * @throws ServiceFailure
1077
   * @throws NotFound
1078
   * @throws NotAuthorized
1079
   * @throws NotImplemented
1080
   * @throws InvalidRequest
1081
   */
1082
  public boolean setAccessPolicy(Session session, Identifier pid, 
1083
      AccessPolicy accessPolicy, long serialVersion) 
1084
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, 
1085
      NotImplemented, InvalidRequest {
1086
      
1087
      // The lock to be used for this identifier
1088
      ILock lock = null;
1089
      
1090
      boolean success = false;
1091
      
1092
      // get the subject
1093
      Subject subject = session.getSubject();
1094
      
1095
      // are we allowed to do this?
1096
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
1097
          throw new NotAuthorized("4420", "not allowed by " + subject.getValue() + 
1098
          " on " + pid.getValue());  
1099
      }
1100
      
1101
      SystemMetadata systemMetadata = null;
1102
      try {
1103
          lock = HazelcastService.getInstance().getLock(pid.getValue());
1104
          lock.lock();
1105
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1106

    
1107
          // does the request have the most current system metadata?
1108
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
1109
             String msg = "The requested system metadata version number " + 
1110
                 serialVersion + " differs from the current version at " +
1111
                 systemMetadata.getSerialVersion().longValue() +
1112
                 ". Please get the latest copy in order to modify it.";
1113
             throw new InvalidRequest("4402", msg);
1114
          }
1115
          
1116
      } catch (Exception e) {
1117
          // convert Hazelcast RuntimeException to NotFound
1118
          throw new NotFound("4400", "No record found for: " + pid);
1119
        
1120
      }
1121
          
1122
      // set the access policy
1123
      systemMetadata.setAccessPolicy(accessPolicy);
1124
      
1125
      // update the system metadata
1126
      try {
1127
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1128
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1129
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1130
        
1131
      } catch (Exception e) {
1132
          // convert Hazelcast RuntimeException to ServiceFailure
1133
          throw new ServiceFailure("4430", e.getMessage());
1134
        
1135
      } finally {
1136
          lock.unlock();
1137
          logMetacat.debug("Unlocked identifier " + pid.getValue());
1138
        
1139
      }
1140
    
1141
    // TODO: how do we know if the map was persisted?
1142
    success = true;
1143
    
1144
    return success;
1145
  }
1146

    
1147
  /**
1148
   * Full replacement of replication metadata in the system metadata for the 
1149
   * specified object, changes date system metadata modified
1150
   * 
1151
   * @param session - the Session object containing the credentials for the Subject
1152
   * @param pid - the object identifier for the given object to apply the policy
1153
   * @param replica - the replica to be updated
1154
   * @return
1155
   * @throws NotImplemented
1156
   * @throws NotAuthorized
1157
   * @throws ServiceFailure
1158
   * @throws InvalidRequest
1159
   * @throws NotFound
1160
   */
1161
  public boolean updateReplicationMetadata(Session session, Identifier pid,
1162
      Replica replica, long serialVersion) 
1163
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest,
1164
      NotFound {
1165
      
1166
      // The lock to be used for this identifier
1167
      ILock lock = null;
1168
      
1169
      // get the subject
1170
      Subject subject = session.getSubject();
1171
      
1172
      // are we allowed to do this?
1173
      try {
1174
        // what is the controlling permission?
1175
        if (!isAuthorized(session, pid, Permission.WRITE)) {
1176
            throw new NotAuthorized("4851", "not allowed by " + subject.getValue() + 
1177
            " on " + pid.getValue());  
1178
        }
1179
        
1180
      } catch (InvalidToken e) {
1181
          throw new NotAuthorized("4851", "not allowed by " + subject.getValue() + 
1182
                  " on " + pid.getValue());  
1183
          
1184
      }
1185

    
1186
      SystemMetadata systemMetadata = null;
1187
      try {      
1188
          lock = HazelcastService.getInstance().getLock(pid.getValue());
1189
          lock.lock();
1190
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1191

    
1192
          // does the request have the most current system metadata?
1193
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
1194
             String msg = "The requested system metadata version number " + 
1195
                 serialVersion + " differs from the current version at " +
1196
                 systemMetadata.getSerialVersion().longValue() +
1197
                 ". Please get the latest copy in order to modify it.";
1198
             throw new InvalidRequest("4853", msg);
1199
          }
1200
          
1201
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
1202
        throw new NotFound("4854", "No record found for: " + pid.getValue() +
1203
            " : " + e.getMessage());
1204
        
1205
      }
1206
          
1207
      // set the status for the replica
1208
      List<Replica> replicas = systemMetadata.getReplicaList();
1209
      NodeReference replicaNode = replica.getReplicaMemberNode();
1210
      int index = 0;
1211
      for (Replica listedReplica: replicas) {
1212
          
1213
          // remove the replica that we are replacing
1214
          if ( replicaNode.getValue().equals(listedReplica.getReplicaMemberNode().getValue())) {
1215
              replicas.remove(index);
1216
              break;
1217
              
1218
          }
1219
          index++;
1220
      }
1221
      
1222
      // add the new replica item
1223
      replicas.add(replica);
1224
      systemMetadata.setReplicaList(replicas);
1225
      
1226
      // update the metadata
1227
      try {
1228
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1229
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1230
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1231
        
1232
      } catch (Exception e) {
1233
          throw new ServiceFailure("4852", e.getMessage());
1234
      
1235
      } finally {
1236
          lock.unlock();
1237
          logMetacat.debug("Unlocked identifier " + pid.getValue());
1238
          
1239
      }
1240
    
1241
      return true;
1242
      
1243
  }
1244
  
1245
    @Override
1246
    public ObjectList listObjects(Session session, Date startTime, 
1247
            Date endTime, ObjectFormatIdentifier formatid, Boolean replicaStatus,
1248
            Integer start, Integer count)
1249
      throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1250
      ServiceFailure {
1251
      
1252
      ObjectList objectList = null;
1253
        try {
1254
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, formatid, replicaStatus, start, count);
1255
        } catch (Exception e) {
1256
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1257
        }
1258

    
1259
        return objectList;
1260
  }
1261
    
1262
}
(1-1/5)