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.service.cn.v1.CNAuthorization;
41
import org.dataone.service.cn.v1.CNCore;
42
import org.dataone.service.cn.v1.CNRead;
43
import org.dataone.service.cn.v1.CNReplication;
44
import org.dataone.service.exceptions.IdentifierNotUnique;
45
import org.dataone.service.exceptions.InsufficientResources;
46
import org.dataone.service.exceptions.InvalidRequest;
47
import org.dataone.service.exceptions.InvalidSystemMetadata;
48
import org.dataone.service.exceptions.InvalidToken;
49
import org.dataone.service.exceptions.NotAuthorized;
50
import org.dataone.service.exceptions.NotFound;
51
import org.dataone.service.exceptions.NotImplemented;
52
import org.dataone.service.exceptions.ServiceFailure;
53
import org.dataone.service.exceptions.UnsupportedType;
54
import org.dataone.service.types.v1.AccessPolicy;
55
import org.dataone.service.types.v1.Checksum;
56
import org.dataone.service.types.v1.Identifier;
57
import org.dataone.service.types.v1.Node;
58
import org.dataone.service.types.v1.NodeList;
59
import org.dataone.service.types.v1.NodeReference;
60
import org.dataone.service.types.v1.NodeType;
61
import org.dataone.service.types.v1.ObjectFormat;
62
import org.dataone.service.types.v1.ObjectFormatIdentifier;
63
import org.dataone.service.types.v1.ObjectFormatList;
64
import org.dataone.service.types.v1.ObjectList;
65
import org.dataone.service.types.v1.ObjectLocationList;
66
import org.dataone.service.types.v1.Permission;
67
import org.dataone.service.types.v1.Replica;
68
import org.dataone.service.types.v1.ReplicationPolicy;
69
import org.dataone.service.types.v1.ReplicationStatus;
70
import org.dataone.service.types.v1.Session;
71
import org.dataone.service.types.v1.Subject;
72
import org.dataone.service.types.v1.SystemMetadata;
73
import org.dataone.service.types.v1.util.ChecksumUtil;
74
import org.dataone.service.util.Constants;
75

    
76
import com.hazelcast.query.SqlPredicate;
77

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

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

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

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

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

    
183
  /**
184
   * Set the replication status for an object given the object identifier
185
   * 
186
   * @param session - the Session object containing the credentials for the Subject
187
   * @param pid - the object identifier for the given object
188
   * @param status - the replication status to be applied
189
   * 
190
   * @return true or false
191
   * 
192
   * @throws NotImplemented
193
   * @throws NotAuthorized
194
   * @throws ServiceFailure
195
   * @throws InvalidRequest
196
   * @throws InvalidToken
197
   * @throws NotFound
198
   * 
199
   */
200
  @Override
201
  public boolean setReplicationStatus(Session session, Identifier pid,
202
    NodeReference targetNode, ReplicationStatus status, long serialVersion) 
203
    throws ServiceFailure, NotImplemented, InvalidToken, NotAuthorized, 
204
    InvalidRequest, NotFound {
205

    
206
    // get the subject
207
    Subject subject = session.getSubject();
208
    
209
    // are we allowed to do this?
210
    if (!isAuthorized(session, pid, Permission.WRITE)) {
211
      throw new NotAuthorized("4720", Permission.WRITE + " not allowed by " + 
212
          subject.getValue() + " on " + pid.getValue());  
213
    }
214
    
215
    SystemMetadata systemMetadata = null;
216
    try {      
217
        HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
218
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
219

    
220

    
221
        // does the request have the most current system metadata?
222
        if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
223
           String msg = "The requested system metadata version number " + 
224
               serialVersion + "differs from the current version at " +
225
               systemMetadata.getSerialVersion().longValue() +
226
               " Please get the latest copy in order to modify it.";
227
           throw new InvalidRequest("4730", msg);
228
        }
229
        
230
    } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
231
      throw new NotFound("4740", "No record found for: " + pid.getValue() +
232
          " : " + e.getMessage());
233
      
234
    }
235
        
236
    // set the status for the replica
237
    List<Replica> replicas = systemMetadata.getReplicaList();
238
    for (Replica replica: replicas) {
239
        if (replica.getReplicaMemberNode().getValue().equals(targetNode.getValue())) {
240
            replica.setReplicationStatus(status);
241
            
242
        }
243
    }
