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(Exception e) {
873
    	ServiceFailure sf = new ServiceFailure("4872", 
874
                "Couldn't determine if node is allowed: " + 
875
                e.getMessage());
876
    	sf.initCause(e);
877
        throw sf;
878
        
879
    } finally {
880
      // always unlock the pid
881
      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
882

    
883
    }
884
      
885
    return isAllowed;
886
    
887
  }
888

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

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

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

    
963
  }
964

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

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

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

    
1078
      SystemMetadata systemMetadata = null;
1079
      try {      
1080
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1081
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1082

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

    
1150
        return objectList;
1151
  }
1152
}
(1-1/4)