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) 
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
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
148
        throw new NotFound("4884", "No record found for: " + pid.getValue());
149
        
150
      }
151
          
152
      // set the new policy
153
      systemMetadata.setReplicationPolicy(policy);
154
      
155
      // update the metadata
156
      try {
157
        systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
158
        systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
159
	      HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
160
	      HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
161
	      
162
      } catch (Exception e) {
163
		      throw new ServiceFailure("4882", e.getMessage());
164
		  
165
	    } finally {
166
	        HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
167
	        
168
	    }
169
    
170
      return true;
171
  }
172

    
173
  /**
174
   * Set the replication status for an object given the object identifier
175
   * 
176
   * @param session - the Session object containing the credentials for the Subject
177
   * @param pid - the object identifier for the given object
178
   * @param status - the replication status to be applied
179
   * 
180
   * @return true or false
181
   * 
182
   * @throws NotImplemented
183
   * @throws NotAuthorized
184
   * @throws ServiceFailure
185
   * @throws InvalidRequest
186
   * @throws InvalidToken
187
   * @throws NotFound
188
   * 
189
   */
190
  @Override
191
  public boolean setReplicationStatus(Session session, Identifier pid,
192
    NodeReference targetNode, ReplicationStatus status) 
193
    throws ServiceFailure, NotImplemented, InvalidToken, NotAuthorized, 
194
    InvalidRequest, NotFound {
195

    
196
    // get the subject
197
    Subject subject = session.getSubject();
198
    
199
    // are we allowed to do this?
200
    if (!isAuthorized(session, pid, Permission.WRITE)) {
201
      throw new NotAuthorized("4720", Permission.WRITE + " not allowed by " + 
202
          subject.getValue() + " on " + pid.getValue());  
203
    }
204
    
205
    SystemMetadata systemMetadata = null;
206
    try {      
207
        HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
208
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
209

    
210
    } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
211
      throw new NotFound("4740", "No record found for: " + pid.getValue() +
212
          " : " + e.getMessage());
213
      
214
    }
215
        
216
    // set the status for the replica
217
    List<Replica> replicas = systemMetadata.getReplicaList();
218
    for (Replica replica: replicas) {
219
        if (replica.getReplicaMemberNode().getValue().equals(targetNode.getValue())) {
220
            replica.setReplicationStatus(status);
221
            
222
        }
223
    }
224
    
225
    // [re]set the list -- redundant?
226
    systemMetadata.setReplicaList(replicas);
227
    
228
    // update the metadata
229
    try {
230
        systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
231
        systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
232
	      HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
233
	      HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
234
	    
235
    } catch (Exception e) {
236
		    throw new ServiceFailure("4700", e.getMessage());
237
		
238
	  } finally {
239
	      HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
240
	      
241
	  }
242
	
243
    return true;
244
  }
245

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

    
321
    }
322
        
323
    return asserted;
324
  }
325
  
326
  /**
327
   * Return the checksum of the object given the identifier 
328
   * 
329
   * @param session - the Session object containing the credentials for the Subject
330
   * @param pid - the object identifier for the given object
331
   * 
332
   * @return checksum - the checksum of the object
333
   * 
334
   * @throws InvalidToken
335
   * @throws ServiceFailure
336
   * @throws NotAuthorized
337
   * @throws NotFound
338
   * @throws InvalidRequest
339
   * @throws NotImplemented
340
   */
341
  @Override
342
  public Checksum getChecksum(Session session, Identifier pid)
343
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
344
    InvalidRequest, NotImplemented {
345
        
346
    if (!isAuthorized(session, pid, Permission.READ)) {
347
        throw new NotAuthorized("1400", Permission.READ + " not allowed on " + pid.getValue());  
348
    }
349
    
350
    SystemMetadata systemMetadata = null;
351
    Checksum checksum = null;
352
    
353
    try {
354
        HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
355
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
356
        checksum = systemMetadata.getChecksum();
357
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
358

    
359
    } catch (Exception e) {
360
        throw new ServiceFailure("1410", "An error occurred getting the checksum for " + 
361
            pid.getValue() + ". The error message was: " + e.getMessage());
362
      
363
    } finally {
364
        HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
365
        
366
    }
367
    
368
    return checksum;
369
  }