244
    
245
    // [re]set the list -- redundant?
246
    systemMetadata.setReplicaList(replicas);
247
    
248
    // update the metadata
249
    try {
250
        systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
251
        systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
252
	      HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
253
	      HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
254
	    
255
    } catch (Exception e) {
256
		    throw new ServiceFailure("4700", e.getMessage());
257
		
258
	  } finally {
259
	      HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
260
	      
261
	  }
262
	
263
    return true;
264
  }
265

    
266
  /**
267
   * Test that the specified relationship between pidOfSubject and pidOfObject exists
268
   * 
269
   * @param session - the Session object containing the credentials for the Subject
270
   * @param node - the node information for the given node be modified
271
   * 
272
   * @return true if the relationship exists
273
   * 
274
   * @throws InvalidToken
275
   * @throws ServiceFailure
276
   * @throws NotAuthorized
277
   * @throws NotFound
278
   * @throws InvalidRequest
279
   * @throws NotImplemented
280
   */
281
  @Override
282
  public boolean assertRelation(Session session, Identifier pidOfSubject, 
283
    String relationship, Identifier pidOfObject) 
284
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
285
    InvalidRequest, NotImplemented {
286
    
287
    boolean asserted = false;
288
        
289
    // are we allowed to do this?
290
    if (!isAuthorized(session, pidOfSubject, Permission.READ)) {
291
      throw new NotAuthorized("4881", Permission.READ + " not allowed on " + pidOfSubject.getValue());  
292
    }
293
    
294
    SystemMetadata systemMetadata = null;
295
    try {
296
        HazelcastService.getInstance().getSystemMetadataMap().lock(pidOfSubject);
297
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pidOfSubject);
298
        
299
        
300
        // check relationships
301
        // TODO: use ORE map
302
        if (relationship.equalsIgnoreCase("describes")) {
303
          
304
        }
305
        
306
        if (relationship.equalsIgnoreCase("describedBy")) {
307
          
308
        }
309
        
310
        if (relationship.equalsIgnoreCase("derivedFrom")) {
311
          
312
        }
313
        
314
        if (relationship.equalsIgnoreCase("obsoletes")) {
315
            Identifier pid = systemMetadata.getObsoletes();
316
            if (pid.getValue().equals(pidOfObject.getValue())) {
317
              asserted = true;
318
            
319
        }
320
          //return systemMetadata.getObsoleteList().contains(pidOfObject);
321
        }
322
        if (relationship.equalsIgnoreCase("obsoletedBy")) {
323
            Identifier pid = systemMetadata.getObsoletedBy();
324
            if (pid.getValue().equals(pidOfObject.getValue())) {
325
              asserted = true;
326
            
327
        }
328
          //return systemMetadata.getObsoletedByList().contains(pidOfObject);
329
        }
330
        
331
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pidOfSubject);
332
      
333
    } catch (Exception e) {
334
        throw new ServiceFailure("4270", "Could not assert relation for: " + 
335
            pidOfSubject.getValue() +
336
            ". The error message was: " + e.getMessage());
337
      
338
    } finally {
339
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pidOfSubject);
340

    
341
    }
342
        
343
    return asserted;
344
  }
345
  
346
  /**
347
   * Return the checksum of the object given the identifier 
348
   * 
349
   * @param session - the Session object containing the credentials for the Subject
350
   * @param pid - the object identifier for the given object
351
   * 
352
   * @return checksum - the checksum of the object
353
   * 
354
   * @throws InvalidToken
355
   * @throws ServiceFailure
356
   * @throws NotAuthorized
357
   * @throws NotFound
358
   * @throws NotImplemented
359
   */
360
  @Override
361
  public Checksum getChecksum(Session session, Identifier pid)
362
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
363
    NotImplemented {
364
        
365
    if (!isAuthorized(session, pid, Permission.READ)) {
366
        throw new NotAuthorized("1400", Permission.READ + " not allowed on " + pid.getValue());  
367
    }
368
    
369
    SystemMetadata systemMetadata = null;
370
    Checksum checksum = null;
371
    
372
    try {
373
        HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
374
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
375
        checksum = systemMetadata.getChecksum();
376
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
377

    
378
    } catch (Exception e) {
379
        throw new ServiceFailure("1410", "An error occurred getting the checksum for " + 
380
            pid.getValue() + ". The error message was: " + e.getMessage());
381
      
382
    } finally {
383
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
384
        
385
    }
386
    
387
    return checksum;
388
  }
