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 InvalidRequest
359
   * @throws NotImplemented
360
   */
361
  @Override
362
  public Checksum getChecksum(Session session, Identifier pid)
363
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
364
    InvalidRequest, NotImplemented {
365
        
366
    if (!isAuthorized(session, pid, Permission.READ)) {
367
        throw new NotAuthorized("1400", Permission.READ + " not allowed on " + pid.getValue());  
368
    }
369
    
370
    SystemMetadata systemMetadata = null;
371
    Checksum checksum = null;
372
    
373
    try {
374
        HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
375
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
376
        checksum = systemMetadata.getChecksum();
377
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
378

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

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

    
411
    throw new NotImplemented("4131", "resolve not implemented");
412

    
413
  }
414

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

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

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

    
474
            DBQuery metacat = new DBQuery();
475

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

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

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

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

    
499
        } catch (Exception e) {
500
            e.printStackTrace();
501
        }*/
502
    
503

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

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

    
547
    return ObjectFormatService.getInstance().listFormats();
548
  }
549

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

    
562
    throw new NotImplemented("4800", "listNodes not implemented");
563
  }
564

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

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

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

    
630
      }
631
      
632
      logMetacat.debug("Returning from registerSystemMetadata");
633
      EventLog.getInstance().log(request.getRemoteAddr(), 
634
          request.getHeader("User-Agent"), session.getSubject().getValue(), 
635
          pid.getValue(), "registerSystemMetadata");
636
      return pid;
637
  }
638

    
639
  /**
640
   * Provides a mechanism for updating system metadata independently of its 
641
   * associated object
642
    * 
643
   * @param session - the Session object containing the credentials for the Subject
644
   * @param pid - The identifier of the system metadata
645
   * @param sysmeta - The system metadata to be registered
646
   * 
647
   * @return true if the update succeeds
648
   * 
649
   * @throws NotImplemented
650
   * @throws NotAuthorized
651
   * @throws ServiceFailure
652
   * @throws InvalidRequest
653
   * @throws InvalidSystemMetadata
654
   * @throws NotFound
655
   */
656
  @Override
657
  public boolean updateSystemMetadata(Session session, Identifier guid,
658
      SystemMetadata sysmeta) 
659
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest, 
660
      InvalidSystemMetadata, NotFound {
661

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

    
680
      logMetacat.debug("Checking if identifier exists...");
681
      // Check that the identifier exists
682
      if (!IdentifierManager.getInstance().identifierExists(guid.getValue())) {
683
          throw new NotFound("000", 
684
              "GUID does not exist");
685
      }
686

    
687
      // update the system metadata into the object store
688
      logMetacat.debug("Starting to update SystemMetadata...");
689
      
690
      // update system metadata
691
      try {
692
    	    HazelcastService.getInstance().getSystemMetadataMap().lock(sysmeta.getIdentifier());
693
          SystemMetadata currentSysMeta = 
694
              HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getIdentifier());
695
          
696
          // only update if the requester has the most current system metadata
697
          if (sysmeta.getSerialVersion().compareTo(currentSysMeta.getSerialVersion()) != 0 ) {
698
              String msg = "The serial version of the system metadata " + 
699
                  "to be updated does not equal the serial version of the current " +
700
                  "system metadata for identifier " + guid + ". Ensure that the " +
701
                  "copy is the most current before updating.";
702
              logMetacat.warn(msg);
703
              HazelcastService.getInstance().getSystemMetadataMap().unlock(sysmeta.getIdentifier());
704
              throw new InvalidRequest("4913", msg);
705
              
706
          }
707
          
708
          sysmeta.setSerialVersion(currentSysMeta.getSerialVersion().add(BigInteger.ONE));
709
          sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
710
          HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
711
    	    HazelcastService.getInstance().getSystemMetadataMap().unlock(sysmeta.getIdentifier());
712
    	    
713
      } catch (Exception e) {
714
    	throw new ServiceFailure("4852", e.getMessage());
715
    	
716
    	} finally {
717
    	    HazelcastService.getInstance().getSystemMetadataMap().unlock(sysmeta.getIdentifier());
718
    	  
719
    	}
720
      
721
      logMetacat.debug("Returning from updateSystemMetadata");
722
      EventLog.getInstance().log(request.getRemoteAddr(), 
723
                                 request.getHeader("User-Agent"), 
724
                                 session.getSubject().getValue(), 
725
                                 guid.getValue(), "updateSystemMetadata");
726
      return true;
727
  }
728
  