370

    
371
  /**
372
   * Resolve the location of a given object
373
   * 
374
   * @param session - the Session object containing the credentials for the Subject
375
   * @param pid - the object identifier for the given object
376
   * 
377
   * @return objectLocationList - the list of nodes known to contain the object
378
   * 
379
   * @throws InvalidRequest
380
   * @throws InvalidToken
381
   * @throws ServiceFailure
382
   * @throws NotAuthorized
383
   * @throws NotFound
384
   * @throws NotImplemented
385
   */
386
  @Override
387
  public ObjectLocationList resolve(Session session, Identifier pid)
388
    throws InvalidRequest, InvalidToken, ServiceFailure, NotAuthorized,
389
    NotFound, NotImplemented {
390

    
391
    throw new NotImplemented("4131", "resolve not implemented");
392

    
393
  }
394

    
395
  /**
396
   * Search the metadata catalog for identifiers that match the criteria
397
   * 
398
   * @param session - the Session object containing the credentials for the Subject
399
   * @param queryType - An identifier for the type of query expression 
400
   *                    provided in the query
401
   * @param query -  The criteria for matching the characteristics of the 
402
   *                 metadata objects of interest
403
   * 
404
   * @return objectList - the list of objects matching the criteria
405
   * 
406
   * @throws InvalidToken
407
   * @throws ServiceFailure
408
   * @throws NotAuthorized
409
   * @throws InvalidRequest
410
   * @throws NotImplemented
411
   */
412
  @Override
413
  public ObjectList search(Session session, String queryType, String query)
414
    throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
415
    NotImplemented {
416

    
417
    ObjectList objectList = null;
418
    try {
419
        objectList = 
420
          IdentifierManager.getInstance().querySystemMetadata(
421
              null, //startTime, 
422
              null, //endTime,
423
              null, //objectFormat, 
424
              false, //replicaStatus, 
425
              0, //start, 
426
              -1 //count
427
              );
428
        
429
    } catch (Exception e) {
430
      throw new ServiceFailure("4310", "Error querying system metadata: " + e.getMessage());
431
    }
432

    
433
      return objectList;
434
      
435
    //throw new NotImplemented("4281", "search not implemented");
436
    
437
    // the code block below is from an older implementation
438
    
439
    /*  This block commented out because of the EcoGrid circular dependency.
440
         *  For now, query will not be supported until the circularity can be
441
         *  resolved, probably by moving the ecogrid query syntax transformers
442
         *  directly into the Metacat codebase.  MBJ 2010-02-03
443
         
444
        try {
445
            EcogridQueryParser parser = new EcogridQueryParser(request
446
                    .getReader());
447
            parser.parseXML();
448
            QueryType queryType = parser.getEcogridQuery();
449
            EcogridJavaToMetacatJavaQueryTransformer queryTransformer = 
450
                new EcogridJavaToMetacatJavaQueryTransformer();
451
            QuerySpecification metacatQuery = queryTransformer
452
                    .transform(queryType);
453

    
454
            DBQuery metacat = new DBQuery();
455

    
456
            boolean useXMLIndex = (new Boolean(PropertyService
457
                    .getProperty("database.usexmlindex"))).booleanValue();
458
            String xmlquery = "query"; // we don't care the query in resultset,
459
            // the query can be anything
460
            PrintWriter out = null; // we don't want metacat result, so set out null
461

    
462
            // parameter: queryspecification, user, group, usingIndexOrNot
463
            StringBuffer result = metacat.createResultDocument(xmlquery,
464
                    metacatQuery, out, username, groupNames, useXMLIndex);
465

    
466
            // create result set transfer       
467
            String saxparser = PropertyService.getProperty("xml.saxparser");
468
            MetacatResultsetParser metacatResultsetParser = new MetacatResultsetParser(
469
                    new StringReader(result.toString()), saxparser, queryType
470
                            .getNamespace().get_value());
471
            ResultsetType records = metacatResultsetParser.getEcogridResult();
472

    
473
            System.out
474
                    .println(EcogridResultsetTransformer.toXMLString(records));
475
            response.setContentType("text/xml");
476
            out = response.getWriter();
477
            out.print(EcogridResultsetTransformer.toXMLString(records));
478

    
479
        } catch (Exception e) {
480
            e.printStackTrace();
481
        }*/
482
    
483

    
484
  }
485
  
486
  /**
487
   * Returns the object format registered in the DataONE Object Format 
488
   * Vocabulary for the given format identifier
489
   * 
490
   * @param fmtid - the identifier of the format requested
491
   * 
492
   * @return objectFormat - the object format requested
493
   * 
494
   * @throws InvalidRequest
495
   * @throws ServiceFailure
496
   * @throws NotFound
497
   * @throws InsufficientResources
498
   * @throws NotImplemented
499
   */