389

    
390
  /**
391
   * Resolve the location of a given object
392
   * 
393
   * @param session - the Session object containing the credentials for the Subject
394
   * @param pid - the object identifier for the given object
395
   * 
396
   * @return objectLocationList - the list of nodes known to contain the object
397
   * 
398
   * @throws InvalidToken
399
   * @throws ServiceFailure
400
   * @throws NotAuthorized
401
   * @throws NotFound
402
   * @throws NotImplemented
403
   */
404
  @Override
405
  public ObjectLocationList resolve(Session session, Identifier pid)
406
    throws InvalidToken, ServiceFailure, NotAuthorized,
407
    NotFound, NotImplemented {
408

    
409
    throw new NotImplemented("4131", "resolve not implemented");
410

    
411
  }
412

    
413
  /**
414
   * Search the metadata catalog for identifiers that match the criteria
415
   * 
416
   * @param session - the Session object containing the credentials for the Subject
417
   * @param queryType - An identifier for the type of query expression 
418
   *                    provided in the query
419
   * @param query -  The criteria for matching the characteristics of the 
420
   *                 metadata objects of interest
421
   * 
422
   * @return objectList - the list of objects matching the criteria
423
   * 
424
   * @throws InvalidToken
425
   * @throws ServiceFailure
426
   * @throws NotAuthorized
427
   * @throws InvalidRequest
428
   * @throws NotImplemented
429
   */
430
  @Override
431
  public ObjectList search(Session session, String queryType, String query)
432
    throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
433
    NotImplemented {
434

    
435
    ObjectList objectList = null;
436
    try {
437
        objectList = 
438
          IdentifierManager.getInstance().querySystemMetadata(
439
              null, //startTime, 
440
              null, //endTime,
441
              null, //objectFormat, 
442
              false, //replicaStatus, 
443
              0, //start, 
444
              -1 //count
445
              );
446
        
447
    } catch (Exception e) {
448
      throw new ServiceFailure("4310", "Error querying system metadata: " + e.getMessage());
449
    }
450

    
451
      return objectList;
452
      
453
    //throw new NotImplemented("4281", "search not implemented");
454
    
455
    // the code block below is from an older implementation
456
    
457
    /*  This block commented out because of the EcoGrid circular dependency.
458
         *  For now, query will not be supported until the circularity can be
459
         *  resolved, probably by moving the ecogrid query syntax transformers
460
         *  directly into the Metacat codebase.  MBJ 2010-02-03
461
         
462
        try {
463
            EcogridQueryParser parser = new EcogridQueryParser(request
464
                    .getReader());
465
            parser.parseXML();
466
            QueryType queryType = parser.getEcogridQuery();
467
            EcogridJavaToMetacatJavaQueryTransformer queryTransformer = 
468
                new EcogridJavaToMetacatJavaQueryTransformer();
469
            QuerySpecification metacatQuery = queryTransformer
470
                    .transform(queryType);
471

    
472
            DBQuery metacat = new DBQuery();
473

    
474
            boolean useXMLIndex = (new Boolean(PropertyService
475
                    .getProperty("database.usexmlindex"))).booleanValue();
476
            String xmlquery = "query"; // we don't care the query in resultset,
477
            // the query can be anything
478
            PrintWriter out = null; // we don't want metacat result, so set out null
479

    
480
            // parameter: queryspecification, user, group, usingIndexOrNot
481
            StringBuffer result = metacat.createResultDocument(xmlquery,
482
                    metacatQuery, out, username, groupNames, useXMLIndex);
483

    
484
            // create result set transfer       
485
            String saxparser = PropertyService.getProperty("xml.saxparser");
486
            MetacatResultsetParser metacatResultsetParser = new MetacatResultsetParser(
487
                    new StringReader(result.toString()), saxparser, queryType
488
                            .getNamespace().get_value());
489
            ResultsetType records = metacatResultsetParser.getEcogridResult();
490

    
491
            System.out
492
                    .println(EcogridResultsetTransformer.toXMLString(records));
493
            response.setContentType("text/xml");
494
            out = response.getWriter();
495
            out.print(EcogridResultsetTransformer.toXMLString(records));
496

    
497
        } catch (Exception e) {
498
            e.printStackTrace();
499
        }*/
500
    
501

    
502
  }
