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.util.Date;
27
import java.util.List;
28
import java.util.Set;
29

    
30
import org.apache.log4j.Logger;
31
import org.dataone.configuration.Settings;
32
import org.dataone.service.cn.v1.CNAuthorization;
33
import org.dataone.service.cn.v1.CNCore;
34
import org.dataone.service.cn.v1.CNRead;
35
import org.dataone.service.cn.v1.CNReplication;
36
import org.dataone.service.exceptions.IdentifierNotUnique;
37
import org.dataone.service.exceptions.InsufficientResources;
38
import org.dataone.service.exceptions.InvalidRequest;
39
import org.dataone.service.exceptions.InvalidSystemMetadata;
40
import org.dataone.service.exceptions.InvalidToken;
41
import org.dataone.service.exceptions.NotAuthorized;
42
import org.dataone.service.exceptions.NotFound;
43
import org.dataone.service.exceptions.NotImplemented;
44
import org.dataone.service.exceptions.ServiceFailure;
45
import org.dataone.service.types.v1.Checksum;
46
import org.dataone.service.types.v1.Identifier;
47
import org.dataone.service.types.v1.Node;
48
import org.dataone.service.types.v1.NodeList;
49
import org.dataone.service.types.v1.NodeReference;
50
import org.dataone.service.types.v1.ObjectFormat;
51
import org.dataone.service.types.v1.ObjectFormatIdentifier;
52
import org.dataone.service.types.v1.ObjectFormatList;
53
import org.dataone.service.types.v1.ObjectList;
54
import org.dataone.service.types.v1.ObjectLocationList;
55
import org.dataone.service.types.v1.Permission;
56
import org.dataone.service.types.v1.Replica;
57
import org.dataone.service.types.v1.ReplicationPolicy;
58
import org.dataone.service.types.v1.ReplicationStatus;
59
import org.dataone.service.types.v1.Session;
60
import org.dataone.service.types.v1.Subject;
61
import org.dataone.service.types.v1.SystemMetadata;
62

    
63
import com.hazelcast.client.HazelcastClient;
64
import com.hazelcast.core.EntryEvent;
65
import com.hazelcast.core.EntryListener;
66
import com.hazelcast.core.Hazelcast;
67
import com.hazelcast.core.IMap;
68
import com.hazelcast.core.IQueue;
69
import com.hazelcast.query.SqlPredicate;
70

    
71
import edu.ucsb.nceas.metacat.EventLog;
72
import edu.ucsb.nceas.metacat.IdentifierManager;
73
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
74
import edu.ucsb.nceas.metacat.properties.PropertyService;
75
import edu.ucsb.nceas.metacat.replication.ForceReplicationSystemMetadataHandler;
76
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
77

    
78
/**
79
 * Represents Metacat's implementation of the DataONE Coordinating Node 
80
 * service API. Methods implement the various CN* interfaces, and methods common
81
 * to both Member Node and Coordinating Node interfaces are found in the
82
 * D1NodeService super class.
83
 *
84
 */
