Project

General

Profile

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

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

    
26
import java.io.IOException;
27
import java.io.InputStream;
28
import java.math.BigInteger;
29
import java.util.Calendar;
30
import java.util.Date;
31
import java.util.List;
32
import java.util.Set;
33

    
34
import javax.servlet.http.HttpServletRequest;
35

    
36
import org.apache.commons.io.IOUtils;
37
import org.apache.log4j.Logger;
38
import org.dataone.client.CNode;
39
import org.dataone.client.D1Client;
40
import org.dataone.client.auth.CertificateManager;
41
import org.dataone.service.cn.v1.CNAuthorization;
42
import org.dataone.service.cn.v1.CNCore;
43
import org.dataone.service.cn.v1.CNRead;
44
import org.dataone.service.cn.v1.CNReplication;
45
import org.dataone.service.exceptions.IdentifierNotUnique;
46
import org.dataone.service.exceptions.InsufficientResources;
47
import org.dataone.service.exceptions.InvalidRequest;
48
import org.dataone.service.exceptions.InvalidSystemMetadata;
49
import org.dataone.service.exceptions.InvalidToken;
50
import org.dataone.service.exceptions.NotAuthorized;
51
import org.dataone.service.exceptions.NotFound;
52
import org.dataone.service.exceptions.NotImplemented;
53
import org.dataone.service.exceptions.ServiceFailure;
54
import org.dataone.service.exceptions.UnsupportedType;
55
import org.dataone.service.types.v1.AccessPolicy;
56
import org.dataone.service.types.v1.Checksum;
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
import org.dataone.service.types.v1.util.ChecksumUtil;
75
import org.dataone.service.util.Constants;
76

    
77
import com.hazelcast.query.SqlPredicate;
78

    
79
import edu.ucsb.nceas.metacat.EventLog;
80
import edu.ucsb.nceas.metacat.IdentifierManager;
81
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
82
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
83

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

    
94
  /* the logger instance */
95
  private Logger logMetacat = null;
96

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

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

    
184
  /**
185
   * Set the replication status for an object given the object identifier
186
   * 
187
   * @param session - the Session object containing the credentials for the Subject
188
   * @param pid - the object identifier for the given object
189
   * @param status - the replication status to be applied
190
   * 
191
   * @return true or false
192
   * 
193
   * @throws NotImplemented
194
   * @throws NotAuthorized
195
   * @throws ServiceFailure
196
   * @throws InvalidRequest
197
   * @throws InvalidToken
198
   * @throws NotFound
199
   * 
200
   */
201
  @Override
202
  public boolean setReplicationStatus(Session session, Identifier pid,
203
      NodeReference targetNode, ReplicationStatus status, long serialVersion) 
204
      throws ServiceFailure, NotImplemented, InvalidToken, NotAuthorized, 
205
      InvalidRequest, NotFound {
206
      
207
      boolean allowed = false;
208
      int replicaEntryIndex = -1;
209
      List<Replica> replicas = null;
210
      // get the subject
211
      Subject subject = session.getSubject();
212
      
213
      SystemMetadata systemMetadata = null;
214
      try {      
215
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
216
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
217
          
218
          replicas = systemMetadata.getReplicaList();
219
          int count = 0;
220
          
221
          // find the target replica index in the replica list
222
          for (Replica replica: replicas) {
223
              if (replica.getReplicaMemberNode().getValue().equals(targetNode.getValue())) {
224
                  replicaEntryIndex = count;
225
                  break;
226
              }
227
              count++;
228
              
229
          }
230

    
231
          // are we allowed to do this? only CNs and target MNs are allowed
232
          CNode cn = D1Client.getCN();
233
          List<Node> nodes = cn.listNodes().getNodeList();
234
          
235
          // find the node in the node list
236
          for ( Node node : nodes ) {
237
              
238
              NodeReference nodeReference = node.getIdentifier();
239
              logMetacat.debug("In setReplicationStatus(), Node reference is: " + nodeReference.getValue());
240
              
241
              // allow target MN certs and CN certs
242
              if (targetNode.getValue().equals(nodeReference.getValue()) ||
243
                  node.getType() == NodeType.CN) {
244
                  List<Subject> nodeSubjects = node.getSubjectList();
245
                  
246
                  // check if the session subject is in the node subject list
247
                  for (Subject nodeSubject : nodeSubjects) {
248
                      if ( CertificateManager.getInstance().equalsDN(
249
                              nodeSubject.getValue(), subject.getValue()) ) {
250
                          allowed = true; // subject of session == target node subject
251
                          break;
252
                          
253
                      }
254
                  }                 
255
              }
256
          }
257

    
258
          if ( !allowed ) {
259
              String msg = "The subject identified by " + subject.getValue() +
260
                " does not have permission to set the replication status for " +
261
                "the replica identified by " + targetNode.getValue() + ".";
262
              logMetacat.info(msg);
263
              throw new NotAuthorized("4720", msg);
264
              
265
          }
266
          
267
          // does the request have the most current system metadata?
268
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
269
             String msg = "The requested system metadata version number " + 
270
                 serialVersion + "differs from the current version at " +
271
                 systemMetadata.getSerialVersion().longValue() +
272
                 " Please get the latest copy in order to modify it.";
273
             throw new InvalidRequest("4730", msg);
274
          }
275
          
276
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
277
        throw new NotFound("4740", "No record found for: " + pid.getValue() +
278
            " : " + e.getMessage());
279
        
280
      }