503
  
504
  /**
505
   * Returns the object format registered in the DataONE Object Format 
506
   * Vocabulary for the given format identifier
507
   * 
508
   * @param fmtid - the identifier of the format requested
509
   * 
510
   * @return objectFormat - the object format requested
511
   * 
512
   * @throws ServiceFailure
513
   * @throws NotFound
514
   * @throws InsufficientResources
515
   * @throws NotImplemented
516
   */
517
  @Override
518
  public ObjectFormat getFormat(ObjectFormatIdentifier fmtid)
519
    throws ServiceFailure, NotFound, InsufficientResources,
520
    NotImplemented {
521
     
522
      return ObjectFormatService.getInstance().getFormat(fmtid);
523
      
524
  }
525

    
526
  /**
527
   * Returns a list of all object formats registered in the DataONE Object 
528
   * Format Vocabulary
529
    * 
530
   * @return objectFormatList - The list of object formats registered in 
531
   *                            the DataONE Object Format Vocabulary
532
   * 
533
   * @throws ServiceFailure
534
   * @throws NotImplemented
535
   * @throws InsufficientResources
536
   */
537
  @Override
538
  public ObjectFormatList listFormats() 
539
    throws ServiceFailure, InsufficientResources, 
540
    NotImplemented {
541

    
542
    return ObjectFormatService.getInstance().listFormats();
543
  }
544

    
545
  /**
546
   * Returns a list of nodes that have been registered with the DataONE infrastructure
547
    * 
548
   * @return nodeList - List of nodes from the registry
549
   * 
550
   * @throws ServiceFailure
551
   * @throws NotImplemented
552
   */
553
  @Override
554
  public NodeList listNodes() 
555
    throws NotImplemented, ServiceFailure {
556

    
557
    throw new NotImplemented("4800", "listNodes not implemented");
558
  }
559

    
560
  /**
561
   * Provides a mechanism for adding system metadata independently of its 
562
   * associated object, such as when adding system metadata for data objects.
563
    * 
564
   * @param session - the Session object containing the credentials for the Subject
565
   * @param pid - The identifier of the object to register the system metadata against
566
   * @param sysmeta - The system metadata to be registered
567
   * 
568
   * @return true if the registration succeeds
569
   * 
570
   * @throws NotImplemented
571
   * @throws NotAuthorized
572
   * @throws ServiceFailure
573
   * @throws InvalidRequest
574
   * @throws InvalidSystemMetadata
575
   */
576
  @Override
577
  public Identifier registerSystemMetadata(Session session, Identifier pid,
578
      SystemMetadata sysmeta) 
579
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest, 
580
      InvalidSystemMetadata {
581

    
582
      // TODO: control who can call this?
583
      if (session == null) {
584
          //TODO: many of the thrown exceptions do not use the correct error codes
585
          //check these against the docs and correct them
586
          throw new NotAuthorized("4861", "No Session - could not authorize for registration." +
587
                  "  If you are not logged in, please do so and retry the request.");
588
      }
589
      
590
      // verify that guid == SystemMetadata.getIdentifier()
591
      logMetacat.debug("Comparing guid|sysmeta_guid: " + pid.getValue() + 
592
          "|" + sysmeta.getIdentifier().getValue());
593
      if (!pid.getValue().equals(sysmeta.getIdentifier().getValue())) {
594
          throw new InvalidRequest("4863", 
595
              "The identifier in method call (" + pid.getValue() + 
596
              ") does not match identifier in system metadata (" +
597
              sysmeta.getIdentifier().getValue() + ").");
598
      }
599

    
600
      logMetacat.debug("Checking if identifier exists...");
601
      // Check that the identifier does not already exist
602
      if (HazelcastService.getInstance().getSystemMetadataMap().containsKey(pid)) {
603
          throw new InvalidRequest("4863", 
604
              "The identifier is already in use by an existing object.");
605
      
606
      }
607
      
608
      // insert the system metadata into the object store
609
      logMetacat.debug("Starting to insert SystemMetadata...");
610
      try {
611
          HazelcastService.getInstance().getSystemMetadataMap().lock(sysmeta.getIdentifier());
612
          sysmeta.setSerialVersion(BigInteger.ONE);
613
          sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
614
          HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
615
          HazelcastService.getInstance().getSystemMetadataMap().unlock(sysmeta.getIdentifier());
616
          
617
      } catch (Exception e) {
618
      	logMetacat.error("Problem registering system metadata: " + pid.getValue(), e);
619
          throw new ServiceFailure("4862", "Error inserting system metadata: " + 
620
              e.getClass() + ": " + e.getMessage());
621
          
622
      } finally {
623
        HazelcastService.getInstance().getSystemMetadataMap().unlock(sysmeta.getIdentifier());
624

    
625
      }
626
      
627
      logMetacat.debug("Returning from registerSystemMetadata");
628
      EventLog.getInstance().log(request.getRemoteAddr(), 
629
          request.getHeader("User-Agent"), session.getSubject().getValue(), 
630
          pid.getValue(), "registerSystemMetadata");
631
      return pid;
632
  }