85
public class CNodeService extends D1NodeService implements CNAuthorization,
86
    CNCore, CNRead, CNReplication, EntryListener<Identifier, SystemMetadata> {
87

    
88
  /* the instance of the CNodeService object */
89
  private static CNodeService instance = null;
90
  
91
  /* The instance of the Hazelcast client */
92
  private HazelcastClient hzClient;
93

    
94
  /* The name of the DataONE Hazelcast cluster group */
95
  private String groupName;
96

    
97
  /* The name of the DataONE Hazelcast cluster password */
98
  private String groupPassword;
99
  
100
  /* The name of the DataONE Hazelcast cluster IP addresses */
101
  private String addressList;
102
  
103
  /* The name of the node map */
104
  private String nodeMap;
105

    
106
  /* The name of the system metadata map */
107
  private String systemMetadataMap;
108
  
109
  /* The Hazelcast distributed task id generator namespace */
110
  private String taskIds;
111
  
112
  /* The name of the pending replication tasks map */
113
  private String pendingTasksQueue;
114
  
115
  /* The Hazelcast distributed system metadata map */
116
  private IMap<NodeReference, Node> nodes;
117

    
118
  /* The Hazelcast distributed system metadata map */
119
  private IMap<Identifier, SystemMetadata> systemMetadata;
120
  
121
  /* The Hazelcast distributed pending replication tasks map*/
122
  private IMap<String, CNReplicationTask> pendingReplicationTasks;
123

    
124
  /* the logger instance */
125
  private Logger logMetacat = null;
126

    
127
  /**
128
   * singleton accessor
129
   */
130
  public static CNodeService getInstance() { 
131
    if (instance == null) {
132
    
133
      instance = new CNodeService();
134
      
135
    }
136
  
137
    return instance;
138
  }
139
  
140
  /**
141
   * Constructor, private for singleton access
142
   */
143
  private CNodeService() {
144
    super();
145
    logMetacat = Logger.getLogger(CNodeService.class);
146
    
147
    // Get configuration properties on instantiation
148
    try {
149
      groupName = 
150
        PropertyService.getProperty("dataone.hazelcast.processCluster.groupName");
151
      groupPassword = 
152
        PropertyService.getProperty("dataone.hazelcast.processCluster.password");
153
      addressList = 
154
        PropertyService.getProperty("dataone.hazelcast.processCluster.instances");
155
      nodeMap = 
156
        PropertyService.getProperty("dataone.hazelcast.processCluster.nodesMap");
157
      systemMetadataMap = 
158
        PropertyService.getProperty("dataone.hazelcast.storageCluster.systemMetadataMap");
159
      pendingTasksQueue = 
160
    	PropertyService.getProperty("dataone.hazelcast.replicationPendingTasks");
161
      
162
      // Become a DataONE-process cluster client
163
      //TODO: where should this be?
164
//      String[] addresses = addressList.split(",");
165
//      hzClient = 
166
//        HazelcastClient.newHazelcastClient(this.groupName, this.groupPassword, addresses);
167
//      nodes = hzClient.getMap(nodeMap);
168
//      pendingReplicationTasks = hzClient.getMap(pendingTasksQueue);
169
      
170
      // Get a reference to the shared system metadata map as a cluster member
171
      systemMetadata = Hazelcast.getMap(systemMetadataMap);
172
      
173
      // Listen for changes to the system metadata map
174
      systemMetadata.addEntryListener(this, true);
175
      
176
    } catch (PropertyNotFoundException e) {
177

    
178
      String msg = "Couldn't find Hazelcast properties for the DataONE clusters. " +
179
        "The error message was: " + e.getMessage();
180
      logMetacat.error(msg);
181
      
182
    }
183

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

    
207
    // get the subject
208
    Subject subject = session.getSubject();
209
    // get the system metadata
210
    String guid = pid.getValue();
211
    
212
    // are we allowed to do this?
213
    if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
214
      throw new NotAuthorized("4881", Permission.CHANGE_PERMISSION + " not allowed by " + subject.getValue() + " on " + guid);  
215
    }
216
    
217
    SystemMetadata systemMetadata = null;
218
    try {
219
      systemMetadata = IdentifierManager.getInstance().getSystemMetadata(guid);
220
    } catch (McdbDocNotFoundException e) {
221
      throw new NotFound("4884", "No record found for: " + guid);
222
    }
223
        
224
    // set the new policy
225
    systemMetadata.setReplicationPolicy(policy);
226
    
227
    // update the metadata
228
    try {
229
      IdentifierManager.getInstance().updateSystemMetadata(systemMetadata);
230
    } catch (McdbDocNotFoundException e) {
231
      throw new ServiceFailure("4882", e.getMessage());
232
    }
233

    
234
    return true;
235
  }
236

    
237
  /**
238
   * Set the replication status for an object given the object identifier
239
   * 
240
   * @param session - the Session object containing the credentials for the Subject
241
   * @param pid - the object identifier for the given object
242
   * @param status - the replication status to be applied
243
   * 
244
   * @return true or false
245
   * 
246
   * @throws NotImplemented
247
   * @throws NotAuthorized
248
   * @throws ServiceFailure
249
   * @throws InvalidRequest
250
   * @throws InvalidToken
251
   * @throws NotFound
252
   * 
253
   */
254
  @Override
255
  public boolean setReplicationStatus(Session session, Identifier pid,
256
    NodeReference targetNode, ReplicationStatus status) 
257
    throws ServiceFailure, NotImplemented, InvalidToken, NotAuthorized, 