729
  /**
730
   * Given an optional scope and format, reserves and returns an identifier 
731
   * within that scope and format that is unique and will not be 
732
   * used by any other sessions. 
733
    * 
734
   * @param session - the Session object containing the credentials for the Subject
735
   * @param pid - The identifier of the object to register the system metadata against
736
   * @param scope - An optional string to be used to qualify the scope of 
737
   *                the identifier namespace, which is applied differently 
738
   *                depending on the format requested. If scope is not 
739
   *                supplied, a default scope will be used.
740
   * @param format - The optional name of the identifier format to be used, 
741
   *                  drawn from a DataONE-specific vocabulary of identifier 
742
   *                 format names, including several common syntaxes such 
743
   *                 as DOI, LSID, UUID, and LSRN, among others. If the 
744
   *                 format is not supplied by the caller, the CN service 
745
   *                 will use a default identifier format, which may change 
746
   *                 over time.
747
   * 
748
   * @return true if the registration succeeds
749
   * 
750
   * @throws InvalidToken
751
   * @throws ServiceFailure
752
   * @throws NotAuthorized
753
   * @throws IdentifierNotUnique
754
   * @throws NotImplemented
755
   */
756
  @Override
757
  public boolean reserveIdentifier(Session session, Identifier pid)
758
  throws InvalidToken, ServiceFailure,
759
        NotAuthorized, IdentifierNotUnique, NotImplemented, InvalidRequest {
760

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

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

    
796
  /**
797
   * Changes ownership (RightsHolder) of the specified object to the 
798
   * subject specified by userId
799
    * 
800
   * @param session - the Session object containing the credentials for the Subject
801
   * @param pid - Identifier of the object to be modified
802
   * @param userId - The subject that will be taking ownership of the specified object.
803
   *
804
   * @return pid - the identifier of the modified object
805
   * 
806
   * @throws ServiceFailure
807
   * @throws InvalidToken
808
   * @throws NotFound
809
   * @throws NotAuthorized
810
   * @throws NotImplemented
811
   * @throws InvalidRequest
812
   */  
813
  @Override
814
  public Identifier setOwner(Session session, Identifier pid, Subject userId,
815
      long serialVersion)
816
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized,
817
      NotImplemented, InvalidRequest {
818
      
819
      // get the subject
820
      Subject subject = session.getSubject();
821
      
822
      // are we allowed to do this?
823
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
824
        throw new NotAuthorized("4440", "not allowed by " + subject.getValue() + " on " + pid.getValue());  
825
      }
826
      
827
      SystemMetadata systemMetadata = null;
828
      try {
829
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
830
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
831
          
832
          // does the request have the most current system metadata?
833
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
834
             String msg = "The requested system metadata version number " + 
835
                 serialVersion + "differs from the current version at " +
836
                 systemMetadata.getSerialVersion().longValue() +
837
                 " Please get the latest copy in order to modify it.";
838
             throw new InvalidRequest("4442", msg);
839
          }
840
          
841
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
842
          throw new NotFound("4460", "No record found for: " + pid.getValue());
843
          
844
      }
845
          
846
      // set the new rights holder
847
      systemMetadata.setRightsHolder(userId);
848
      
849
      // update the metadata
850
      try {
851
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
852
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
853
	        HazelcastService.getInstance().getSystemMetadataMap().put(pid, systemMetadata);
854
	        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
855
	        
856
      } catch (Exception e) {
857
		  throw new ServiceFailure("4490", e.getMessage());
858
		  
859
	    } finally {
860
	        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
861
	    }
862
      
863
      return pid;
864
  }
865

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

    
890
	  boolean isAllowed = false;
891
	  SystemMetadata sysmeta = null;
892
    NodeReference targetNode = null;
893
    
894
	  try {
895
	    // get the target node reference from the nodes list
896
	    CNode cn = D1Client.getCN();
897
	    List<Node> nodes = (List<Node>) cn.listNodes();
898

    
899
	    for ( Node node : nodes ) {
900
	        Subject nodeSubject = node.getSubject(0);
901
	        if (nodeSubject.getValue().equals(targetNodeSubject)) {
902
	            targetNode = node.getIdentifier();
903
	            
904
	        }
905
	    }
906
	    
907

    
908
	    //lock, get, and unlock the pid
909
	    HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
910
	    sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
911
	    List<Replica> replicaList = sysmeta.getReplicaList();
912
	    
913
      // find the replica with the status set to 'requested'
914
      for (Replica replica : replicaList) {
915
          ReplicationStatus status = replica.getReplicationStatus();
916
          NodeReference listedNode = replica.getReplicaMemberNode();
917
          if (listedNode.equals(targetNode)
918
                  && status.equals(ReplicationStatus.REQUESTED)) {
919
              isAllowed = true;
920
              break;
921

    
922
          }
923
      }
924
      
925
      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
926

    
927
	  } catch(Exception e) {
928
	      // Catch Hazelcast RuntimeExceptions
929
	      throw new ServiceFailure("4872", 
930
	          "Couldn't determine if node is allowed: " + e.getMessage());
931
	    
932
	  } finally {
933
	    // always unlock the pid
934
      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
935

    
936
	  }