633
  
634
  /**
635
   * Given an optional scope and format, reserves and returns an identifier 
636
   * within that scope and format that is unique and will not be 
637
   * used by any other sessions. 
638
    * 
639
   * @param session - the Session object containing the credentials for the Subject
640
   * @param pid - The identifier of the object to register the system metadata against
641
   * @param scope - An optional string to be used to qualify the scope of 
642
   *                the identifier namespace, which is applied differently 
643
   *                depending on the format requested. If scope is not 
644
   *                supplied, a default scope will be used.
645
   * @param format - The optional name of the identifier format to be used, 
646
   *                  drawn from a DataONE-specific vocabulary of identifier 
647
   *                 format names, including several common syntaxes such 
648
   *                 as DOI, LSID, UUID, and LSRN, among others. If the 
649
   *                 format is not supplied by the caller, the CN service 
650
   *                 will use a default identifier format, which may change 
651
   *                 over time.
652
   * 
653
   * @return true if the registration succeeds
654
   * 
655
   * @throws InvalidToken
656
   * @throws ServiceFailure
657
   * @throws NotAuthorized
658
   * @throws IdentifierNotUnique
659
   * @throws NotImplemented
660
   */
661
  @Override
662
  public Identifier reserveIdentifier(Session session, Identifier pid)
663
  throws InvalidToken, ServiceFailure,
664
        NotAuthorized, IdentifierNotUnique, NotImplemented, InvalidRequest {
665

    
666
    throw new NotImplemented("4191", "reserveIdentifier not implemented on this node");
667
  }
668
  
669
  @Override
670
  public Identifier generateIdentifier(Session session, String scheme, String fragment)
671
  throws InvalidToken, ServiceFailure,
672
        NotAuthorized, NotImplemented, InvalidRequest {
673
    throw new NotImplemented("4191", "generateIdentifier not implemented on this node");
674
  }
675
  
676
  /**
677
    * Checks whether the pid is reserved by the subject in the session param
678
    * If the reservation is held on the pid by the subject, we return true.
679
    * 
680
   * @param session - the Session object containing the Subject
681
   * @param pid - The identifier to check
682
   * 
683
   * @return true if the reservation exists for the subject/pid
684
   * 
685
   * @throws InvalidToken
686
   * @throws ServiceFailure
687
   * @throws NotFound - when the pid is not found (in use or in reservation)
688
   * @throws NotAuthorized - when the subject does not hold a reservation on the pid
689
   * @throws IdentifierNotUnique - when the pid is in use
690
   * @throws NotImplemented
691
   */
692

    
693
  @Override
694
  public boolean hasReservation(Session session, Identifier pid) 
695
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, IdentifierNotUnique, 
696
      NotImplemented, InvalidRequest {
697
  
698
      throw new NotImplemented("4191", "hasReservation not implemented on this node");
699
  }
700

    
701
  /**
702
   * Changes ownership (RightsHolder) of the specified object to the 
703
   * subject specified by userId
704
    * 
705
   * @param session - the Session object containing the credentials for the Subject
706
   * @param pid - Identifier of the object to be modified
707
   * @param userId - The subject that will be taking ownership of the specified object.
708
   *
709
   * @return pid - the identifier of the modified object
710
   * 
711
   * @throws ServiceFailure
712
   * @throws InvalidToken
713
   * @throws NotFound
714
   * @throws NotAuthorized
715
   * @throws NotImplemented
716
   * @throws InvalidRequest
717
   */  