281
          
282
      // set the status for the replica
283
      if ( replicaEntryIndex != -1 ) {          
284
          replicas.get(replicaEntryIndex).setReplicationStatus(status);
285
          
286
      }
287
            
288
      // update the metadata
289
      try {
290
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
291
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
292
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
293
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
294
        
295
      } catch (Exception e) {
296
          throw new ServiceFailure("4700", e.getMessage());
297
      
298
      } finally {
299
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
300
          
301
      }
302
      
303
      return true;
304
  }
305

    
306
  /**
307
   * Test that the specified relationship between pidOfSubject and pidOfObject exists
308
   * 
309
   * @param session - the Session object containing the credentials for the Subject
310
   * @param node - the node information for the given node be modified
311
   * 
312
   * @return true if the relationship exists
313
   * 
314
   * @throws InvalidToken
315
   * @throws ServiceFailure
316
   * @throws NotAuthorized
317
   * @throws NotFound
318
   * @throws InvalidRequest
319
   * @throws NotImplemented
320
   */
321
  @Override
322
  public boolean assertRelation(Session session, Identifier pidOfSubject, 
323
    String relationship, Identifier pidOfObject) 
324
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
325
    InvalidRequest, NotImplemented {
326
    
327
    boolean asserted = false;
328
        
329
    // are we allowed to do this?
330
    if (!isAuthorized(session, pidOfSubject, Permission.READ)) {
331
      throw new NotAuthorized("4881", Permission.READ + " not allowed on " + pidOfSubject.getValue());  
332
    }
333
    
334
    SystemMetadata systemMetadata = null;
335
    try {
336
        HazelcastService.getInstance().getSystemMetadataMap().lock(pidOfSubject);
337
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pidOfSubject);
338
        
339
        
340
        // check relationships
341
        // TODO: use ORE map
342
        if (relationship.equalsIgnoreCase("describes")) {
343
          
344
        }
345
        
346
        if (relationship.equalsIgnoreCase("describedBy")) {
347
          
348
        }
349
        
350
        if (relationship.equalsIgnoreCase("derivedFrom")) {
351
          
352
        }
353
        
354
        if (relationship.equalsIgnoreCase("obsoletes")) {
355
            Identifier pid = systemMetadata.getObsoletes();
356
            if (pid.getValue().equals(pidOfObject.getValue())) {
357
              asserted = true;
358
            
359
        }
360
          //return systemMetadata.getObsoleteList().contains(pidOfObject);
361
        }
362
        if (relationship.equalsIgnoreCase("obsoletedBy")) {
363
            Identifier pid = systemMetadata.getObsoletedBy();
364
            if (pid.getValue().equals(pidOfObject.getValue())) {
365
              asserted = true;
366
            
367
        }
368
          //return systemMetadata.getObsoletedByList().contains(pidOfObject);
369
        }
370
        
371
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pidOfSubject);
372
      
373
    } catch (Exception e) {
374
        throw new ServiceFailure("4270", "Could not assert relation for: " + 
375
            pidOfSubject.getValue() +
376
            ". The error message was: " + e.getMessage());
377
      
378
    } finally {
379
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pidOfSubject);
380

    
381
    }
382
        
383
    return asserted;
384
  }
385
  
386
  /**
387
   * Return the checksum of the object given the identifier 
388
   * 
389
   * @param session - the Session object containing the credentials for the Subject
390
   * @param pid - the object identifier for the given object
391
   * 
392
   * @return checksum - the checksum of the object
393
   * 
394
   * @throws InvalidToken
395
   * @throws ServiceFailure
396
   * @throws NotAuthorized
397
   * @throws NotFound
398
   * @throws NotImplemented
399
   */