500
  @Override
501
  public ObjectFormat getFormat(ObjectFormatIdentifier fmtid)
502
    throws InvalidRequest, ServiceFailure, NotFound, InsufficientResources,
503
    NotImplemented {
504
     
505
      return ObjectFormatService.getInstance().getFormat(fmtid);
506
      
507
  }
508

    
509
  /**
510
   * Returns a list of all object formats registered in the DataONE Object 
511
   * Format Vocabulary
512
    * 
513
   * @return objectFormatList - The list of object formats registered in 
514
   *                            the DataONE Object Format Vocabulary
515
   * 
516
   * @throws InvalidRequest
517
   * @throws ServiceFailure
518
   * @throws NotImplemented
519
   * @throws NotFound
520
   * @throws InsufficientResources
521
   */
522
  @Override
523
  public ObjectFormatList listFormats() 
524
    throws InvalidRequest, ServiceFailure, NotFound, InsufficientResources, 
525
    NotImplemented {
526

    
527
    return ObjectFormatService.getInstance().listFormats();
528
  }
529

    
530
  /**
531
   * Returns a list of nodes that have been registered with the DataONE infrastructure
532
    * 
533
   * @return nodeList - List of nodes from the registry
534
   * 
535
   * @throws ServiceFailure
536
   * @throws NotImplemented
537
   */
538
  @Override
539
  public NodeList listNodes() 
540
    throws NotImplemented, ServiceFailure {
541

    
542
    throw new NotImplemented("4800", "listNodes not implemented");
543
  }
544

    
545
  /**
546
   * Provides a mechanism for adding system metadata independently of its 
547
   * associated object, such as when adding system metadata for data objects.
548
    * 
549
   * @param session - the Session object containing the credentials for the Subject
550
   * @param pid - The identifier of the object to register the system metadata against
551
   * @param sysmeta - The system metadata to be registered
552
   * 
553
   * @return true if the registration succeeds
554
   * 
555
   * @throws NotImplemented
556
   * @throws NotAuthorized
557
   * @throws ServiceFailure
558
   * @throws InvalidRequest
559
   * @throws InvalidSystemMetadata
560
   */
561
  @Override
562
  public Identifier registerSystemMetadata(Session session, Identifier pid,
563
      SystemMetadata sysmeta) 
564
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest, 
565
      InvalidSystemMetadata {
566

    
567
      // TODO: control who can call this?
568
      if (session == null) {
569
          //TODO: many of the thrown exceptions do not use the correct error codes
570
          //check these against the docs and correct them
571
          throw new NotAuthorized("4861", "No Session - could not authorize for registration." +
572
                  "  If you are not logged in, please do so and retry the request.");
573
      }
574
      
575
      // verify that guid == SystemMetadata.getIdentifier()
576
      logMetacat.debug("Comparing guid|sysmeta_guid: " + pid.getValue() + 
577
          "|" + sysmeta.getIdentifier().getValue());
578
      if (!pid.getValue().equals(sysmeta.getIdentifier().getValue())) {
579
          throw new InvalidRequest("4863", 
580
              "The identifier in method call (" + pid.getValue() + 
581
              ") does not match identifier in system metadata (" +
582
              sysmeta.getIdentifier().getValue() + ").");
583
      }
584

    
585
      logMetacat.debug("Checking if identifier exists...");
586
      // Check that the identifier does not already exist
587
      if (HazelcastService.getInstance().getSystemMetadataMap().containsKey(pid)) {
588
          throw new InvalidRequest("4863", 
589
              "The identifier is already in use by an existing object.");
590
      
591
      }
592
      
593
      // insert the system metadata into the object store
594
      logMetacat.debug("Starting to insert SystemMetadata...");
595
      try {
596
          HazelcastService.getInstance().getSystemMetadataMap().lock(sysmeta.getIdentifier());
597
          sysmeta.setSerialVersion(BigInteger.ONE);
598
          sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
599
          HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
600
          HazelcastService.getInstance().getSystemMetadataMap().unlock(sysmeta.getIdentifier());
601
          
602
      } catch (Exception e) {
603
      	logMetacat.error("Problem registering system metadata: " + pid.getValue(), e);
604
          throw new ServiceFailure("4862", "Error inserting system metadata: " + 
605
              e.getClass() + ": " + e.getMessage());
606
          
607
      } finally {
608
        HazelcastService.getInstance().getSystemMetadataMap().unlock(sysmeta.getIdentifier());
609

    
610
      }
611
      
612
      logMetacat.debug("Returning from registerSystemMetadata");
613
      EventLog.getInstance().log(request.getRemoteAddr(), 
614
          request.getHeader("User-Agent"), session.getSubject().getValue(), 
615
          pid.getValue(), "registerSystemMetadata");
616
      return pid;
617
  }