718
  @Override
719
  public Identifier setOwner(Session session, Identifier pid, Subject userId,
720
      long serialVersion)
721
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized,
722
      NotImplemented, InvalidRequest {
723
      
724
      // get the subject
725
      Subject subject = session.getSubject();
726
      
727
      // are we allowed to do this?
728
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
729
        throw new NotAuthorized("4440", "not allowed by " + subject.getValue() + " on " + pid.getValue());  
730
      }
731
      
732
      SystemMetadata systemMetadata = null;
733
      try {
734
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
735
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
736
          
737
          // does the request have the most current system metadata?
738
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
739
             String msg = "The requested system metadata version number " + 
740
                 serialVersion + "differs from the current version at " +
741
                 systemMetadata.getSerialVersion().longValue() +
742
                 " Please get the latest copy in order to modify it.";
743
             throw new InvalidRequest("4442", msg);
744
          }
745
          
746
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
747
          throw new NotFound("4460", "No record found for: " + pid.getValue());
748
          
749
      }
750
          
751
      // set the new rights holder
752
      systemMetadata.setRightsHolder(userId);
753
      
754
      // update the metadata
755
      try {
756
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
757
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
758
	        HazelcastService.getInstance().getSystemMetadataMap().put(pid, systemMetadata);
759
	        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
760
	        
761
      } catch (Exception e) {
762
		  throw new ServiceFailure("4490", e.getMessage());
763
		  
764
	    } finally {
765
	        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
766
	    }
767
      
768
      return pid;
769
  }
770

    
771
  /**
772
   * Verify that a replication task is authorized by comparing the target node's
773
   * Subject (from the X.509 certificate-derived Session) with the list of 
774
   * subjects in the known, pending replication tasks map.
775
   * 
776
   * @param originatingNodeSession - Session information that contains the 
777
   *                                 identity of the calling user
778
   * @param targetNodeSubject - Subject identifying the target node
779
   * @param pid - the identifier of the object to be replicated
780
   * @param replicatePermission - the execute permission to be granted
781
   * 
782
   * @throws ServiceFailure
783
   * @throws NotImplemented
784
   * @throws InvalidToken
785
   * @throws NotAuthorized
786
   * @throws InvalidRequest
787
   * @throws NotFound
788
   */
789
  @Override
790
  public boolean isNodeAuthorized(Session originatingNodeSession, 
791
    Subject targetNodeSubject, Identifier pid, Permission replicatePermission) 
792
    throws NotImplemented, NotAuthorized, InvalidToken, ServiceFailure, 
793
    NotFound, InvalidRequest {
794

    
795
	  boolean isAllowed = false;
796
	  SystemMetadata sysmeta = null;
797
    NodeReference targetNode = null;
798
    
799
	  try {
800
	    // get the target node reference from the nodes list
801
	    CNode cn = D1Client.getCN();
802
	    List<Node> nodes = cn.listNodes().getNodeList();
803

    
804
	    for ( Node node : nodes ) {
805
	        Subject nodeSubject = node.getSubject(0);
806
	        if (nodeSubject.getValue().equals(targetNodeSubject)) {
807
	            targetNode = node.getIdentifier();
808
	            
809
	        }
810
	    }
811
	    
812

    
813
	    //lock, get, and unlock the pid
814
	    HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
815
	    sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
816
	    List<Replica> replicaList = sysmeta.getReplicaList();
817
	    
818
      // find the replica with the status set to 'requested'
819
      for (Replica replica : replicaList) {
820
          ReplicationStatus status = replica.getReplicationStatus();
821
          NodeReference listedNode = replica.getReplicaMemberNode();
822
          if (listedNode.equals(targetNode)
823
                  && status.equals(ReplicationStatus.REQUESTED)) {
824
              isAllowed = true;
825
              break;
826

    
827
          }
828
      }
829
      
830
      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
831

    
832
	  } catch(RuntimeException e) {
833
	      // Catch Hazelcast RuntimeExceptions
834
	      throw new ServiceFailure("4872", 
835
	          "RuntimeException: Couldn't determine if node is allowed: " + 
836
	          e.getStackTrace().toString());
837
	    
838
    } catch(Exception e) {
839
        throw new ServiceFailure("4872", 
840
                "General Exception: Couldn't determine if node is allowed: " + 
841
                e.getStackTrace().toString());
842
        
843
	  } finally {
844
	    // always unlock the pid
845
      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
846

    
847
	  }
848
	    
849
	  return isAllowed;
850
    
851
  }