400
  @Override
401
  public Checksum getChecksum(Session session, Identifier pid)
402
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
403
    NotImplemented {
404
        
405
    if (!isAuthorized(session, pid, Permission.READ)) {
406
        throw new NotAuthorized("1400", Permission.READ + " not allowed on " + pid.getValue());  
407
    }
408
    
409
    SystemMetadata systemMetadata = null;
410
    Checksum checksum = null;
411
    
412
    try {
413
        HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
414
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
415
        checksum = systemMetadata.getChecksum();
416
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
417

    
418
    } catch (Exception e) {
419
        throw new ServiceFailure("1410", "An error occurred getting the checksum for " + 
420
            pid.getValue() + ". The error message was: " + e.getMessage());
421
      
422
    } finally {
423
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
424
        
425
    }
426
    
427
    return checksum;
428
  }
429

    
430
  /**
431
   * Resolve the location of a given object
432
   * 
433
   * @param session - the Session object containing the credentials for the Subject
434
   * @param pid - the object identifier for the given object
435
   * 
436
   * @return objectLocationList - the list of nodes known to contain the object
437
   * 
438
   * @throws InvalidToken
439
   * @throws ServiceFailure
440
   * @throws NotAuthorized
441
   * @throws NotFound
442
   * @throws NotImplemented
443
   */
444
  @Override
445
  public ObjectLocationList resolve(Session session, Identifier pid)
446
    throws InvalidToken, ServiceFailure, NotAuthorized,
447
    NotFound, NotImplemented {
448

    
449
    throw new NotImplemented("4131", "resolve not implemented");
450

    
451
  }
452

    
453
  /**
454
   * Search the metadata catalog for identifiers that match the criteria
455
   * 
456
   * @param session - the Session object containing the credentials for the Subject
457
   * @param queryType - An identifier for the type of query expression 
458
   *                    provided in the query
459
   * @param query -  The criteria for matching the characteristics of the 
460
   *                 metadata objects of interest
461
   * 
462
   * @return objectList - the list of objects matching the criteria
463
   * 
464
   * @throws InvalidToken
465
   * @throws ServiceFailure
466
   * @throws NotAuthorized
467
   * @throws InvalidRequest
468
   * @throws NotImplemented
469
   */
470
  @Override
471
  public ObjectList search(Session session, String queryType, String query)
472
    throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
473
    NotImplemented {
474

    
475
    ObjectList objectList = null;
476
    try {
477
        objectList = 
478
          IdentifierManager.getInstance().querySystemMetadata(
479
              null, //startTime, 
480
              null, //endTime,
481
              null, //objectFormat, 
482
              false, //replicaStatus, 
483
              0, //start, 
484
              -1 //count
485
              );
486
        
487
    } catch (Exception e) {
488
      throw new ServiceFailure("4310", "Error querying system metadata: " + e.getMessage());
489
    }
490

    
491
      return objectList;
492
      
493
    //throw new NotImplemented("4281", "search not implemented");
494
    
495
    // the code block below is from an older implementation
496
    
497
    /*  This block commented out because of the EcoGrid circular dependency.
498
         *  For now, query will not be supported until the circularity can be
499
         *  resolved, probably by moving the ecogrid query syntax transformers
500
         *  directly into the Metacat codebase.  MBJ 2010-02-03
501
         
502
        try {
503
            EcogridQueryParser parser = new EcogridQueryParser(request
504
                    .getReader());
505
            parser.parseXML();
506
            QueryType queryType = parser.getEcogridQuery();
507
            EcogridJavaToMetacatJavaQueryTransformer queryTransformer = 
508
                new EcogridJavaToMetacatJavaQueryTransformer();
509
            QuerySpecification metacatQuery = queryTransformer
510
                    .transform(queryType);
511

    
512
            DBQuery metacat = new DBQuery();
513

    
514
            boolean useXMLIndex = (new Boolean(PropertyService
515
                    .getProperty("database.usexmlindex"))).booleanValue();
516
            String xmlquery = "query"; // we don't care the query in resultset,
517
            // the query can be anything
518
            PrintWriter out = null; // we don't want metacat result, so set out null
519

    
520
            // parameter: queryspecification, user, group, usingIndexOrNot
521
            StringBuffer result = metacat.createResultDocument(xmlquery,
522
                    metacatQuery, out, username, groupNames, useXMLIndex);
523

    
524
            // create result set transfer       
525
            String saxparser = PropertyService.getProperty("xml.saxparser");
526
            MetacatResultsetParser metacatResultsetParser = new MetacatResultsetParser(
527
                    new StringReader(result.toString()), saxparser, queryType
528
                            .getNamespace().get_value());
529
            ResultsetType records = metacatResultsetParser.getEcogridResult();
530

    
531
            System.out
532
                    .println(EcogridResultsetTransformer.toXMLString(records));
533
            response.setContentType("text/xml");
534
            out = response.getWriter();
535
            out.print(EcogridResultsetTransformer.toXMLString(records));
536

    
537
        } catch (Exception e) {
538
            e.printStackTrace();
539
        }*/
540
    
541

    
542
  }