258
    InvalidRequest, NotFound {
259

    
260
    // get the subject
261
    Subject subject = session.getSubject();
262
    // get the system metadata
263
    String guid = pid.getValue();
264
    
265
    // are we allowed to do this?
266
    if (!isAuthorized(session, pid, Permission.WRITE)) {
267
      throw new NotAuthorized("4720", Permission.WRITE + " not allowed by " + subject.getValue() + " on " + guid);  
268
    }
269
    
270
    SystemMetadata systemMetadata = null;
271
    try {
272
      systemMetadata = IdentifierManager.getInstance().getSystemMetadata(guid);
273
    } catch (McdbDocNotFoundException e) {
274
      throw new NotFound("4740", "No record found for: " + guid);
275
    }
276
        
277
    // set the status for each replica
278
    // TODO: should this method select a certain replica?
279
    List<Replica> replicas = systemMetadata.getReplicaList();
280
    for (Replica replica: replicas) {
281
      replica.setReplicationStatus(status);
282
    }
283
    
284
    // [re]set the list -- redundant?
285
    systemMetadata.setReplicaList(replicas);
286
    
287
    // update the metadata
288
    try {
289
      IdentifierManager.getInstance().updateSystemMetadata(systemMetadata);
290
    } catch (McdbDocNotFoundException e) {
291
      throw new ServiceFailure("4700", e.getMessage());
292
    }
293

    
294
    return true;
295
  }
296

    
297
  /**
298
   * Test that the specified relationship between pidOfSubject and pidOfObject exists
299
   * 
300
   * @param session - the Session object containing the credentials for the Subject
301
   * @param node - the node information for the given node be modified
302
   * 
303
   * @return true if the relationship exists
304
   * 
305
   * @throws InvalidToken
306
   * @throws ServiceFailure
307
   * @throws NotAuthorized
308
   * @throws NotFound
309
   * @throws InvalidRequest
310
   * @throws NotImplemented
311
   */
312
  @Override
313
  public boolean assertRelation(Session session, Identifier pidOfSubject, 
314
    String relationship, Identifier pidOfObject) 
315
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
316
    InvalidRequest, NotImplemented {
317
    
318
    
319
    // get the system metadata
320
    String guid1 = pidOfSubject.getValue();
321
    // are we allowed to do this?
322
    if (!isAuthorized(session, pidOfSubject, Permission.READ)) {
323
      throw new NotAuthorized("4881", Permission.READ + " not allowed on " + guid1);  
324
    }
325
    
326
    SystemMetadata systemMetadata = null;
327
    try {
328
      systemMetadata = IdentifierManager.getInstance().getSystemMetadata(guid1);
329
    } catch (McdbDocNotFoundException e) {
330
      throw new NotFound("4884", "No record found for: " + guid1);
331
    }
332
        
333
    // check relationships
334
    // TODO: use ORE map
335
    if (relationship.equalsIgnoreCase("describes")) {
336
      
337
    }
338
    if (relationship.equalsIgnoreCase("describedBy")) {
339
      
340
    }
341
    if (relationship.equalsIgnoreCase("derivedFrom")) {
342
      
343
    }
344
    if (relationship.equalsIgnoreCase("obsoletes")) {
345
      Identifier pid = systemMetadata.getObsoletes();
346
      if (pid.getValue().equals(pidOfObject.getValue())) {
347
        return true;
348
      }
349
      //return systemMetadata.getObsoleteList().contains(pidOfObject);
350
    }
351
    if (relationship.equalsIgnoreCase("obsoletedBy")) {
352
      Identifier pid = systemMetadata.getObsoletedBy();
353
      if (pid.getValue().equals(pidOfObject.getValue())) {
354
        return true;
355
      }
356
      //return systemMetadata.getObsoletedByList().contains(pidOfObject);
357
    }
358

    
359
    return false;
360
  }
361
  
362
  /**
363
   * Return the checksum of the object given the identifier 
364
   * 
365
   * @param session - the Session object containing the credentials for the Subject
366
   * @param pid - the object identifier for the given object
367
   * 
368
   * @return checksum - the checksum of the object
369
   * 
370
   * @throws InvalidToken
371
   * @throws ServiceFailure
372
   * @throws NotAuthorized
373
   * @throws NotFound
374
   * @throws InvalidRequest
375
   * @throws NotImplemented
376
   */
377
  @Override
378
  public Checksum getChecksum(Session session, Identifier pid)