618

    
619
  /**
620
   * Provides a mechanism for updating system metadata independently of its 
621
   * associated object
622
    * 
623
   * @param session - the Session object containing the credentials for the Subject
624
   * @param pid - The identifier of the system metadata
625
   * @param sysmeta - The system metadata to be registered
626
   * 
627
   * @return true if the update succeeds
628
   * 
629
   * @throws NotImplemented
630
   * @throws NotAuthorized
631
   * @throws ServiceFailure
632
   * @throws InvalidRequest
633
   * @throws InvalidSystemMetadata
634
   * @throws NotFound
635
   */
636
  @Override
637
  public boolean updateSystemMetadata(Session session, Identifier guid,
638
      SystemMetadata sysmeta) 
639
      throws NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest, 
640
      InvalidSystemMetadata, NotFound {
641

    
642
      // TODO: control who can call this?
643
      if (session == null) {
644
          //TODO: many of the thrown exceptions do not use the correct error codes
645
          //check these against the docs and correct them
646
          throw new NotAuthorized("4861", "No Session - could not authorize for update." +
647
                  "  If you are not logged in, please do so and retry the request.");
648
      }
649
      
650
      // verify that guid == SystemMetadata.getIdentifier()
651
      logMetacat.debug("Comparing guid|sysmeta_guid: " + guid.getValue() + "|" + 
652
          sysmeta.getIdentifier().getValue());
653
      if (!guid.getValue().equals(sysmeta.getIdentifier().getValue())) {
654
          throw new InvalidRequest("4863", 
655
              "GUID in method call (" + guid.getValue() + 
656
              ") does not match GUID in system metadata (" +
657
              sysmeta.getIdentifier().getValue() + ").");
658
      }
659

    
660
      logMetacat.debug("Checking if identifier exists...");
661
      // Check that the identifier exists
662
      if (!IdentifierManager.getInstance().identifierExists(guid.getValue())) {
663
          throw new NotFound("000", 
664
              "GUID does not exist");
665
      }
666

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

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

    
768
  @Override
769
  public boolean hasReservation(Session session, Identifier pid) 
770
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, IdentifierNotUnique, 
771
      NotImplemented, InvalidRequest {
772
  
773
      throw new NotImplemented("4191", "hasReservation not implemented on this node");
774
  }
775

    
776
  /**
777
   * Changes ownership (RightsHolder) of the specified object to the 
778
   * subject specified by userId
779
    * 
780
   * @param session - the Session object containing the credentials for the Subject
781
   * @param pid - Identifier of the object to be modified
782
   * @param userId - The subject that will be taking ownership of the specified object.
783
   *
784
   * @return pid - the identifier of the modified object
785
   * 
786
   * @throws ServiceFailure
787
   * @throws InvalidToken
788
   * @throws NotFound
789
   * @throws NotAuthorized
790
   * @throws NotImplemented
791
   * @throws InvalidRequest
792
   */  
793
  @Override
794
  public Identifier setOwner(Session session, Identifier pid, Subject userId)
795
    throws InvalidToken, ServiceFailure, NotFound, NotAuthorized,
796
    NotImplemented, InvalidRequest {
797
    
798
    // get the subject
799
    Subject subject = session.getSubject();
800
    
801
    // are we allowed to do this?
802
    if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
803
      throw new NotAuthorized("4440", "not allowed by " + subject.getValue() + " on " + pid.getValue());  
804
    }
805
    
806
    SystemMetadata systemMetadata = null;
807
    try {
808
        HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
809
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
810
        
811
    } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
812
        throw new NotFound("4460", "No record found for: " + pid.getValue());
813
        
814
    }
815
        
816
    // set the new rights holder
817
    systemMetadata.setRightsHolder(userId);
818
    
819
    // update the metadata
820
    try {
821
        systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
822
        systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
823
	      HazelcastService.getInstance().getSystemMetadataMap().put(pid, systemMetadata);
824
	      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
825
	      
826
    } catch (Exception e) {
827
		throw new ServiceFailure("4490", e.getMessage());
828
		
829
	  } finally {
830
	      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
831
	  }