543
  
544
  /**
545
   * Returns the object format registered in the DataONE Object Format 
546
   * Vocabulary for the given format identifier
547
   * 
548
   * @param fmtid - the identifier of the format requested
549
   * 
550
   * @return objectFormat - the object format requested
551
   * 
552
   * @throws ServiceFailure
553
   * @throws NotFound
554
   * @throws InsufficientResources
555
   * @throws NotImplemented
556
   */
557
  @Override
558
  public ObjectFormat getFormat(ObjectFormatIdentifier fmtid)
559
    throws ServiceFailure, NotFound, InsufficientResources,
560
    NotImplemented {
561
     
562
      return ObjectFormatService.getInstance().getFormat(fmtid);
563
      
564
  }
565

    
566
  /**
567
   * Returns a list of all object formats registered in the DataONE Object 
568
   * Format Vocabulary
569
    * 
570
   * @return objectFormatList - The list of object formats registered in 
571
   *                            the DataONE Object Format Vocabulary
572
   * 
573
   * @throws ServiceFailure
574
   * @throws NotImplemented
575
   * @throws InsufficientResources
576
   */
577
  @Override
578
  public ObjectFormatList listFormats() 
579
    throws ServiceFailure, InsufficientResources, 
580
    NotImplemented {
581

    
582
    return ObjectFormatService.getInstance().listFormats();
583
  }
584

    
585
  /**
586
   * Returns a list of nodes that have been registered with the DataONE infrastructure
587
    * 
588
   * @return nodeList - List of nodes from the registry
589
   * 
590
   * @throws ServiceFailure
591
   * @throws NotImplemented
592
   */
593
  @Override
594
  public NodeList listNodes() 
595
    throws NotImplemented, ServiceFailure {
596

    
597
    throw new NotImplemented("4800", "listNodes not implemented");
598
  }
599

    
600
  /**
601
   * Provides a mechanism for adding system metadata independently of its 
602
   * associated object, such as when adding system metadata for data objects.
603
    * 
604
   * @param session - the Session object containing the credentials for the Subject
605
   * @param pid - The identifier of the object to register the system metadata against
606
   * @param sysmeta - The system metadata to be registered
607
   * 
608
   * @return true if the registration succeeds
609
   * 
610
   * @throws NotImplemented
611
   * @throws NotAuthorized
612
   * @throws ServiceFailure
613
   * @throws InvalidRequest
614
   * @throws InvalidSystemMetadata
615
   */
616
  @Override
617
  public Identifier registerSystemMetadata(Session session, Identifier pid,
618
      SystemMetadata sysmeta) 