379
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
380
    InvalidRequest, NotImplemented {
381
    
382
    if (!isAuthorized(session, pid, Permission.READ)) {
383
      throw new NotAuthorized("1400", Permission.READ + " not allowed on " + pid.getValue());  
384
    }
385
    SystemMetadata systemMetadata = null;
386
    try {
387
      systemMetadata = IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
388
    } catch (McdbDocNotFoundException e) {
389
      throw new NotFound("1420", "No record found for: " + pid.getValue());
390
    }
391
    Checksum checksum = systemMetadata.getChecksum();
392
    
393
    return checksum;
394
  }
395

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

    
416
    throw new NotImplemented("4131", "resolve not implemented");
417

    
418
  }
419

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

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

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

    
479
            DBQuery metacat = new DBQuery();
480

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

    
487
            // parameter: queryspecification, user, group, usingIndexOrNot
488
            StringBuffer result = metacat.createResultDocument(xmlquery,
489
                    metacatQuery, out, username, groupNames, useXMLIndex);
490

    
491
            // create result set transfer       
492
            String saxparser = PropertyService.getProperty("xml.saxparser");
493
            MetacatResultsetParser metacatResultsetParser = new MetacatResultsetParser(
494
                    new StringReader(result.toString()), saxparser, queryType
495
                            .getNamespace().get_value());
496
            ResultsetType records = metacatResultsetParser.getEcogridResult();
497

    
498
            System.out
499
                    .println(EcogridResultsetTransformer.toXMLString(records));
500
            response.setContentType("text/xml");
501
            out = response.getWriter();
502
            out.print(EcogridResultsetTransformer.toXMLString(records));
503

    
504
        } catch (Exception e) {
505
            e.printStackTrace();
506
        }*/
507
    
508

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

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

    
552
    return ObjectFormatService.getInstance().listFormats();
553
  }
554

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

    
567
    throw new NotImplemented("4800", "listNodes not implemented");
568
  }
569

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

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

    
608
        logMetacat.debug("Checking if identifier exists...");
609
        // Check that the identifier does not already exist
610
        if (IdentifierManager.getInstance().identifierExists(guid.getValue())) {
611
            throw new InvalidRequest("4863", 
612
                "GUID is already in use by an existing object.");
613
      
614
        }
615

    
616
        // insert the system metadata into the object store
617
        logMetacat.debug("Starting to insert SystemMetadata...");
618
        sysmeta.setDateSysMetadataModified(new Date());
619
        try {
620
          IdentifierManager.getInstance().createSystemMetadata(sysmeta);
621
          // force replication of this record
622
          ForceReplicationSystemMetadataHandler forceReplication = 
623
            new ForceReplicationSystemMetadataHandler(guid.getValue(), null);
624
        } catch (Exception e) {
625
            throw new ServiceFailure("4862", "Error inserting system metadata: " + e.getClass() + ": " + e.getMessage());
626
        }
627
        
628
        logMetacat.debug("Returning from registerSystemMetadata");
629
        EventLog.getInstance().log(null, session.getSubject().getValue(), guid.getValue(), "registerSystemMetadata");
630
        return guid;
631
  }
632

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

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

    
672
        logMetacat.debug("Checking if identifier exists...");
673
        // Check that the identifier exists
674
        if (!IdentifierManager.getInstance().identifierExists(guid.getValue())) {
675
            throw new NotFound("000", 
676
                "GUID does not exist");
677
        }
678

    
679
        // update the system metadata into the object store
680
        logMetacat.debug("Starting to update SystemMetadata...");
681
        sysmeta.setDateSysMetadataModified(new Date());
682
        try {
683
          IdentifierManager.getInstance().updateSystemMetadata(sysmeta);
684
          // force replication of this record
685
          ForceReplicationSystemMetadataHandler forceReplication = 
686
            new ForceReplicationSystemMetadataHandler(guid.getValue(), null);
687
        } catch (Exception e) {
688
            throw new ServiceFailure("4862", "Error updating system metadata: " + e.getClass() + ": " + e.getMessage());
689
        }
690
        
691
        logMetacat.debug("Returning from updateSystemMetadata");
692
        EventLog.getInstance().log(null, session.getSubject().getValue(), guid.getValue(), "updateSystemMetadata");
693
        return true;
694
  }
695
  