852

    
853
  /**
854
   * Adds a new object to the Node, where the object is a science metadata object.
855
   * 
856
   * @param session - the Session object containing the credentials for the Subject
857
   * @param pid - The object identifier to be created
858
   * @param object - the object bytes
859
   * @param sysmeta - the system metadata that describes the object  
860
   * 
861
   * @return pid - the object identifier created
862
   * 
863
   * @throws InvalidToken
864
   * @throws ServiceFailure
865
   * @throws NotAuthorized
866
   * @throws IdentifierNotUnique
867
   * @throws UnsupportedType
868
   * @throws InsufficientResources
869
   * @throws InvalidSystemMetadata
870
   * @throws NotImplemented
871
   * @throws InvalidRequest
872
   */
873
  public Identifier create(Session session, Identifier pid, InputStream object,
874
    SystemMetadata sysmeta) 
875
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
876
    UnsupportedType, InsufficientResources, InvalidSystemMetadata, 
877
    NotImplemented, InvalidRequest {
878
      
879
      
880
      try {
881
        // are we allowed?
882
          boolean isAllowed = false;
883
          CNode cn = D1Client.getCN();
884
          List<Node> nodes = (List<Node>) cn.listNodes();
885
          
886
          for (Node node : nodes) {
887
              if ( node.getType().equals(NodeType.CN) ) {
888
                  
889
                  List<Subject> subjects = node.getSubjectList();
890
                  for (Subject subject : subjects) {
891
                     if (subject.getValue().equals(session.getSubject().getValue())) {
892
                         isAllowed = true;
893
                         break;
894
                     }
895
                  }
896
              }
897
          }
898

    
899
          // proceed if we're called by a CN
900
          if ( isAllowed ) {
901
              // create the coordinating node version of the document      
902
              HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
903
              sysmeta.setSerialVersion(BigInteger.ONE);
904
              sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
905
              pid = super.create(session, pid, object, sysmeta);
906
              HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
907

    
908
          } else {
909
              String msg = "The subject listed as " + session.getSubject().getValue() + 
910
                  " isn't allowed to call create() on a Coordinating Node.";
911
              logMetacat.info(msg);
912
              throw new NotAuthorized("1100", msg);
913
          }
914
          
915
      } catch (Exception e) {
916
          // Convert Hazelcast runtime exceptions to service failures
917
          String msg = "There was a problem creating the object identified by " +
918
              pid.getValue() + ". There error message was: " + e.getMessage();
919
          
920
      } finally {
921
          HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
922
      
923
      }
924
      
925
      return pid;
926

    
927
  }
928

    
929
  /**
930
   * Set access for a given object using the object identifier and a Subject
931
   * under a given Session.
932
   * 
933
   * @param session - the Session object containing the credentials for the Subject
934
   * @param pid - the object identifier for the given object to apply the policy
935
   * @param policy - the access policy to be applied
936
   * 
937
   * @return true if the application of the policy succeeds
938
   * @throws InvalidToken
939
   * @throws ServiceFailure
940
   * @throws NotFound
941
   * @throws NotAuthorized
942
   * @throws NotImplemented
943
   * @throws InvalidRequest
944
   */
945
  public boolean setAccessPolicy(Session session, Identifier pid, 
946
      AccessPolicy accessPolicy, long serialVersion) 
947
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, 
948
      NotImplemented, InvalidRequest {
949
      
950
      boolean success = false;
951
      
952
      // get the subject
953
      Subject subject = session.getSubject();
954
      
955
      // are we allowed to do this?
956
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
957
          throw new NotAuthorized("4420", "not allowed by " + subject.getValue() + 
958
          " on " + pid.getValue());  
959
      }
960
      
961
      SystemMetadata systemMetadata = null;
962
      try {
963
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
964
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
965

    
966
          // does the request have the most current system metadata?
967
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
968
             String msg = "The requested system metadata version number " + 
969
                 serialVersion + "differs from the current version at " +
970
                 systemMetadata.getSerialVersion().longValue() +
971
                 " Please get the latest copy in order to modify it.";
972
             throw new InvalidRequest("4402", msg);
973
          }
974
          