937
	    
938
	  return isAllowed;
939
    
940
  }
941

    
942
  /**
943
   * Adds a new object to the Node, where the object is a science metadata object.
944
   * 
945
   * @param session - the Session object containing the credentials for the Subject
946
   * @param pid - The object identifier to be created
947
   * @param object - the object bytes
948
   * @param sysmeta - the system metadata that describes the object  
949
   * 
950
   * @return pid - the object identifier created
951
   * 
952
   * @throws InvalidToken
953
   * @throws ServiceFailure
954
   * @throws NotAuthorized
955
   * @throws IdentifierNotUnique
956
   * @throws UnsupportedType
957
   * @throws InsufficientResources
958
   * @throws InvalidSystemMetadata
959
   * @throws NotImplemented
960
   * @throws InvalidRequest
961
   */
962
  public Identifier create(Session session, Identifier pid, InputStream object,
963
    SystemMetadata sysmeta) 
964
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
965
    UnsupportedType, InsufficientResources, InvalidSystemMetadata, 
966
    NotImplemented, InvalidRequest {
967
      
968
      
969
      try {
970
        // are we allowed?
971
          boolean isAllowed = false;
972
          CNode cn = D1Client.getCN();
973
          List<Node> nodes = (List<Node>) cn.listNodes();
974
          
975
          for (Node node : nodes) {
976
              if ( node.getType().equals(NodeType.CN) ) {
977
                  
978
                  List<Subject> subjects = node.getSubjectList();
979
                  for (Subject subject : subjects) {
980
                     if (subject.getValue().equals(session.getSubject().getValue())) {
981
                         isAllowed = true;
982
                         break;
983
                     }
984
                  }
985
              }
986
          }
987

    
988
          // proceed if we're called by a CN
989
          if ( isAllowed ) {
990
              // create the coordinating node version of the document      
991
              HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
992
              sysmeta.setSerialVersion(BigInteger.ONE);
993
              sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
994
              pid = super.create(session, pid, object, sysmeta);
995
              HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
996

    
997
          } else {
998
              String msg = "The subject listed as " + session.getSubject().getValue() + 
999
                  " isn't allowed to call create() on a Coordinating Node.";
1000
              logMetacat.info(msg);
1001
              throw new NotAuthorized("1100", msg);
1002
          }
1003
          
1004
      } catch (Exception e) {
1005
          // Convert Hazelcast runtime exceptions to service failures
1006
          String msg = "There was a problem creating the object identified by " +
1007
              pid.getValue() + ". There error message was: " + e.getMessage();
1008
          
1009
      } finally {
1010
          HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
1011
      
1012
      }
1013
      
1014
      return pid;
1015

    
1016
  }
1017

    
1018
  /**
1019
   * Set access for a given object using the object identifier and a Subject
1020
   * under a given Session.
1021
   * 
1022
   * @param session - the Session object containing the credentials for the Subject
1023
   * @param pid - the object identifier for the given object to apply the policy
1024
   * @param policy - the access policy to be applied
1025
   * 
1026
   * @return true if the application of the policy succeeds
1027
   * @throws InvalidToken
1028
   * @throws ServiceFailure
1029
   * @throws NotFound
1030
   * @throws NotAuthorized
1031
   * @throws NotImplemented
1032
   * @throws InvalidRequest
1033
   */
1034
  public boolean setAccessPolicy(Session session, Identifier pid, 
1035
      AccessPolicy accessPolicy, long serialVersion) 
1036
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, 
1037
      NotImplemented, InvalidRequest {
1038
      
1039
      boolean success = false;
1040
      
1041
      // get the subject
1042
      Subject subject = session.getSubject();
1043
      
1044
      // are we allowed to do this?
1045
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
1046
          throw new NotAuthorized("4420", "not allowed by " + subject.getValue() + 
1047
          " on " + pid.getValue());  
1048
      }