696
  /**
697
   * Given an optional scope and format, reserves and returns an identifier 
698
   * within that scope and format that is unique and will not be 
699
   * used by any other sessions. 
700
    * 
701
   * @param session - the Session object containing the credentials for the Subject
702
   * @param pid - The identifier of the object to register the system metadata against
703
   * @param scope - An optional string to be used to qualify the scope of 
704
   *                the identifier namespace, which is applied differently 
705
   *                depending on the format requested. If scope is not 
706
   *                supplied, a default scope will be used.
707
   * @param format - The optional name of the identifier format to be used, 
708
   *                  drawn from a DataONE-specific vocabulary of identifier 
709
   *                 format names, including several common syntaxes such 
710
   *                 as DOI, LSID, UUID, and LSRN, among others. If the 
711
   *                 format is not supplied by the caller, the CN service 
712
   *                 will use a default identifier format, which may change 
713
   *                 over time.
714
   * 
715
   * @return true if the registration succeeds
716
   * 
717
   * @throws InvalidToken
718
   * @throws ServiceFailure
719
   * @throws NotAuthorized
720
   * @throws IdentifierNotUnique
721
   * @throws NotImplemented
722
   */
723
  @Override
724
  public boolean reserveIdentifier(Session session, Identifier pid)
725
  throws InvalidToken, ServiceFailure,
726
        NotAuthorized, IdentifierNotUnique, NotImplemented, InvalidRequest {
727

    
728
    throw new NotImplemented("4191", "reserveIdentifier not implemented on this node");
729
  }
730
  
731
  @Override
732
  public Identifier generateIdentifier(Session session, String scheme, String fragment)
733
  throws InvalidToken, ServiceFailure,
734
        NotAuthorized, NotImplemented, InvalidRequest {
735
    throw new NotImplemented("4191", "generateIdentifier not implemented on this node");
736
  }
737
  
738
  /**
739
    * Checks whether the pid is reserved by the subject in the session param
740
    * If the reservation is held on the pid by the subject, we return true.
741
    * 
742
   * @param session - the Session object containing the Subject
743
   * @param pid - The identifier to check
744
   * 
745
   * @return true if the reservation exists for the subject/pid
746
   * 
747
   * @throws InvalidToken
748
   * @throws ServiceFailure
749
   * @throws NotFound - when the pid is not found (in use or in reservation)
750
   * @throws NotAuthorized - when the subject does not hold a reservation on the pid
751
   * @throws IdentifierNotUnique - when the pid is in use
752
   * @throws NotImplemented
753
   */
754

    
755
  @Override
756
  public boolean hasReservation(Session session, Identifier pid) 
757
      throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, IdentifierNotUnique, 
758
      NotImplemented, InvalidRequest {
759
  
760
      throw new NotImplemented("4191", "hasReservation not implemented on this node");
761
  }
762

    
763
  /**
764
   * Changes ownership (RightsHolder) of the specified object to the 
765
   * subject specified by userId
766
    * 
767
   * @param session - the Session object containing the credentials for the Subject
768
   * @param pid - Identifier of the object to be modified
769
   * @param userId - The subject that will be taking ownership of the specified object.
770
   *
771
   * @return pid - the identifier of the modified object
772
   * 
773
   * @throws ServiceFailure
774
   * @throws InvalidToken
775
   * @throws NotFound
776
   * @throws NotAuthorized
777
   * @throws NotImplemented
778
   * @throws InvalidRequest
779
   */  
780
  @Override
781
  public Identifier setOwner(Session session, Identifier pid, Subject userId)
782
    throws InvalidToken, ServiceFailure, NotFound, NotAuthorized,
783
    NotImplemented, InvalidRequest {
784
    
785
    // get the subject
786
    Subject subject = session.getSubject();
787
    // get the system metadata
788
    String guid = pid.getValue();
789
    
790
    // are we allowed to do this?
791
    if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
792
      throw new NotAuthorized("4440", "not allowed by " + subject.getValue() + " on " + guid);  
793
    }
794
    
795
    SystemMetadata systemMetadata = null;
796
    try {
797
      systemMetadata = IdentifierManager.getInstance().getSystemMetadata(guid);
798
    } catch (McdbDocNotFoundException e) {
799
      throw new NotFound("4460", "No record found for: " + guid);
800
    }
801
        
802
    // set the new rights holder
803
    systemMetadata.setRightsHolder(userId);