619
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest, 
620
      InvalidSystemMetadata {
621

    
622
      // TODO: control who can call this?
623
      if (session == null) {
624
          //TODO: many of the thrown exceptions do not use the correct error codes
625
          //check these against the docs and correct them
626
          throw new NotAuthorized("4861", "No Session - could not authorize for registration." +
627
                  "  If you are not logged in, please do so and retry the request.");
628
      }
629
      
630
      // verify that guid == SystemMetadata.getIdentifier()
631
      logMetacat.debug("Comparing guid|sysmeta_guid: " + pid.getValue() + 
632
          "|" + sysmeta.getIdentifier().getValue());
633
      if (!pid.getValue().equals(sysmeta.getIdentifier().getValue())) {
634
          throw new InvalidRequest("4863", 
635
              "The identifier in method call (" + pid.getValue() + 
636
              ") does not match identifier in system metadata (" +
637
              sysmeta.getIdentifier().getValue() + ").");
638
      }
639

    
640
      logMetacat.debug("Checking if identifier exists...");
641
      // Check that the identifier does not already exist
642
      if (HazelcastService.getInstance().getSystemMetadataMap().containsKey(pid)) {
643
          throw new InvalidRequest("4863", 
644
              "The identifier is already in use by an existing object.");
645
      
646
      }
647
      
648
      // insert the system metadata into the object store
649
      logMetacat.debug("Starting to insert SystemMetadata...");
650
      try {
651
          HazelcastService.getInstance().getSystemMetadataMap().lock(sysmeta.getIdentifier());
652
          sysmeta.setSerialVersion(BigInteger.ONE);
653
          sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
654
          HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
655
          HazelcastService.getInstance().getSystemMetadataMap().unlock(sysmeta.getIdentifier());
656
          
657
      } catch (Exception e) {
658
        logMetacat.error("Problem registering system metadata: " + pid.getValue(), e);
659
          throw new ServiceFailure("4862", "Error inserting system metadata: " + 
660
              e.getClass() + ": " + e.getMessage());
661
          
662
      } finally {
663
        HazelcastService.getInstance().getSystemMetadataMap().unlock(sysmeta.getIdentifier());
664

    
665
      }
666
      
667
      logMetacat.debug("Returning from registerSystemMetadata");
668
      EventLog.getInstance().log(request.getRemoteAddr(), 
669
          request.getHeader("User-Agent"), session.getSubject().getValue(), 
670
          pid.getValue(), "registerSystemMetadata");
671
      return pid;
672
  }
673
  
674
  /**
675
   * Given an optional scope and format, reserves and returns an identifier 
676
   * within that scope and format that is unique and will not be 
677
   * used by any other sessions. 
678
    * 
679
   * @param session - the Session object containing the credentials for the Subject
680
   * @param pid - The identifier of the object to register the system metadata against
681
   * @param scope - An optional string to be used to qualify the scope of 
682
   *                the identifier namespace, which is applied differently 
683
   *                depending on the format requested. If scope is not 
684
   *                supplied, a default scope will be used.
685
   * @param format - The optional name of the identifier format to be used, 
686
   *                  drawn from a DataONE-specific vocabulary of identifier 
687
   *                 format names, including several common syntaxes such 
688
   *                 as DOI, LSID, UUID, and LSRN, among others. If the 
689
   *                 format is not supplied by the caller, the CN service 
690
   *                 will use a default identifier format, which may change 
691
   *                 over time.
692
   * 
693
   * @return true if the registration succeeds
694
   * 
695
   * @throws InvalidToken
696
   * @throws ServiceFailure
697
   * @throws NotAuthorized
698
   * @throws IdentifierNotUnique
699
   * @throws NotImplemented
700
   */
701
  @Override
702
  public Identifier reserveIdentifier(Session session, Identifier pid)
703
  throws InvalidToken, ServiceFailure,
704
        NotAuthorized, IdentifierNotUnique, NotImplemented, InvalidRequest {
705

    
706
    throw new NotImplemented("4191", "reserveIdentifier not implemented on this node");
707
  }
708
  
709
  @Override
710
  public Identifier generateIdentifier(Session session, String scheme, String fragment)
711
  throws InvalidToken, ServiceFailure,
712
        NotAuthorized, NotImplemented, InvalidRequest {
713
    throw new NotImplemented("4191", "generateIdentifier not implemented on this node");
714
  }
715
  
716
  /**
717
    * Checks whether the pid is reserved by the subject in the session param
718
    * If the reservation is held on the pid by the subject, we return true.
719
    * 
720
   * @param session - the Session object containing the Subject
721
   * @param pid - The identifier to check
722
   * 
723
   * @return true if the reservation exists for the subject/pid
724
   * 
725
   * @throws InvalidToken
726
   * @throws ServiceFailure
727
   * @throws NotFound - when the pid is not found (in use or in reservation)
728
   * @throws NotAuthorized - when the subject does not hold a reservation on the pid
729
   * @throws IdentifierNotUnique - when the pid is in use
730
   * @throws NotImplemented
731
   */
732

    
733
  @Override
734
  public boolean hasReservation(Session session, Identifier pid) 
735
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, IdentifierNotUnique, 
736
      NotImplemented, InvalidRequest {
737
  
738
      throw new NotImplemented("4191", "hasReservation not implemented on this node");
739
  }