975
      } catch (Exception e) {
976
          // convert Hazelcast RuntimeException to NotFound
977
          throw new NotFound("4400", "No record found for: " + pid);
978
        
979
      }
980
          
981
      // set the access policy
982
      systemMetadata.setAccessPolicy(accessPolicy);
983
      
984
      // update the system metadata
985
      try {
986
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
987
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
988
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
989
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
990
        
991
      } catch (Exception e) {
992
          // convert Hazelcast RuntimeException to ServiceFailure
993
          throw new ServiceFailure("4430", e.getMessage());
994
        
995
      } finally {
996
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
997
        
998
      }
999
    
1000
    // TODO: how do we know if the map was persisted?
1001
    success = true;
1002
    
1003
    return success;
1004
  }
1005

    
1006
  /**
1007
   * Full replacement of replication metadata in the system metadata for the 
1008
   * specified object, changes date system metadata modified
1009
   * 
1010
   * @param session - the Session object containing the credentials for the Subject
1011
   * @param pid - the object identifier for the given object to apply the policy
1012
   * @param replica - the replica to be updated
1013
   * @return
1014
   * @throws NotImplemented
1015
   * @throws NotAuthorized
1016
   * @throws ServiceFailure
1017
   * @throws InvalidRequest
1018
   * @throws NotFound
1019
   */
1020
  public boolean updateReplicationMetadata(Session session, Identifier pid,
1021
      Replica replica, long serialVersion) 
1022
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest,
1023
      NotFound {
1024
      
1025
      // get the subject
1026
      Subject subject = session.getSubject();
1027
      
1028
      // are we allowed to do this?
1029
      try {
1030
        // what is the controlling permission?
1031
        if (!isAuthorized(session, pid, Permission.WRITE)) {
1032
            throw new NotAuthorized("4851", "not allowed by " + subject.getValue() + 
1033
            " on " + pid.getValue());  
1034
        }
1035
        
1036
      } catch (InvalidToken e) {
1037
          throw new NotAuthorized("4851", "not allowed by " + subject.getValue() + 
1038
                  " on " + pid.getValue());  
1039
          
1040
      }
1041

    
1042
      SystemMetadata systemMetadata = null;
1043
      try {      
1044
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1045
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1046

    
1047
          // does the request have the most current system metadata?
1048
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
1049
             String msg = "The requested system metadata version number " + 
1050
                 serialVersion + "differs from the current version at " +
1051
                 systemMetadata.getSerialVersion().longValue() +
1052
                 " Please get the latest copy in order to modify it.";
1053
             throw new InvalidRequest("4853", msg);
1054
          }
1055
          
1056
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
1057
        throw new NotFound("4854", "No record found for: " + pid.getValue() +
1058
            " : " + e.getMessage());
1059
        
1060
      }
1061
          
1062
      // set the status for the replica
1063
      List<Replica> replicas = systemMetadata.getReplicaList();
1064
      NodeReference replicaNode = replica.getReplicaMemberNode();
1065
      int index = 0;
1066
      for (Replica listedReplica: replicas) {
1067
          
1068
          // remove the replica that we are replacing
1069
          if ( replicaNode.getValue().equals(listedReplica.getReplicaMemberNode().getValue())) {
1070
              replicas.remove(index);
1071
              break;
1072
              
1073
          }
1074
          index++;
1075
      }
1076
      
1077
      // add the new replica item
1078
      replicas.add(replica);
1079
      systemMetadata.setReplicaList(replicas);
1080
      
1081
      // update the metadata
1082
      try {
1083
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1084
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1085
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1086
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1087
        
1088
      } catch (Exception e) {
1089
          throw new ServiceFailure("4852", e.getMessage());
1090
      
1091
      } finally {
1092
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1093
          
1094
      }
1095
    
1096
      return true;
1097
      
1098
  }
1099
  
1100
  	@Override
1101
  	public ObjectList listObjects(Session session, Date startTime, 
1102
            Date endTime, ObjectFormatIdentifier formatid, Boolean replicaStatus,
1103
            Integer start, Integer count)
1104
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1105
			ServiceFailure {
1106
  		
1107
  		ObjectList objectList = null;
1108
        try {
1109
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, formatid, replicaStatus, start, count);
1110
        } catch (Exception e) {
1111
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1112
        }
1113

    
1114
        return objectList;
1115
	}
1116
}
(1-1/4)