Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000-2011 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author:  $'
7
 *     '$Date:  $'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23

    
24
package edu.ucsb.nceas.metacat.dataone;
25

    
26
import java.io.IOException;
27
import java.io.InputStream;
28
import java.math.BigInteger;
29
import java.util.Calendar;
30
import java.util.Date;
31
import java.util.List;
32
import java.util.Set;
33

    
34
import javax.servlet.http.HttpServletRequest;
35

    
36
import org.apache.commons.io.IOUtils;
37
import org.apache.log4j.Logger;
38
import org.dataone.client.CNode;
39
import org.dataone.client.D1Client;
40
import org.dataone.service.cn.v1.CNAuthorization;
41
import org.dataone.service.cn.v1.CNCore;
42
import org.dataone.service.cn.v1.CNRead;
43
import org.dataone.service.cn.v1.CNReplication;
44
import org.dataone.service.exceptions.IdentifierNotUnique;
45
import org.dataone.service.exceptions.InsufficientResources;
46
import org.dataone.service.exceptions.InvalidRequest;
47
import org.dataone.service.exceptions.InvalidSystemMetadata;
48
import org.dataone.service.exceptions.InvalidToken;
49
import org.dataone.service.exceptions.NotAuthorized;
50
import org.dataone.service.exceptions.NotFound;
51
import org.dataone.service.exceptions.NotImplemented;
52
import org.dataone.service.exceptions.ServiceFailure;
53
import org.dataone.service.exceptions.UnsupportedType;
54
import org.dataone.service.types.v1.AccessPolicy;
55
import org.dataone.service.types.v1.Checksum;
56
import org.dataone.service.types.v1.Identifier;
57
import org.dataone.service.types.v1.Node;
58
import org.dataone.service.types.v1.NodeList;
59
import org.dataone.service.types.v1.NodeReference;
60
import org.dataone.service.types.v1.NodeType;
61
import org.dataone.service.types.v1.ObjectFormat;
62
import org.dataone.service.types.v1.ObjectFormatIdentifier;
63
import org.dataone.service.types.v1.ObjectFormatList;
64
import org.dataone.service.types.v1.ObjectList;
65
import org.dataone.service.types.v1.ObjectLocationList;
66
import org.dataone.service.types.v1.Permission;
67
import org.dataone.service.types.v1.Replica;
68
import org.dataone.service.types.v1.ReplicationPolicy;
69
import org.dataone.service.types.v1.ReplicationStatus;
70
import org.dataone.service.types.v1.Session;
71
import org.dataone.service.types.v1.Subject;
72
import org.dataone.service.types.v1.SystemMetadata;
73
import org.dataone.service.types.v1.util.ChecksumUtil;
74
import org.dataone.service.util.Constants;
75

    
76
import com.hazelcast.query.SqlPredicate;
77

    
78
import edu.ucsb.nceas.metacat.EventLog;
79
import edu.ucsb.nceas.metacat.IdentifierManager;
80
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
81
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
82

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

    
93
  /* the logger instance */
94
  private Logger logMetacat = null;
95

    
96
  /**
97
   * singleton accessor
98
   */
99
  public static CNodeService getInstance(HttpServletRequest request) { 
100
    return new CNodeService(request);
101
  }
102
  
103
  /**
104
   * Constructor, private for singleton access
105
   */
106
  private CNodeService(HttpServletRequest request) {
107
    super(request);
108
    logMetacat = Logger.getLogger(CNodeService.class);
109
        
110
  }
111
    
112
  /**
113
   * Set the replication policy for an object given the object identifier
114
   * 
115
   * @param session - the Session object containing the credentials for the Subject
116
   * @param pid - the object identifier for the given object
117
   * @param policy - the replication policy to be applied
118
   * 
119
   * @return true or false
120
   * 
121
   * @throws NotImplemented
122
   * @throws NotAuthorized
123
   * @throws ServiceFailure
124
   * @throws InvalidRequest
125
   * 
126
   */
127
  @Override
128
  public boolean setReplicationPolicy(Session session, Identifier pid,
129
      ReplicationPolicy policy) 
130
      throws NotImplemented, NotFound, NotAuthorized, ServiceFailure, 
131
      InvalidRequest, InvalidToken {
132
      
133
      // get the subject
134
      Subject subject = session.getSubject();
135
      
136
      // are we allowed to do this?
137
      if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
138
        throw new NotAuthorized("4881", Permission.CHANGE_PERMISSION + 
139
            " not allowed by " + subject.getValue() + " on " + pid.getValue());  
140
      }
141
      
142
      SystemMetadata systemMetadata = null;
143
      try {
144
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
145
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
146
        
147
      } catch (Exception e) { // Catch is generic since HZ throws RuntimeException
148
        throw new NotFound("4884", "No record found for: " + pid.getValue());
149
        
150
      }
151
          
152
      // set the new policy
153
      systemMetadata.setReplicationPolicy(policy);
154
      
155
      // update the metadata
156
      try {
157
        systemMetadata.setSerialVersion(systemMetadata.getSerialVersion().add(BigInteger.ONE));
158
        systemMetadata.setDateSysMetadataModified(Calendar.getInstance().getTime());
159
	      HazelcastService.getInstance().getSystemMetadataMap().put(systemMetadata.getIdentifier(), systemMetadata);
160
	      HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
161
	      
162
      } catch (Exception e) {
163
		      throw new ServiceFailure("4882", e.getMessage());
164
		  
165
	    } finally {
166
	        HazelcastService.getInstance().getSystemMetadataMap().unlock(systemMetadata.getIdentifier());
167
	        
168
	    }
169
    
170
      return true;
171
  }
172

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

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

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

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

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

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

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

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

    
393
  }
394

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

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

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

    
454
            DBQuery metacat = new DBQuery();
455

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
890
          }
891
      }
892
      
893
      HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
894

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

    
904
	  }
905
	    
906
	  return isAllowed;
907
    
908
  }
909

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

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

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

    
984
  }
985

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

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

    
1056
}
(1-1/4)