740

    
741
  /**
742
   * Changes ownership (RightsHolder) of the specified object to the 
743
   * subject specified by userId
744
    * 
745
   * @param session - the Session object containing the credentials for the Subject
746
   * @param pid - Identifier of the object to be modified
747
   * @param userId - The subject that will be taking ownership of the specified object.
748
   *
749
   * @return pid - the identifier of the modified object
750
   * 
751
   * @throws ServiceFailure
752
   * @throws InvalidToken
753
   * @throws NotFound
754
   * @throws NotAuthorized
755
   * @throws NotImplemented
756
   * @throws InvalidRequest
757
   */  
758
  @Override
759
  public Identifier setOwner(Session session, Identifier pid, Subject userId,
760
      long serialVersion)
761
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized,
762
      NotImplemented, InvalidRequest {
763
      
764
      // get the subject
765
      Subject subject = session.getSubject();
766
      
767
      // are we allowed to do this?
768
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
769
        throw new NotAuthorized("4440", "not allowed by " + subject.getValue() + " on " + pid.getValue());  
770
      }
771
      
772
      SystemMetadata systemMetadata = null;
773
      try {
774
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
775
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
776
          
777
          // does the request have the most current system metadata?
778
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
779
             String msg = "The requested system metadata version number " + 
780
                 serialVersion + "differs from the current version at " +
781
                 systemMetadata.getSerialVersion().longValue() +
782
                 " Please get the latest copy in order to modify it.";
783
             throw new InvalidRequest("4442", msg);
784
          }
785
          
786
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
787
          throw new NotFound("4460", "No record found for: " + pid.getValue());
788
          
789
      }
790
          
791
      // set the new rights holder
792
      systemMetadata.setRightsHolder(userId);
793
      
794
      // update the metadata
795
      try {
796
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
797
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
798
          HazelcastService.getInstance().getSystemMetadataMap().put(pid, systemMetadata);
799
          HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
800
          
801
      } catch (Exception e) {
802
      throw new ServiceFailure("4490", e.getMessage());
803
      
804
      } finally {
805
          HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
806
      }
807
      
808
      return pid;
809
  }
810

    
811
  /**
812
   * Verify that a replication task is authorized by comparing the target node's
813
   * Subject (from the X.509 certificate-derived Session) with the list of 
814
   * subjects in the known, pending replication tasks map.
815
   * 
816
   * @param originatingNodeSession - Session information that contains the 
817
   *                                 identity of the calling user
818
   * @param targetNodeSubject - Subject identifying the target node
819
   * @param pid - the identifier of the object to be replicated
820
   * @param replicatePermission - the execute permission to be granted
821
   * 
822
   * @throws ServiceFailure
823
   * @throws NotImplemented
824
   * @throws InvalidToken
825
   * @throws NotAuthorized
826
   * @throws InvalidRequest
827
   * @throws NotFound
828
   */
829
  @Override
830
  public boolean isNodeAuthorized(Session originatingNodeSession, 
831
    Subject targetNodeSubject, Identifier pid, Permission replicatePermission) 
832
    throws NotImplemented, NotAuthorized, InvalidToken, ServiceFailure, 
833
    NotFound, InvalidRequest {
834

    
835
    boolean isAllowed = false;
836
    SystemMetadata sysmeta = null;
837
    NodeReference targetNode = null;
838
    
839
    try {
840
      // get the target node reference from the nodes list
841
      CNode cn = D1Client.getCN();
842
      List<Node> nodes = cn.listNodes().getNodeList();
843

    
844
      for ( Node node : nodes ) {
845
          Subject nodeSubject = node.getSubject(0);
846
          if (nodeSubject.getValue().equals(targetNodeSubject)) {
847
              targetNode = node.getIdentifier();
848
              
849
          }
850
      }
851
      
852

    
853
      //lock, get, and unlock the pid
854
      HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
855
      sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
856
      List<Replica> replicaList = sysmeta.getReplicaList();
857
      
858
      // find the replica with the status set to 'requested'
859
      for (Replica replica : replicaList) {
860
          ReplicationStatus status = replica.getReplicationStatus();
861
          NodeReference listedNode = replica.getReplicaMemberNode();
862
          if (listedNode.equals(targetNode)
863
                  && status.equals(ReplicationStatus.REQUESTED)) {
864
              isAllowed = true;
865
              break;
866

    
867
          }
868
      }
869
      
870
      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
871

    
872
    } catch(RuntimeException e) {
873
        // Catch Hazelcast RuntimeExceptions
874
        throw new ServiceFailure("4872", 
875
            "RuntimeException: Couldn't determine if node is allowed: " + 
876
            e.getStackTrace().toString());
877
      
878
    } catch(Exception e) {
879
        throw new ServiceFailure("4872", 
880
                "General Exception: Couldn't determine if node is allowed: " + 
881
                e.getStackTrace().toString());
882
        
883
    } finally {
884
      // always unlock the pid
885
      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
886

    
887
    }
888
      
889
    return isAllowed;
890
    
891
  }
