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

    
62
import com.hazelcast.client.HazelcastClient;
63
import com.hazelcast.core.EntryEvent;
64
import com.hazelcast.core.EntryListener;
65
import com.hazelcast.core.Hazelcast;
66
import com.hazelcast.core.IMap;
67
import com.hazelcast.core.Member;
68
import com.hazelcast.partition.Partition;
69
import com.hazelcast.partition.PartitionService;
70
import com.hazelcast.query.SqlPredicate;
71

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

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

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

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

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

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

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

    
122
  /* the logger instance */
123
  private Logger logMetacat = null;
124

    
125
  /**
126
   * singleton accessor
127
   */
128
  public static CNodeService getInstance() { 
129
    if (instance == null) {
130
    
131
      instance = new CNodeService();
132
      
133
    }
134
  
135
    return instance;
136
  }
137
  
138
  /**
139
   * Constructor, private for singleton access
140
   */
141
  private CNodeService() {
142
    super();
143
    logMetacat = Logger.getLogger(CNodeService.class);
144
    
145
    // Get configuration properties on instantiation
146
    try {
147
      groupName = 
148
        PropertyService.getProperty("dataone.hazelcast.processCluster.groupName");
149
      groupPassword = 
150
        PropertyService.getProperty("dataone.hazelcast.processCluster.password");
151
      addressList = 
152
        PropertyService.getProperty("dataone.hazelcast.processCluster.instances");
153
      nodeMap = 
154
        PropertyService.getProperty("dataone.hazelcast.processCluster.nodesMap");
155
      systemMetadataMap = 
156
        PropertyService.getProperty("dataone.hazelcast.storageCluster.systemMetadataMap");
157

    
158
      // Become a DataONE-process cluster client
159
      //TODO: where should this be?
160
//      String[] addresses = addressList.split(",");
161
//      hzClient = 
162
//        HazelcastClient.newHazelcastClient(this.groupName, this.groupPassword, addresses);
163
//      nodes = hzClient.getMap(nodeMap);
164
//      pendingReplicationTasks = hzClient.getMap(pendingTasksQueue);
165
      
166
      // Get a reference to the shared system metadata map as a cluster member
167
      systemMetadata = Hazelcast.getMap(systemMetadataMap);
168
      
169
      // Listen for changes to the system metadata map
170
      systemMetadata.addEntryListener(this, true);
171
      
172
    } catch (PropertyNotFoundException e) {
173

    
174
      String msg = "Couldn't find Hazelcast properties for the DataONE clusters. " +
175
        "The error message was: " + e.getMessage();
176
      logMetacat.error(msg);
177
      
178
    }
179

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

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

    
230
    return true;
231
  }
232

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

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

    
290
    return true;
291
  }
292

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

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

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

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

    
414
  }
415

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

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

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

    
475
            DBQuery metacat = new DBQuery();
476

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
808
    return pid;
809
  }
810

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

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

    
869
  /**
870
	 * Implement the EntryListener interface for Hazelcast, reponding to entry
871
	 * added events in the hzSystemMetadata map. Evaluate the entry and create
872
	 * CNReplicationTasks as appropriate (for DATA, METADATA, RESOURCE)
873
	 * 
874
	 * @param event - The EntryEvent that occurred
875
	 */
876
	@Override
877
	public void entryAdded(EntryEvent<Identifier, SystemMetadata> event) {
878
		// handle as update - that method will create if necessary
879
		entryUpdated(event);
880
	}
881

    
882
  /**
883
   * Implement the EntryListener interface for Hazelcast, reponding to entry
884
   * evicted events in the hzSystemMetadata map.  Evaluate the entry and create
885
   * CNReplicationTasks as appropriate (for DATA, METADATA, RESOURCE)
886
   * 
887
   * @param event - The EntryEvent that occurred
888
   */
889
  @Override
890
  public void entryEvicted(EntryEvent<Identifier, SystemMetadata> event) {
891
    // nothing to do, entries are still in the backing store
892
    
893
  }
894

    
895
  /**
896
   * Implement the EntryListener interface for Hazelcast, reponding to entry
897
   * removed events in the hzSystemMetadata map.  Evaluate the entry and create
898
   * CNReplicationTasks as appropriate (for DATA, METADATA, RESOURCE)
899
   * 
900
   * @param event - The EntryEvent that occurred
901
   */
902
  @Override
903
  public void entryRemoved(EntryEvent<Identifier, SystemMetadata> event) {
904
    // we don't remove objects
905
    
906
  }
907

    
908
  /**
909
   * Implement the EntryListener interface for Hazelcast, reponding to entry
910
   * updated 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 entryUpdated(EntryEvent<Identifier, SystemMetadata> event) {
917

    
918
		logMetacat.debug("Entry added/updated to System Metadata map: " + event.getKey().getValue());
919
		PartitionService partitionService = Hazelcast.getPartitionService();
920
		Partition partition = partitionService.getPartition(event.getKey());
921
		Member ownerMember = partition.getOwner();
922
		if (!ownerMember.localMember()) {
923
			// need to pull the entry into the local store
924
			logMetacat.debug("Saving entry locally: " + event.getKey().getValue());
925
			try {
926
				if (!IdentifierManager.getInstance().identifierExists(event.getKey().getValue())) {
927
					IdentifierManager.getInstance().createSystemMetadata(event.getValue());
928
				} else {
929
					IdentifierManager.getInstance().updateSystemMetadata(event.getValue());
930
				}
931
			} catch (McdbDocNotFoundException e) {
932
				logMetacat.error(
933
						"Could not save System Metadata to local store.", e);
934
			}
935
		}
936

    
937
		// TODO evaluate the type of system metadata change, decide if it
938
		// warrants a replication event, what type (DATA, METADATA, RESOURCE),
939
		// iteratively lock the PID, create and submit the tasks, and expect a
940
		// result back. Deal with exceptions.
941
		boolean isMetadata = D1NodeService.isScienceMetadata(event.getValue());
942
		// TODO: do we need to do anything explicit here?
943
    
944
  }
945

    
946
}
(2-2/7)