832
    
833
    return pid;
834
  }
835

    
836
  /**
837
   * Verify that a replication task is authorized by comparing the target node's
838
   * Subject (from the X.509 certificate-derived Session) with the list of 
839
   * subjects in the known, pending replication tasks map.
840
   * 
841
   * @param originatingNodeSession - Session information that contains the 
842
   *                                 identity of the calling user
843
   * @param targetNodeSubject - Subject identifying the target node
844
   * @param pid - the identifier of the object to be replicated
845
   * @param replicatePermission - the execute permission to be granted
846
   * 
847
   * @throws ServiceFailure
848
   * @throws NotImplemented
849
   * @throws InvalidToken
850
   * @throws NotAuthorized
851
   * @throws InvalidRequest
852
   * @throws NotFound
853
   */
854
  @Override
855
  public boolean isNodeAuthorized(Session originatingNodeSession, 
856
    Subject targetNodeSubject, Identifier pid, Permission replicatePermission) 
857
    throws NotImplemented, NotAuthorized, InvalidToken, ServiceFailure, 
858
    NotFound, InvalidRequest {
859

    
860
	  boolean isAllowed = false;
861
	  SystemMetadata sysmeta = null;
862
    NodeReference targetNode = null;
863
    
864
	  try {
865
	    // get the target node reference from the nodes list
866
	    CNode cn = D1Client.getCN();
867
	    List<Node> nodes = (List<Node>) cn.listNodes();
868

    
869
	    for ( Node node : nodes ) {
870
	        Subject nodeSubject = node.getSubject(0);
871
	        if (nodeSubject.getValue().equals(targetNodeSubject)) {
872
	            targetNode = node.getIdentifier();
873
	            
874
	        }
875
	    }
876
	    
877

    
878
	    //lock, get, and unlock the pid
879
	    HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
880
	    sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
881
	    List<Replica> replicaList = sysmeta.getReplicaList();
882
	    
883
      // find the replica with the status set to 'requested'
884
      for (Replica replica : replicaList) {
885
          ReplicationStatus status = replica.getReplicationStatus();
886
          NodeReference listedNode = replica.getReplicaMemberNode();
887
          if (listedNode.equals(targetNode)
888
                  && status.equals(ReplicationStatus.REQUESTED)) {
889
              isAllowed = true;
890
              break;
891

    
892
          }
893
      }
894
      
895
      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
896

    
897
	  } catch(Exception e) {
898
	      // Catch Hazelcast RuntimeExceptions
899
	      throw new ServiceFailure("4872", 
900
	          "Couldn't determine if node is allowed: " + e.getMessage());
901
	    
902
	  } finally {
903
	    // always unlock the pid
904
      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
905

    
906
	  }
907
	    
908
	  return isAllowed;
909
    
910
  }
911

    
912
  /**
913
   * Adds a new object to the Node, where the object is a science metadata object.
914
   * 
915
   * @param session - the Session object containing the credentials for the Subject
916
   * @param pid - The object identifier to be created
917
   * @param object - the object bytes
918
   * @param sysmeta - the system metadata that describes the object  
919
   * 
920
   * @return pid - the object identifier created
921
   * 
922
   * @throws InvalidToken
923
   * @throws ServiceFailure
924
   * @throws NotAuthorized
925
   * @throws IdentifierNotUnique
926
   * @throws UnsupportedType
927
   * @throws InsufficientResources
928
   * @throws InvalidSystemMetadata
929
   * @throws NotImplemented
930
   * @throws InvalidRequest
931
   */
932
  public Identifier create(Session session, Identifier pid, InputStream object,
933
    SystemMetadata sysmeta) 
934
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
935
    UnsupportedType, InsufficientResources, InvalidSystemMetadata, 