804
    
805
    // update the metadata
806
    try {
807
      IdentifierManager.getInstance().updateSystemMetadata(systemMetadata);
808
    } catch (McdbDocNotFoundException e) {
809
      throw new ServiceFailure("4490", e.getMessage());
810
    }
811

    
812
    return pid;
813
  }
814

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

    
839
	// build a predicate like: 
840
	    // "pid                    = '{pid}                   ' AND 
841
	    //  pemission              = '{permission}            ' AND
842
	    //  originatingNodeSubject = '{originatingNodeSubject}' AND
843
	    //  targetNodeSubject      = '{targetNodeSubject}     '"
844
	    boolean isAllowed = false;
845
	    String query = "";
846
	    query += "pid = '";
847
	    query += pid;
848
	    query += "' AND permission = '";
849
	    query += replicatePermission.name();
850
	    query += "' AND originatingNodeSubject = '";
851
	    query += originatingNodeSession.getSubject().getValue();
852
	    query += "' AND targetNodeSubject = '";
853
	    query += targetNodeSubject.getValue();
854
	    query += "'";
855
	    
856
	    logMetacat.debug("Pending replication task query is: " + query);
857
	    // search the hzPendingReplicationTasks map for the  originating node subject, 
858
	    // target node subject, pid, and replicate permission
859
	    
860
	    Set<CNReplicationTask> tasks = 
861
	      (Set<CNReplicationTask>) this.pendingReplicationTasks.values(new SqlPredicate(query));
862
	    
863
	    // do we have a matching task?
864
	    if ( tasks.size() >= 1 ) {
865
	      isAllowed = true;
866
	      
867
	    }
868
	    
869
	    return isAllowed;
870
    
871
  }
872

    
873
  /**
874
   * Implement the EntryListener interface for Hazelcast, reponding to entry
875
   * added events in the hzSystemMetadata map.  Evaluate the entry and create
876
   * CNReplicationTasks as appropriate (for DATA, METADATA, RESOURCE)
877
   * 
878
   * @param event - The EntryEvent that occurred
879
   */
880
  @Override
881
  public void entryAdded(EntryEvent<Identifier, SystemMetadata> event) {
882

    
883
    // TODO evaluate the type of system metadata change, decide if it warrants
884
    // a replication event, what type (DATA, METADATA, RESOURCE), 
885
    // iteratively lock the PID, create and submit the tasks, and expect a result back. 
886
    // Deal with exceptions.
887
    
888
  }
889

    
890
  /**
891
   * Implement the EntryListener interface for Hazelcast, reponding to entry
892
   * evicted events in the hzSystemMetadata map.  Evaluate the entry and create
893
   * CNReplicationTasks as appropriate (for DATA, METADATA, RESOURCE)
894
   * 
895
   * @param event - The EntryEvent that occurred
896
   */
897
  @Override
898
  public void entryEvicted(EntryEvent<Identifier, SystemMetadata> event) {
899
    // nothing to do, entries are still in the backing store
900
    
901
  }
902

    
903
  /**
904
   * Implement the EntryListener interface for Hazelcast, reponding to entry
905
   * removed events in the hzSystemMetadata map.  Evaluate the entry and create
906
   * CNReplicationTasks as appropriate (for DATA, METADATA, RESOURCE)
907
   * 
908
   * @param event - The EntryEvent that occurred
909
   */
910
  @Override
911
  public void entryRemoved(EntryEvent<Identifier, SystemMetadata> event) {
912
    // we don't remove objects
913
    
914
  }
915

    
916
  /**
917
   * Implement the EntryListener interface for Hazelcast, reponding to entry
918
   * updated events in the hzSystemMetadata map.  Evaluate the entry and create
919
   * CNReplicationTasks as appropriate (for DATA, METADATA, RESOURCE)
920
   * 
921
   * @param event - The EntryEvent that occurred
922
   */
923
  @Override
924
  public void entryUpdated(EntryEvent<Identifier, SystemMetadata> event) {
925

    
926
    // TODO evaluate the type of system metadata change, decide if it warrants
927
    // a replication event, what type (DATA, METADATA, RESOURCE), 
928
    // iteratively lock the PID, create and submit the tasks, and expect a result back. 
929
    // Deal with exceptions.
930
    
931
  }
932

    
933
}
(2-2/7)