1049
      
1050
      SystemMetadata systemMetadata = null;
1051
      try {
1052
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1053
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1054

    
1055
          // does the request have the most current system metadata?
1056
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
1057
             String msg = "The requested system metadata version number " + 
1058
                 serialVersion + "differs from the current version at " +
1059
                 systemMetadata.getSerialVersion().longValue() +
1060
                 " Please get the latest copy in order to modify it.";
1061
             throw new InvalidRequest("4402", msg);
1062
          }
1063
          
1064
      } catch (Exception e) {
1065
          // convert Hazelcast RuntimeException to NotFound
1066
          throw new NotFound("4400", "No record found for: " + pid);
1067
        
1068
      }
1069
          
1070
      // set the access policy
1071
      systemMetadata.setAccessPolicy(accessPolicy);
1072
      
1073
      // update the system metadata
1074
      try {
1075
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1076
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1077
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1078
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1079
        
1080
      } catch (Exception e) {
1081
          // convert Hazelcast RuntimeException to ServiceFailure
1082
          throw new ServiceFailure("4430", e.getMessage());
1083
        
1084
      } finally {
1085
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1086
        
1087
      }
1088
    
1089
    // TODO: how do we know if the map was persisted?
1090
    success = true;
1091
    
1092
    return success;
1093
  }
1094

    
1095
  /**
1096
   * Full replacement of replication metadata in the system metadata for the 
1097
   * specified object, changes date system metadata modified
1098
   * 
1099
   * @param session - the Session object containing the credentials for the Subject
1100
   * @param pid - the object identifier for the given object to apply the policy
1101
   * @param replica - the replica to be updated
1102
   * @return
1103
   * @throws NotImplemented
1104
   * @throws NotAuthorized
1105
   * @throws ServiceFailure
1106
   * @throws InvalidRequest
1107
   * @throws NotFound
1108
   */
1109
  public boolean updateReplicationMetadata(Session session, Identifier pid,
1110
      Replica replica, long serialVersion) 
1111
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest,
1112
      NotFound {
1113
      
1114
      // get the subject
1115
      Subject subject = session.getSubject();
1116
      
1117
      // are we allowed to do this?
1118
      try {
1119
        // what is the controlling permission?
1120
        if (!isAuthorized(session, pid, Permission.WRITE)) {
1121
            throw new NotAuthorized("4851", "not allowed by " + subject.getValue() + 
1122
            " on " + pid.getValue());  
1123
        }
1124
        
1125
      } catch (InvalidToken e) {
1126
          throw new NotAuthorized("4851", "not allowed by " + subject.getValue() + 
1127
                  " on " + pid.getValue());  
1128
          
1129
      }
1130

    
1131
      SystemMetadata systemMetadata = null;
1132
      try {      
1133
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1134
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1135

    
1136
          // does the request have the most current system metadata?
1137
          if ( systemMetadata.getSerialVersion().longValue() != serialVersion ) {
1138
             String msg = "The requested system metadata version number " + 
1139
                 serialVersion + "differs from the current version at " +
1140
                 systemMetadata.getSerialVersion().longValue() +
1141
                 " Please get the latest copy in order to modify it.";
1142
             throw new InvalidRequest("4853", msg);
1143
          }
1144
          
1145
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
1146
        throw new NotFound("4854", "No record found for: " + pid.getValue() +
1147
            " : " + e.getMessage());
1148
        
1149
      }
1150
          
1151
      // set the status for the replica
1152
      List<Replica> replicas = systemMetadata.getReplicaList();
1153
      NodeReference replicaNode = replica.getReplicaMemberNode();
1154
      int index = 0;
1155
      for (Replica listedReplica: replicas) {
1156
          
1157
          // remove the replica that we are replacing
1158
          if ( replicaNode.getValue().equals(listedReplica.getReplicaMemberNode().getValue())) {
1159
              replicas.remove(index);
1160
              break;
1161
              
1162
          }
1163
          index++;
1164
      }
1165
      
1166
      // add the new replica item
1167
      replicas.add(replica);
1168
      systemMetadata.setReplicaList(replicas);
1169
      
1170
      // update the metadata
1171
      try {
1172
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1173
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1174
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1175
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1176
        
1177
      } catch (Exception e) {
1178
          throw new ServiceFailure("4852", e.getMessage());
1179
      
1180
      } finally {
1181
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1182
          
1183
      }
1184
    
1185
      return true;
1186
      
1187
  }
1188
}
(1-1/4)