892

    
893
  /**
894
   * Adds a new object to the Node, where the object is a science metadata object.
895
   * 
896
   * @param session - the Session object containing the credentials for the Subject
897
   * @param pid - The object identifier to be created
898
   * @param object - the object bytes
899
   * @param sysmeta - the system metadata that describes the object  
900
   * 
901
   * @return pid - the object identifier created
902
   * 
903
   * @throws InvalidToken
904
   * @throws ServiceFailure
905
   * @throws NotAuthorized
906
   * @throws IdentifierNotUnique
907
   * @throws UnsupportedType
908
   * @throws InsufficientResources
909
   * @throws InvalidSystemMetadata
910
   * @throws NotImplemented
911
   * @throws InvalidRequest
912
   */
913
  public Identifier create(Session session, Identifier pid, InputStream object,
914
    SystemMetadata sysmeta) 
915
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
916
    UnsupportedType, InsufficientResources, InvalidSystemMetadata, 
917
    NotImplemented, InvalidRequest {
918
      
919
      
920
      try {
921
        // are we allowed?
922
          boolean isAllowed = false;
923
          CNode cn = D1Client.getCN();
924
          List<Node> nodes = (List<Node>) cn.listNodes();
925
          
926
          for (Node node : nodes) {
927
              if ( node.getType().equals(NodeType.CN) ) {
928
                  
929
                  List<Subject> subjects = node.getSubjectList();
930
                  for (Subject subject : subjects) {
931
                     if (subject.getValue().equals(session.getSubject().getValue())) {
932
                         isAllowed = true;
933
                         break;
934
                     }
935
                  }
936
              }
937
          }
938

    
939
          // proceed if we're called by a CN
940
          if ( isAllowed ) {
941
              // create the coordinating node version of the document      
942
              HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
943
              sysmeta.setSerialVersion(BigInteger.ONE);
944
              sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
945
              pid = super.create(session, pid, object, sysmeta);
946
              HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
947

    
948
          } else {
949
              String msg = "The subject listed as " + session.getSubject().getValue() + 
950
                  " isn't allowed to call create() on a Coordinating Node.";
951
              logMetacat.info(msg);
952
              throw new NotAuthorized("1100", msg);
953
          }
954
          
955
      } catch (Exception e) {
956
          // Convert Hazelcast runtime exceptions to service failures
957
          String msg = "There was a problem creating the object identified by " +
958
              pid.getValue() + ". There error message was: " + e.getMessage();
959
          
960
      } finally {
961
          HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
962
      
963
      }
964
      
965
      return pid;
966

    
967
  }
968

    
969
  /**
970
   * Set access for a given object using the object identifier and a Subject
971
   * under a given Session.
972
   * 
973
   * @param session - the Session object containing the credentials for the Subject
974
   * @param pid - the object identifier for the given object to apply the policy
975
   * @param policy - the access policy to be applied
976
   * 
977
   * @return true if the application of the policy succeeds
978
   * @throws InvalidToken
979
   * @throws ServiceFailure
980
   * @throws NotFound
981
   * @throws NotAuthorized
982
   * @throws NotImplemented
983
   * @throws InvalidRequest
984
   */
985
  public boolean setAccessPolicy(Session session, Identifier pid, 
986
      AccessPolicy accessPolicy, long serialVersion) 
987
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, 
988
      NotImplemented, InvalidRequest {
989
      
990
      boolean success = false;
991
      
992
      // get the subject
993
      Subject subject = session.getSubject();
994
      
995
      // are we allowed to do this?
996
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
997
          throw new NotAuthorized("4420", "not allowed by " + subject.getValue() + 
998
          " on " + pid.getValue());  
999
      }
1000
      
1001
      SystemMetadata systemMetadata = null;
1002
      try {
1003
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1004
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1005

    
1006
          // does the request have the most current system metadata?
1007
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
1008
             String msg = "The requested system metadata version number " + 
1009
                 serialVersion + "differs from the current version at " +
1010
                 systemMetadata.getSerialVersion().longValue() +
1011
                 " Please get the latest copy in order to modify it.";
1012
             throw new InvalidRequest("4402", msg);
1013
          }
