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.core.Member;
70
import com.hazelcast.partition.Partition;
71
import com.hazelcast.partition.PartitionService;
72
import com.hazelcast.query.SqlPredicate;
73

    
74
import edu.ucsb.nceas.metacat.EventLog;
75
import edu.ucsb.nceas.metacat.IdentifierManager;
76
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
77
import edu.ucsb.nceas.metacat.properties.PropertyService;
78
import edu.ucsb.nceas.metacat.replication.ForceReplicationSystemMetadataHandler;
79
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
80

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

    
91
  /* the instance of the CNodeService object */
92
  private static CNodeService instance = null;
93
  
94
  /* The instance of the Hazelcast client */
95
  private HazelcastClient hzClient;
96

    
97
  /* The name of the DataONE Hazelcast cluster group */
98
  private String groupName;
99

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

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

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

    
127
  /* the logger instance */
128
  private Logger logMetacat = null;
129

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

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

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

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

    
237
    return true;
238
  }
239

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

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

    
297
    return true;
298
  }
299

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

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

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

    
419
    throw new NotImplemented("4131", "resolve not implemented");
420

    
421
  }
422

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

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

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

    
482
            DBQuery metacat = new DBQuery();
483

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

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

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

    
501
            System.out
502
                    .println(EcogridResultsetTransformer.toXMLString(records));
503
            response.setContentType("text/xml");
504
            out = response.getWriter();
505
            out.print(EcogridResultsetTransformer.toXMLString(records));
506

    
507
        } catch (Exception e) {
508
            e.printStackTrace();
509
        }*/
510
    
511

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

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

    
555
    return ObjectFormatService.getInstance().listFormats();
556
  }
557

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

    
570
    throw new NotImplemented("4800", "listNodes not implemented");
571
  }
572

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

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

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

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

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

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

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

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

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

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

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

    
815
    return pid;
816
  }
817

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

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

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

    
886
	  logMetacat.debug("Entry added to System Metadata map: " + event.getKey().getValue());
887
	  PartitionService partitionService = Hazelcast.getPartitionService();
888
	  Partition partition = partitionService.getPartition(event.getKey());
889
	  Member ownerMember = partition.getOwner();
890
	  if (!ownerMember.localMember()) {
891
		  // need to pull the entry into the local store
892
		  logMetacat.debug("Saving entry locally: " + event.getKey().getValue());
893
		  try {
894
			IdentifierManager.getInstance().createSystemMetadata(event.getValue());
895
		} catch (McdbDocNotFoundException e) {
896
			// TODO Auto-generated catch block
897
			e.printStackTrace();
898
		}
899
	  }
900
	  
901
    // TODO evaluate the type of system metadata change, decide if it warrants
902
    // a replication event, what type (DATA, METADATA, RESOURCE), 
903
    // iteratively lock the PID, create and submit the tasks, and expect a result back. 
904
    // Deal with exceptions.
905
    
906
  }
907

    
908
  /**
909
   * Implement the EntryListener interface for Hazelcast, reponding to entry
910
   * evicted events in the hzSystemMetadata map.  Evaluate the entry and create
911
   * CNReplicationTasks as appropriate (for DATA, METADATA, RESOURCE)
912
   * 
913
   * @param event - The EntryEvent that occurred
914
   */
915
  @Override
916
  public void entryEvicted(EntryEvent<Identifier, SystemMetadata> event) {
917
    // nothing to do, entries are still in the backing store
918
    
919
  }
920

    
921
  /**
922
   * Implement the EntryListener interface for Hazelcast, reponding to entry
923
   * removed events in the hzSystemMetadata map.  Evaluate the entry and create
924
   * CNReplicationTasks as appropriate (for DATA, METADATA, RESOURCE)
925
   * 
926
   * @param event - The EntryEvent that occurred
927
   */
928
  @Override
929
  public void entryRemoved(EntryEvent<Identifier, SystemMetadata> event) {
930
    // we don't remove objects
931
    
932
  }
933

    
934
  /**
935
   * Implement the EntryListener interface for Hazelcast, reponding to entry
936
   * updated events in the hzSystemMetadata map.  Evaluate the entry and create
937
   * CNReplicationTasks as appropriate (for DATA, METADATA, RESOURCE)
938
   * 
939
   * @param event - The EntryEvent that occurred
940
   */
941
  @Override
942
  public void entryUpdated(EntryEvent<Identifier, SystemMetadata> event) {
943

    
944
    // TODO evaluate the type of system metadata change, decide if it warrants
945
    // a replication event, what type (DATA, METADATA, RESOURCE), 
946
    // iteratively lock the PID, create and submit the tasks, and expect a result back. 
947
    // Deal with exceptions.
948
    
949
  }
950

    
951
}
(2-2/7)