936
    NotImplemented, InvalidRequest {
937
      
938
      
939
      try {
940
        // are we allowed?
941
          boolean isAllowed = false;
942
          CNode cn = D1Client.getCN();
943
          List<Node> nodes = (List<Node>) cn.listNodes();
944
          
945
          for (Node node : nodes) {
946
              if ( node.getType().equals(NodeType.CN) ) {
947
                  
948
                  List<Subject> subjects = node.getSubjectList();
949
                  for (Subject subject : subjects) {
950
                     if (subject.getValue().equals(session.getSubject().getValue())) {
951
                         isAllowed = true;
952
                         break;
953
                     }
954
                  }
955
              }
956
          }
957

    
958
          // proceed if we're called by a CN
959
          if ( isAllowed ) {
960
              // create the coordinating node version of the document      
961
              HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
962
              sysmeta.setSerialVersion(BigInteger.ONE);
963
              sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
964
              pid = super.create(session, pid, object, sysmeta);
965
              HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
966

    
967
          } else {
968
              String msg = "The subject listed as " + session.getSubject().getValue() + 
969
                  " isn't allowed to call create() on a Coordinating Node.";
970
              logMetacat.info(msg);
971
              throw new NotAuthorized("1100", msg);
972
          }
973
          
974
      } catch (Exception e) {
975
          // Convert Hazelcast runtime exceptions to service failures
976
          String msg = "There was a problem creating the object identified by " +
977
              pid.getValue() + ". There error message was: " + e.getMessage();
978
          
979
      } finally {
980
          HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
981
      
982
      }
983
      
984
      return pid;
985

    
986
  }
987

    
988
  /**
989
   * Set access for a given object using the object identifier and a Subject
990
   * under a given Session.
991
   * 
992
   * @param session - the Session object containing the credentials for the Subject
993
   * @param pid - the object identifier for the given object to apply the policy
994
   * @param policy - the access policy to be applied
995
   * 
996
   * @return true if the application of the policy succeeds
997
   * @throws InvalidToken
998
   * @throws ServiceFailure
999
   * @throws NotFound
1000
   * @throws NotAuthorized
1001
   * @throws NotImplemented
1002
   * @throws InvalidRequest
1003
   */
1004
  public boolean setAccessPolicy(Session session, Identifier pid, 
1005
      AccessPolicy accessPolicy) 
1006
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, 
1007
      NotImplemented, InvalidRequest {
1008
      
1009
      boolean success = false;
1010
      
1011
      // get the subject
1012
      Subject subject = session.getSubject();
1013
      
1014
      // are we allowed to do this?
1015
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
1016
          throw new NotAuthorized("4420", "not allowed by " + subject.getValue() + 
1017
          " on " + pid.getValue());  
1018
      }
1019
      
1020
      SystemMetadata systemMetadata = null;
1021
      
1022
      try {
1023
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1024
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1025

    
1026
      } catch (Exception e) {
1027
          // convert Hazelcast RuntimeException to NotFound
1028
          throw new NotFound("4400", "No record found for: " + pid);
1029
        
1030
      }
1031
          
1032
      // set the access policy
1033
      systemMetadata.setAccessPolicy(accessPolicy);
1034
      
1035
      // update the system metadata
1036
      try {
1037
          // TODO: consult authoritative MN on current sysmeta serial version?
1038
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1039
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1040
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1041
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1042
        
1043
      } catch (Exception e) {
1044
          // convert Hazelcast RuntimeException to ServiceFailure
1045
          throw new ServiceFailure("4430", e.getMessage());
1046
        
1047
      } finally {
1048
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1049
        
1050
      }
1051
    
1052
    // TODO: how do we know if the map was persisted?
1053
    success = true;
1054
    
1055
    return success;
1056
  }
1057

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

    
1094
      SystemMetadata systemMetadata = null;
1095
      try {      
1096
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1097
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1098

    
1099
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
1100
        throw new NotFound("4854", "No record found for: " + pid.getValue() +
1101
            " : " + e.getMessage());
1102
        
1103
      }
1104
          
1105
      // set the status for the replica
1106
      List<Replica> replicas = systemMetadata.getReplicaList();
1107
      NodeReference replicaNode = replica.getReplicaMemberNode();
1108
      int index = 0;
1109
      for (Replica listedReplica: replicas) {
1110
          
1111
          // remove the replica that we are replacing
1112
          if ( replicaNode.getValue().equals(listedReplica.getReplicaMemberNode().getValue())) {
1113
              replicas.remove(index);
1114
              break;
1115
              
1116
          }
1117
          index++;
1118
      }
1119
      
1120
      // add the new replica item
1121
      replicas.add(replica);
1122
      systemMetadata.setReplicaList(replicas);
1123
      
1124
      // update the metadata
1125
      try {
1126
          systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
1127
          systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
1128
          HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
1129
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1130
        
1131
      } catch (Exception e) {
1132
          throw new ServiceFailure("4852", e.getMessage());
1133
      
1134
      } finally {
1135
          HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
1136
          
1137
      }
1138
    
1139
      return true;
1140
      
1141
  }
1142
}
(1-1/4)