1014
          
1015
      } catch (Exception e) {
1016
          // convert Hazelcast RuntimeException to NotFound
1017
          throw new NotFound("4400", "No record found for: " + pid);
1018
        
1019
      }
1020
          
1021
      // set the access policy
1022
      systemMetadata.setAccessPolicy(accessPolicy);
1023
      
1024
      // update the system metadata
1025
      try {
1026
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1027
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1028
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1029
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1030
        
1031
      } catch (Exception e) {
1032
          // convert Hazelcast RuntimeException to ServiceFailure
1033
          throw new ServiceFailure("4430", e.getMessage());
1034
        
1035
      } finally {
1036
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1037
        
1038
      }
1039
    
1040
    // TODO: how do we know if the map was persisted?
1041
    success = true;
1042
    
1043
    return success;
1044
  }
1045

    
1046
  /**
1047
   * Full replacement of replication metadata in the system metadata for the 
1048
   * specified object, changes date system metadata modified
1049
   * 
1050
   * @param session - the Session object containing the credentials for the Subject
1051
   * @param pid - the object identifier for the given object to apply the policy
1052
   * @param replica - the replica to be updated
1053
   * @return
1054
   * @throws NotImplemented
1055
   * @throws NotAuthorized
1056
   * @throws ServiceFailure
1057
   * @throws InvalidRequest
1058
   * @throws NotFound
1059
   */
1060
  public boolean updateReplicationMetadata(Session session, Identifier pid,
1061
      Replica replica, long serialVersion) 
1062
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest,
1063
      NotFound {
1064
      
1065
      // get the subject
1066
      Subject subject = session.getSubject();
1067
      
1068
      // are we allowed to do this?
1069
      try {
1070
        // what is the controlling permission?
1071
        if (!isAuthorized(session, pid, Permission.WRITE)) {
1072
            throw new NotAuthorized("4851", "not allowed by " + subject.getValue() + 
1073
            " on " + pid.getValue());  
1074
        }
1075
        
1076
      } catch (InvalidToken e) {
1077
          throw new NotAuthorized("4851", "not allowed by " + subject.getValue() + 
1078
                  " on " + pid.getValue());  
1079
          
1080
      }
1081

    
1082
      SystemMetadata systemMetadata = null;
1083
      try {      
1084
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1085
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1086

    
1087
          // does the request have the most current system metadata?
1088
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
1089
             String msg = "The requested system metadata version number " + 
1090
                 serialVersion + "differs from the current version at " +
1091
                 systemMetadata.getSerialVersion().longValue() +
1092
                 " Please get the latest copy in order to modify it.";
1093
             throw new InvalidRequest("4853", msg);
1094
          }
1095
          
1096
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
1097
        throw new NotFound("4854", "No record found for: " + pid.getValue() +
1098
            " : " + e.getMessage());
1099
        
1100
      }
1101
          
1102
      // set the status for the replica
1103
      List<Replica> replicas = systemMetadata.getReplicaList();
1104
      NodeReference replicaNode = replica.getReplicaMemberNode();
1105
      int index = 0;
1106
      for (Replica listedReplica: replicas) {
1107
          
1108
          // remove the replica that we are replacing
1109
          if ( replicaNode.getValue().equals(listedReplica.getReplicaMemberNode().getValue())) {
1110
              replicas.remove(index);
1111
              break;
1112
              
1113
          }
1114
          index++;
1115
      }
1116
      
1117
      // add the new replica item
1118
      replicas.add(replica);
1119
      systemMetadata.setReplicaList(replicas);
1120
      
1121
      // update the metadata
1122
      try {
1123
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1124
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1125
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1126
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1127
        
1128
      } catch (Exception e) {
1129
          throw new ServiceFailure("4852", e.getMessage());
1130
      
1131
      } finally {
1132
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1133
          
1134
      }
1135
    
1136
      return true;
1137
      
1138
  }
1139
  
1140
    @Override
1141
    public ObjectList listObjects(Session session, Date startTime, 
1142
            Date endTime, ObjectFormatIdentifier formatid, Boolean replicaStatus,
1143
            Integer start, Integer count)
1144
      throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1145
      ServiceFailure {
1146
      
1147
      ObjectList objectList = null;
1148
        try {
1149
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, formatid, replicaStatus, start, count);
1150
        } catch (Exception e) {
1151
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1152
        }
1153

    
1154
        return objectList;
1155
  }
1156
}
(1-1/4)