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: tao $'
7
 *     '$Date: 2015-07-15 11:07:27 -0700 (Wed, 15 Jul 2015) $'
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.ByteArrayOutputStream;
27
import java.io.File;
28
import java.io.FileNotFoundException;
29
import java.io.FileOutputStream;
30
import java.io.IOException;
31
import java.io.InputStream;
32
import java.io.OutputStream;
33
import java.io.OutputStreamWriter;
34
import java.io.Writer;
35
import java.math.BigInteger;
36
import java.sql.SQLException;
37
import java.util.ArrayList;
38
import java.util.Calendar;
39
import java.util.Date;
40
import java.util.Hashtable;
41
import java.util.List;
42
import java.util.Set;
43
import java.util.Timer;
44
import java.util.Vector;
45
import java.util.concurrent.locks.Lock;
46

    
47
import javax.servlet.http.HttpServletRequest;
48

    
49
import org.apache.commons.io.IOUtils;
50
import org.apache.log4j.Logger;
51
import org.dataone.client.v2.CNode;
52
import org.dataone.client.v2.itk.D1Client;
53
import org.dataone.client.v2.formats.ObjectFormatCache;
54
import org.dataone.configuration.Settings;
55
import org.dataone.service.exceptions.BaseException;
56
import org.dataone.service.exceptions.IdentifierNotUnique;
57
import org.dataone.service.exceptions.InsufficientResources;
58
import org.dataone.service.exceptions.InvalidRequest;
59
import org.dataone.service.exceptions.InvalidSystemMetadata;
60
import org.dataone.service.exceptions.InvalidToken;
61
import org.dataone.service.exceptions.NotAuthorized;
62
import org.dataone.service.exceptions.NotFound;
63
import org.dataone.service.exceptions.NotImplemented;
64
import org.dataone.service.exceptions.ServiceFailure;
65
import org.dataone.service.exceptions.UnsupportedType;
66
import org.dataone.service.types.v1.AccessRule;
67
import org.dataone.service.types.v1.DescribeResponse;
68
import org.dataone.service.types.v1.Group;
69
import org.dataone.service.types.v1.Identifier;
70
import org.dataone.service.types.v1.ObjectFormatIdentifier;
71
import org.dataone.service.types.v1.ObjectList;
72
import org.dataone.service.types.v2.Log;
73
import org.dataone.service.types.v2.Node;
74
import org.dataone.service.types.v2.OptionList;
75
import org.dataone.service.types.v1.Event;
76
import org.dataone.service.types.v1.NodeReference;
77
import org.dataone.service.types.v1.NodeType;
78
import org.dataone.service.types.v2.ObjectFormat;
79
import org.dataone.service.types.v1.Permission;
80
import org.dataone.service.types.v1.Replica;
81
import org.dataone.service.types.v1.Session;
82
import org.dataone.service.types.v1.Subject;
83
import org.dataone.service.types.v2.SystemMetadata;
84
import org.dataone.service.types.v1.util.AuthUtils;
85
import org.dataone.service.types.v1.util.ChecksumUtil;
86
import org.dataone.service.util.Constants;
87

    
88
import edu.ucsb.nceas.metacat.AccessionNumberException;
89
import edu.ucsb.nceas.metacat.DBTransform;
90
import edu.ucsb.nceas.metacat.DocumentImpl;
91
import edu.ucsb.nceas.metacat.EventLog;
92
import edu.ucsb.nceas.metacat.IdentifierManager;
93
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
94
import edu.ucsb.nceas.metacat.MetacatHandler;
95
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
96
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
97
import edu.ucsb.nceas.metacat.database.DBConnection;
98
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
99
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
100
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
101
import edu.ucsb.nceas.metacat.properties.PropertyService;
102
import edu.ucsb.nceas.metacat.replication.ForceReplicationHandler;
103
import edu.ucsb.nceas.metacat.util.SkinUtil;
104
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
105

    
106
public abstract class D1NodeService {
107
    
108
  public static final String DELETEDMESSAGE = "The object with the PID has been deleted from the node.";
109
  
110
  private static Logger logMetacat = Logger.getLogger(D1NodeService.class);
111

    
112
  /** For logging the operations */
113
  protected HttpServletRequest request;
114
  
115
  /* reference to the metacat handler */
116
  protected MetacatHandler handler;
117
  
118
  /* parameters set in the incoming request */
119
  private Hashtable<String, String[]> params;
120
  
121
  /**
122
   * limit paged results sets to a configured maximum
123
   */
124
  protected static int MAXIMUM_DB_RECORD_COUNT = 7000;
125
  
126
  static {
127
		try {
128
			MAXIMUM_DB_RECORD_COUNT = Integer.valueOf(PropertyService.getProperty("database.webResultsetSize"));
129
		} catch (Exception e) {
130
			logMetacat.warn("Could not set MAXIMUM_DB_RECORD_COUNT", e);
131
		}
132
	}
133
  
134
  /**
135
   * out-of-band session object to be used when not passed in as a method parameter
136
   */
137
  protected Session session;
138

    
139
  /**
140
   * Constructor - used to set the metacatUrl from a subclass extending D1NodeService
141
   * 
142
   * @param metacatUrl - the URL of the metacat service, including the ending /d1
143
   */
144
  public D1NodeService(HttpServletRequest request) {
145
		this.request = request;
146
	}
147

    
148
  /**
149
   * retrieve the out-of-band session
150
   * @return
151
   */
152
  	public Session getSession() {
153
		return session;
154
	}
155
  	
156
  	/**
157
  	 * Set the out-of-band session
158
  	 * @param session
159
  	 */
160
	public void setSession(Session session) {
161
		this.session = session;
162
	}
163

    
164
  /**
165
   * This method provides a lighter weight mechanism than 
166
   * getSystemMetadata() for a client to determine basic 
167
   * properties of the referenced object.
168
   * 
169
   * @param session - the Session object containing the credentials for the Subject
170
   * @param pid - the identifier of the object to be described
171
   * 
172
   * @return describeResponse - A set of values providing a basic description 
173
   *                            of the object.
174
   * 
175
   * @throws InvalidToken
176
   * @throws ServiceFailure
177
   * @throws NotAuthorized
178
   * @throws NotFound
179
   * @throws NotImplemented
180
   * @throws InvalidRequest
181
   */
182
  public DescribeResponse describe(Session session, Identifier pid) 
183
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
184
      
185
      String serviceFailureCode = "4931";
186
      Identifier sid = getPIDForSID(pid, serviceFailureCode);
187
      if(sid != null) {
188
          pid = sid;
189
      }
190

    
191
    // get system metadata and construct the describe response
192
      SystemMetadata sysmeta = getSystemMetadata(session, pid);
193
      DescribeResponse describeResponse = 
194
      	new DescribeResponse(sysmeta.getFormatId(), sysmeta.getSize(), 
195
      			sysmeta.getDateSysMetadataModified(),
196
      			sysmeta.getChecksum(), sysmeta.getSerialVersion());
197

    
198
      return describeResponse;
199

    
200
  }
201
  
202
  /**
203
   * Deletes an object from the Member Node, where the object is either a 
204
   * data object or a science metadata object.
205
   * 
206
   * @param session - the Session object containing the credentials for the Subject
207
   * @param pid - The object identifier to be deleted
208
   * 
209
   * @return pid - the identifier of the object used for the deletion
210
   * 
211
   * @throws InvalidToken
212
   * @throws ServiceFailure
213
   * @throws NotAuthorized
214
   * @throws NotFound
215
   * @throws NotImplemented
216
   * @throws InvalidRequest
217
   */
218
  public Identifier delete(Session session, Identifier pid) 
219
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
220
      
221
      String localId = null;
222
      if (session == null) {
223
      	throw new InvalidToken("1330", "No session has been provided");
224
      }
225
      // just for logging purposes
226
      String username = session.getSubject().getValue();
227

    
228
      // do we have a valid pid?
229
      if (pid == null || pid.getValue().trim().equals("")) {
230
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
231
      }
232

    
233
      // check for the existing identifier
234
      try {
235
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
236
      } catch (McdbDocNotFoundException e) {
237
          throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
238
      } catch (SQLException e) {
239
          throw new ServiceFailure("1350", "The object with the provided " + "identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
240
      }
241
      
242
      try {
243
          // delete the document, as admin
244
          DocumentImpl.delete(localId, null, null, null, true);
245
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
246

    
247
          // archive it
248
          // DocumentImpl.delete() now sets this
249
          // see https://redmine.dataone.org/issues/3406
250
//          SystemMetadata sysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
251
//          sysMeta.setArchived(true);
252
//          sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
253
//          HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
254
          
255
      } catch (McdbDocNotFoundException e) {
256
          throw new NotFound("1340", "The provided identifier was invalid.");
257

    
258
      } catch (SQLException e) {
259
          throw new ServiceFailure("1350", "There was a problem deleting the object." + "The error message was: " + e.getMessage());
260

    
261
      } catch (InsufficientKarmaException e) {
262
          if ( logMetacat.isDebugEnabled() ) {
263
              e.printStackTrace();
264
          }
265
          throw new NotAuthorized("1320", "The provided identity does not have " + "permission to DELETE objects on the Member Node.");
266
      
267
      } catch (Exception e) { // for some reason DocumentImpl throws a general Exception
268
          throw new ServiceFailure("1350", "There was a problem deleting the object." + "The error message was: " + e.getMessage());
269
      }
270

    
271
      return pid;
272
  }
273
  
274
  /**
275
   * Low level, "are you alive" operation. A valid ping response is 
276
   * indicated by a HTTP status of 200.
277
   * 
278
   * @return true if the service is alive
279
   * 
280
   * @throws NotImplemented
281
   * @throws ServiceFailure
282
   * @throws InsufficientResources
283
   */
284
  public Date ping() 
285
      throws NotImplemented, ServiceFailure, InsufficientResources {
286

    
287
      // test if we can get a database connection
288
      int serialNumber = -1;
289
      DBConnection dbConn = null;
290
      try {
291
          dbConn = DBConnectionPool.getDBConnection("MNodeService.ping");
292
          serialNumber = dbConn.getCheckOutSerialNumber();
293
      } catch (SQLException e) {
294
      	ServiceFailure sf = new ServiceFailure("", e.getMessage());
295
      	sf.initCause(e);
296
          throw sf;
297
      } finally {
298
          // Return the database connection
299
          DBConnectionPool.returnDBConnection(dbConn, serialNumber);
300
      }
301

    
302
      return Calendar.getInstance().getTime();
303
  }
304
  
305
  /**
306
   * Adds a new object to the Node, where the object is either a data 
307
   * object or a science metadata object. This method is called by clients 
308
   * to create new data objects on Member Nodes or internally for Coordinating
309
   * Nodes
310
   * 
311
   * @param session - the Session object containing the credentials for the Subject
312
   * @param pid - The object identifier to be created
313
   * @param object - the object bytes
314
   * @param sysmeta - the system metadata that describes the object  
315
   * 
316
   * @return pid - the object identifier created
317
   * 
318
   * @throws InvalidToken
319
   * @throws ServiceFailure
320
   * @throws NotAuthorized
321
   * @throws IdentifierNotUnique
322
   * @throws UnsupportedType
323
   * @throws InsufficientResources
324
   * @throws InvalidSystemMetadata
325
   * @throws NotImplemented
326
   * @throws InvalidRequest
327
   */
328
  public Identifier create(Session session, Identifier pid, InputStream object,
329
    SystemMetadata sysmeta) 
330
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
331
    UnsupportedType, InsufficientResources, InvalidSystemMetadata, 
332
    NotImplemented, InvalidRequest {
333

    
334
    Identifier resultPid = null;
335
    String localId = null;
336
    boolean allowed = false;
337
    
338
    // check for null session
339
    if (session == null) {
340
    	throw new InvalidToken("4894", "Session is required to WRITE to the Node.");
341
    }
342
    Subject subject = session.getSubject();
343

    
344
    Subject publicSubject = new Subject();
345
    publicSubject.setValue(Constants.SUBJECT_PUBLIC);
346
	// be sure the user is authenticated for create()
347
    if (subject == null || subject.getValue() == null || 
348
        subject.equals(publicSubject) ) {
349
      throw new NotAuthorized("1100", "The provided identity does not have " +
350
        "permission to WRITE to the Node.");
351
      
352
    }
353
        
354
    // verify that pid == SystemMetadata.getIdentifier()
355
    logMetacat.debug("Comparing pid|sysmeta_pid: " + 
356
      pid.getValue() + "|" + sysmeta.getIdentifier().getValue());
357
    if (!pid.getValue().equals(sysmeta.getIdentifier().getValue())) {
358
        throw new InvalidSystemMetadata("1180", 
359
            "The supplied system metadata is invalid. " +
360
            "The identifier " + pid.getValue() + " does not match identifier" +
361
            "in the system metadata identified by " +
362
            sysmeta.getIdentifier().getValue() + ".");
363
        
364
    }
365
    
366

    
367
    logMetacat.debug("Checking if identifier exists: " + pid.getValue());
368
    // Check that the identifier does not already exist
369
    boolean idExists = false;
370
    try {
371
        idExists = IdentifierManager.getInstance().identifierExists(pid.getValue());
372
    } catch (SQLException e) {
373
        throw new ServiceFailure("1190", 
374
                                "The requested identifier " + pid.getValue() +
375
                                " couldn't be determined if it is unique since : "+e.getMessage());
376
    }
377
    if (idExists) {
378
	    	throw new IdentifierNotUnique("1120", 
379
			          "The requested identifier " + pid.getValue() +
380
			          " is already used by another object and" +
381
			          "therefore can not be used for this object. Clients should choose" +
382
			          "a new identifier that is unique and retry the operation or " +
383
			          "use CN.reserveIdentifier() to reserve one.");
384
    	
385
    }
386
    
387
    
388
    // TODO: this probably needs to be refined more
389
    try {
390
      allowed = isAuthorized(session, pid, Permission.WRITE);
391
            
392
    } catch (NotFound e) {
393
      // The identifier doesn't exist, writing should be fine.
394
      allowed = true;
395
    }
396
    
397
    if(!allowed) {
398
        throw new NotAuthorized("1100", "Provited Identity doesn't have the WRITE permission on the pid "+pid.getValue());
399
    }
400
    // verify checksum, only if we can reset the inputstream
401
    if (object.markSupported()) {
402
        logMetacat.debug("Checking checksum for: " + pid.getValue());
403
	    String checksumAlgorithm = sysmeta.getChecksum().getAlgorithm();
404
	    String checksumValue = sysmeta.getChecksum().getValue();
405
	    try {
406
			String computedChecksumValue = ChecksumUtil.checksum(object, checksumAlgorithm).getValue();
407
			// it's very important that we don't consume the stream
408
			object.reset();
409
			if (!computedChecksumValue.equals(checksumValue)) {
410
			    logMetacat.error("Checksum for " + pid.getValue() + " does not match system metadata, computed = " + computedChecksumValue );
411
				throw new InvalidSystemMetadata("4896", "Checksum given does not match that of the object");
412
			}
413
		} catch (Exception e) {
414
			String msg = "Error verifying checksum values";
415
	      	logMetacat.error(msg, e);
416
	        throw new ServiceFailure("1190", msg + ": " + e.getMessage());
417
		}
418
    } else {
419
    	logMetacat.warn("mark is not supported on the object's input stream - cannot verify checksum without consuming stream");
420
    }
421
    	
422
    // we have the go ahead
423
    //if ( allowed ) {
424
         
425
        // save the sysmeta
426
        try {
427
            // lock and unlock of the pid happens in the subclass
428
            HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
429
           
430
        
431
        } catch (Exception e) {
432
            logMetacat.error("Problem creating system metadata: " + pid.getValue(), e);
433
            throw new ServiceFailure("1190", e.getMessage());
434
        }
435
      
436
        logMetacat.debug("Allowed to insert: " + pid.getValue());
437

    
438
      // Science metadata (XML) or science data object?
439
      // TODO: there are cases where certain object formats are science metadata
440
      // but are not XML (netCDF ...).  Handle this.
441
      if ( isScienceMetadata(sysmeta) ) {
442
        
443
        // CASE METADATA:
444
      	//String objectAsXML = "";
445
        try {
446
	        //objectAsXML = IOUtils.toString(object, "UTF-8");
447
	        localId = insertOrUpdateDocument(object,"UTF-8", pid, session, "insert");
448
	        //localId = im.getLocalId(pid.getValue());
449

    
450
        } catch (IOException e) {
451
            removeSystemMeta(pid);
452
        	String msg = "The Node is unable to create the object. " +
453
          "There was a problem converting the object to XML";
454
        	logMetacat.info(msg);
455
          throw new ServiceFailure("1190", msg + ": " + e.getMessage());
456

    
457
        } catch (ServiceFailure e) {
458
            removeSystemMeta(pid);
459
            throw e;
460
        } catch (Exception e) {
461
            removeSystemMeta(pid);
462
            throw new ServiceFailure("1190", "The node is unable to create the object: " + e.getMessage());
463
        }
464
                    
465
      } else {
466
	        
467
	      // DEFAULT CASE: DATA (needs to be checked and completed)
468
          try {
469
              localId = insertDataObject(object, pid, session);
470
          } catch (ServiceFailure e) {
471
              removeSystemMeta(pid);
472
              throw e;
473
          } catch (Exception e) {
474
              removeSystemMeta(pid);
475
              throw new ServiceFailure("1190", "The node is unable to create the object: " + e.getMessage());
476
          }
477
	      
478
      }   
479
    
480
    //}
481

    
482
    logMetacat.debug("Done inserting new object: " + pid.getValue());
483
    
484
    // setting the resulting identifier failed
485
    if (localId == null ) {
486
        removeSystemMeta(pid);
487
      throw new ServiceFailure("1190", "The Node is unable to create the object. ");
488
    }
489
    
490
    try {
491
        // submit for indexing
492
        MetacatSolrIndex.getInstance().submit(sysmeta.getIdentifier(), sysmeta, null, true);
493
    } catch (Exception e) {
494
        logMetacat.warn("Couldn't create solr index for object "+pid.getValue());
495
    }
496

    
497
    resultPid = pid;
498
    
499
    logMetacat.debug("create() complete for object: " + pid.getValue());
500

    
501
    return resultPid;
502
  }
503
  
504
  /*
505
   * Roll-back method when inserting data object fails.
506
   */
507
  protected void removeSystemMeta(Identifier id){
508
      HazelcastService.getInstance().getSystemMetadataMap().remove(id);
509
  }
510
  
511
  /*
512
   * Roll-back method when inserting data object fails.
513
   */
514
  protected void removeSolrIndex(SystemMetadata sysMeta) {
515
      sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
516
      sysMeta.setArchived(true);
517
      sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
518
      try {
519
          MetacatSolrIndex.getInstance().submit(sysMeta.getIdentifier(), sysMeta, null, false);
520
      } catch (Exception e) {
521
          logMetacat.warn("Can't remove the solr index for pid "+sysMeta.getIdentifier().getValue());
522
      }
523
      
524
  }
525

    
526
  /**
527
   * Return the log records associated with a given event between the start and 
528
   * end dates listed given a particular Subject listed in the Session
529
   * 
530
   * @param session - the Session object containing the credentials for the Subject
531
   * @param fromDate - the start date of the desired log records
532
   * @param toDate - the end date of the desired log records
533
   * @param event - restrict log records of a specific event type
534
   * @param start - zero based offset from the first record in the 
535
   *                set of matching log records. Used to assist with 
536
   *                paging the response.
537
   * @param count - maximum number of log records to return in the response. 
538
   *                Used to assist with paging the response.
539
   * 
540
   * @return the desired log records
541
   * 
542
   * @throws InvalidToken
543
   * @throws ServiceFailure
544
   * @throws NotAuthorized
545
   * @throws InvalidRequest
546
   * @throws NotImplemented
547
   */
548
  public Log getLogRecords(Session session, Date fromDate, Date toDate, 
549
      String event, String pidFilter, Integer start, Integer count) throws InvalidToken, ServiceFailure,
550
      NotAuthorized, InvalidRequest, NotImplemented {
551

    
552
	  // only admin access to this method
553
	  // see https://redmine.dataone.org/issues/2855
554
	  if (!isAdminAuthorized(session)) {
555
		  throw new NotAuthorized("1460", "Only the CN or admin is allowed to harvest logs from this node");
556
	  }
557
    Log log = new Log();
558
    IdentifierManager im = IdentifierManager.getInstance();
559
    EventLog el = EventLog.getInstance();
560
    if ( fromDate == null ) {
561
      logMetacat.debug("setting fromdate from null");
562
      fromDate = new Date(1);
563
    }
564
    if ( toDate == null ) {
565
      logMetacat.debug("setting todate from null");
566
      toDate = new Date();
567
    }
568

    
569
    if ( start == null ) {
570
    	start = 0;	
571
    }
572
    
573
    if ( count == null ) {
574
    	count = 1000;
575
    }
576
    
577
    // safeguard against large requests
578
    if (count > MAXIMUM_DB_RECORD_COUNT) {
579
    	count = MAXIMUM_DB_RECORD_COUNT;
580
    }
581

    
582
    String[] filterDocid = null;
583
    if (pidFilter != null && !pidFilter.trim().equals("")) {
584
        //check if the given identifier is a sid. If it is sid, choose the current pid of the sid.
585
        Identifier pid = new Identifier();
586
        pid.setValue(pidFilter);
587
        String serviceFailureCode = "1490";
588
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
589
        if(sid != null) {
590
            pid = sid;
591
        }
592
        pidFilter = pid.getValue();
593
		try {
594
	      String localId = im.getLocalId(pidFilter);
595
	      filterDocid = new String[] {localId};
596
	    } catch (Exception ex) { 
597
	    	String msg = "Could not find localId for given pidFilter '" + pidFilter + "'";
598
	        logMetacat.warn(msg, ex);
599
	        //throw new InvalidRequest("1480", msg);
600
	        return log; //return 0 record
601
	    }
602
    }
603
    
604
    logMetacat.debug("fromDate: " + fromDate);
605
    logMetacat.debug("toDate: " + toDate);
606

    
607
    log = el.getD1Report(null, null, filterDocid, event,
608
        new java.sql.Timestamp(fromDate.getTime()),
609
        new java.sql.Timestamp(toDate.getTime()), false, start, count);
610
    
611
    logMetacat.info("getLogRecords");
612
    return log;
613
  }
614
    
615
  /**
616
   * Return the object identified by the given object identifier
617
   * 
618
   * @param session - the Session object containing the credentials for the Subject
619
   * @param pid - the object identifier for the given object
620
   * 
621
   * TODO: The D1 Authorization API doesn't provide information on which 
622
   * authentication system the Subject belongs to, and so it's not possible to
623
   * discern which Person or Group is a valid KNB LDAP DN.  Fix this.
624
   * 
625
   * @return inputStream - the input stream of the given object
626
   * 
627
   * @throws InvalidToken
628
   * @throws ServiceFailure
629
   * @throws NotAuthorized
630
   * @throws InvalidRequest
631
   * @throws NotImplemented
632
   */
633
  public InputStream get(Session session, Identifier pid) 
634
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
635
    NotImplemented {
636
    
637
    String serviceFailureCode = "1030";
638
    Identifier sid = getPIDForSID(pid, serviceFailureCode);
639
    if(sid != null) {
640
        pid = sid;
641
    }
642
    
643
    InputStream inputStream = null; // bytes to be returned
644
    handler = new MetacatHandler(new Timer());
645
    boolean allowed = false;
646
    String localId; // the metacat docid for the pid
647
    
648
    // get the local docid from Metacat
649
    try {
650
      localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
651
    
652
    } catch (McdbDocNotFoundException e) {
653
      throw new NotFound("1020", "The object specified by " + 
654
                         pid.getValue() +
655
                         " does not exist at this node.");
656
    } catch (SQLException e) {
657
        throw new ServiceFailure("1030", "The object specified by "+ pid.getValue()+
658
                                  " couldn't be identified at this node since "+e.getMessage());
659
    }
660
    
661
    // check for authorization
662
    try {
663
		allowed = isAuthorized(session, pid, Permission.READ);
664
	} catch (InvalidRequest e) {
665
		throw new ServiceFailure("1030", e.getDescription());
666
	}
667
    
668
    // if the person is authorized, perform the read
669
    if (allowed) {
670
      try {
671
        inputStream = handler.read(localId);
672
      } catch (McdbDocNotFoundException de) {
673
          String error ="";
674
          if(EventLog.getInstance().isDeleted(localId)) {
675
                error=DELETEDMESSAGE;
676
          }
677
          throw new NotFound("1020", "The object specified by " + 
678
                           pid.getValue() +
679
                           " does not exist at this node. "+error);
680
      } catch (Exception e) {
681
        throw new ServiceFailure("1030", "The object specified by " + 
682
            pid.getValue() +
683
            " could not be returned due to error: " +
684
            e.getMessage()+". ");
685
      }
686
    }
687

    
688
    // if we fail to set the input stream
689
    if ( inputStream == null ) {
690
        String error ="";
691
        if(EventLog.getInstance().isDeleted(localId)) {
692
              error=DELETEDMESSAGE;
693
        }
694
        throw new NotFound("1020", "The object specified by " + 
695
                         pid.getValue() +
696
                         " does not exist at this node. "+error);
697
    }
698
    
699
	// log the read event
700
    String principal = Constants.SUBJECT_PUBLIC;
701
    if (session != null && session.getSubject() != null) {
702
    	principal = session.getSubject().getValue();
703
    }
704
    EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "read");
705
    
706
    return inputStream;
707
  }
708

    
709
  /**
710
   * Return the system metadata for a given object
711
   * 
712
   * @param session - the Session object containing the credentials for the Subject
713
   * @param pid - the object identifier for the given object
714
   * 
715
   * @return inputStream - the input stream of the given system metadata object
716
   * 
717
   * @throws InvalidToken
718
   * @throws ServiceFailure
719
   * @throws NotAuthorized
720
   * @throws NotFound
721
   * @throws InvalidRequest
722
   * @throws NotImplemented
723
   */
724
    public SystemMetadata getSystemMetadata(Session session, Identifier pid)
725
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
726
        NotImplemented {
727

    
728
        String serviceFailureCode = "1090";
729
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
730
        if(sid != null) {
731
            pid = sid;
732
        }
733
        boolean isAuthorized = false;
734
        SystemMetadata systemMetadata = null;
735
        List<Replica> replicaList = null;
736
        NodeReference replicaNodeRef = null;
737
        List<Node> nodeListBySubject = null;
738
        Subject subject = null;
739
        
740
        if (session != null ) {
741
            subject = session.getSubject();
742
        }
743
        
744
        // check normal authorization
745
        BaseException originalAuthorizationException = null;
746
        if (!isAuthorized) {
747
            try {
748
                isAuthorized = isAuthorized(session, pid, Permission.READ);
749

    
750
            } catch (InvalidRequest e) {
751
                throw new ServiceFailure("1090", e.getDescription());
752
            } catch (NotAuthorized nae) {
753
            	// catch this for later
754
            	originalAuthorizationException = nae;
755
			}
756
        }
757
        
758
        // get the system metadata first because we need the replica list for auth
759
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
760
        
761
        // check the replica information to expand access to MNs that might need it
762
        if (!isAuthorized) {
763
        	
764
	        try {
765
	        	
766
	            // if MNs are listed as replicas, allow access
767
	            if ( systemMetadata != null ) {
768
	                replicaList = systemMetadata.getReplicaList();
769
	                // only check if there are in fact replicas listed
770
	                if ( replicaList != null ) {
771
	                    
772
	                    if ( subject != null ) {
773
	                        // get the list of nodes with a matching node subject
774
	                        try {
775
	                            nodeListBySubject = listNodesBySubject(session.getSubject());
776
	
777
	                        } catch (BaseException e) {
778
	                            // Unexpected error contacting the CN via D1Client
779
	                            String msg = "Caught an unexpected error while trying "
780
	                                    + "to potentially authorize system metadata access "
781
	                                    + "based on the session subject. The error was "
782
	                                    + e.getMessage();
783
	                            logMetacat.error(msg);
784
	                            if (logMetacat.isDebugEnabled()) {
785
	                                e.printStackTrace();
786
	
787
	                            }
788
	                            // isAuthorized is still false 
789
	                        }
790
	
791
	                    }
792
	                    if (nodeListBySubject != null) {
793
	                        // compare node ids to replica node ids
794
	                        outer: for (Replica replica : replicaList) {
795
	                            replicaNodeRef = replica.getReplicaMemberNode();
796
	
797
	                            for (Node node : nodeListBySubject) {
798
	                                if (node.getIdentifier().equals(replicaNodeRef)) {
799
	                                    // node id via session subject matches a replica node
800
	                                    isAuthorized = true;
801
	                                    break outer;
802
	                                }
803
	                            }
804
	                        }
805
	                    }
806
	                }
807
	            }
808
	            
809
	            // if we still aren't authorized, then we are done
810
	            if (!isAuthorized) {
811
	                throw new NotAuthorized("1400", Permission.READ
812
	                        + " not allowed on " + pid.getValue());
813
	            }
814

    
815
	        } catch (RuntimeException e) {
816
	        	e.printStackTrace();
817
	            // convert hazelcast RuntimeException to ServiceFailure
818
	            throw new ServiceFailure("1090", "Unexpected error getting system metadata for: " + 
819
	                pid.getValue());	
820
	        }
821
	        
822
        }
823
        
824
        // It wasn't in the map
825
        if ( systemMetadata == null ) {
826
            String error ="";
827
            String localId = null;
828
            try {
829
                localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
830
              
831
             } catch (Exception e) {
832
                logMetacat.warn("Couldn't find the local id for the pid "+pid.getValue());
833
            }
834
            
835
            if(localId != null && EventLog.getInstance().isDeleted(localId)) {
836
                error = DELETEDMESSAGE;
837
            } else if (localId == null && EventLog.getInstance().isDeleted(pid.getValue())) {
838
                error = DELETEDMESSAGE;
839
            }
840
            throw new NotFound("1420", "No record found for: " + pid.getValue()+". "+error);
841
        }
842
        
843
        return systemMetadata;
844
    }
845
     
846
    
847
    /**
848
     * Test if the specified session represents the authoritative member node for the
849
     * given object specified by the identifier. According the the DataONE documentation, 
850
     * the authoritative member node has all the rights of the *rightsHolder*.
851
     * @param session - the Session object containing the credentials for the Subject
852
     * @param pid - the Identifier of the data object
853
     * @return true if the session represents the authoritative mn.
854
     * @throws ServiceFailure 
855
     * @throws NotImplemented 
856
     */
857
    public boolean isAuthoritativeMNodeAdmin(Session session, Identifier pid) {
858
        boolean allowed = false;
859
        //check the parameters
860
        if(session == null) {
861
            logMetacat.debug("D1NodeService.isAuthoritativeMNodeAdmin - the session object is null and return false.");
862
            return allowed;
863
        } else if (pid == null || pid.getValue() == null || pid.getValue().trim().equals("")) {
864
            logMetacat.debug("D1NodeService.isAuthoritativeMNodeAdmin - the Identifier object is null (not being specified) and return false.");
865
            return allowed;
866
        }
867
        
868
        //Get the subject from the session
869
        Subject subject = session.getSubject();
870
        if(subject != null) {
871
            //Get the authoritative member node info from the system metadata
872
            SystemMetadata sysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
873
            if(sysMeta != null) {
874
                NodeReference authoritativeMNode = sysMeta.getAuthoritativeMemberNode();
875
                if(authoritativeMNode != null) {
876
                        CNode cn = null;
877
                        try {
878
                            cn = D1Client.getCN();
879
                        } catch (BaseException e) {
880
                            logMetacat.error("D1NodeService.isAuthoritativeMNodeAdmin - couldn't connect to the CN since "+
881
                                            e.getDescription()+ ". The false value will be returned for the AuthoritativeMNodeAdmin.");
882
                            return allowed;
883
                        }
884
                        
885
                        if(cn != null) {
886
                            List<Node> nodes = null;
887
                            try {
888
                                nodes = cn.listNodes().getNodeList();
889
                            } catch (NotImplemented e) {
890
                                logMetacat.error("D1NodeService.isAuthoritativeMNodeAdmin - couldn't get the member nodes list from the CN since "+e.getDescription()+ 
891
                                                ". The false value will be returned for the AuthoritativeMNodeAdmin.");
892
                                return allowed;
893
                            } catch (ServiceFailure ee) {
894
                                logMetacat.error("D1NodeService.isAuthoritativeMNodeAdmin - couldn't get the member nodes list from the CN since "+ee.getDescription()+ 
895
                                                ". The false value will be returned for the AuthoritativeMNodeAdmin.");
896
                                return allowed;
897
                            }
898
                            if(nodes != null) {
899
                                for(Node node : nodes) {
900
                                    //find the authoritative node and get its subjects
901
                                    if (node.getType() == NodeType.MN && node.getIdentifier() != null && node.getIdentifier().equals(authoritativeMNode)) {
902
                                        List<Subject> nodeSubjects = node.getSubjectList();
903
                                        if(nodeSubjects != null) {
904
                                            // check if the session subject is in the node subject list
905
                                            for (Subject nodeSubject : nodeSubjects) {
906
                                                logMetacat.debug("D1NodeService.isAuthoritativeMNodeAdmin(), comparing subjects: " +
907
                                                    nodeSubject.getValue() + " and " + subject.getValue());
908
                                                if ( nodeSubject != null && nodeSubject.equals(subject) ) {
909
                                                    allowed = true; // subject of session == target node subject
910
                                                    break;
911
                                                }
912
                                            }              
913
                                        }
914
                                      
915
                                    }
916
                                }
917
                            }
918
                        }
919
                }
920
            }
921
        }
922
        return allowed;
923
    }
924
    
925
    
926
  /**
927
   * Test if the user identified by the provided token has administrative authorization 
928
   * 
929
   * @param session - the Session object containing the credentials for the Subject
930
   * 
931
   * @return true if the user is admin (mn itself or a cn )
932
   * 
933
   * @throws ServiceFailure
934
   * @throws InvalidToken
935
   * @throws NotFound
936
   * @throws NotAuthorized
937
   * @throws NotImplemented
938
   */
939
  public boolean isAdminAuthorized(Session session) 
940
      throws ServiceFailure, InvalidToken, NotAuthorized,
941
      NotImplemented {
942

    
943
      boolean allowed = false;
944
      
945
      // must have a session in order to check admin 
946
      if (session == null) {
947
         logMetacat.debug("In isAdminAuthorized(), session is null ");
948
         return false;
949
      }
950
      
951
      logMetacat.debug("In isAdminAuthorized(), checking CN or MN authorization for " +
952
           session.getSubject().getValue());
953
      
954
      // check if this is the node calling itself (MN)
955
      allowed = isNodeAdmin(session);
956
      
957
      // check the CN list
958
      if (!allowed) {
959
	      allowed = isCNAdmin(session);
960
      }
961
      
962
      return allowed;
963
  }
964
  
965
  /*
966
   * Determine if the specified session is a CN or not. Return true if it is; otherwise false.
967
   */
968
  protected boolean isCNAdmin (Session session) {
969
      boolean allowed = false;
970
      List<Node> nodes = null;
971

    
972
      try {
973
          // are we allowed to do this? only CNs are allowed
974
          CNode cn = D1Client.getCN();
975
          nodes = cn.listNodes().getNodeList();
976
      }
977
      catch (Throwable e) {
978
          logMetacat.warn(e.getMessage());
979
          return false;  
980
      }
981
          
982
      if ( nodes == null ) {
983
          return false;
984
          //throw new ServiceFailure("4852", "Couldn't get node list.");
985
      }
986
      
987
      // find the node in the node list
988
      for ( Node node : nodes ) {
989
          
990
          NodeReference nodeReference = node.getIdentifier();
991
          logMetacat.debug("In isAdminAuthorized(), Node reference is: " + nodeReference.getValue());
992
          
993
          Subject subject = session.getSubject();
994
          
995
          if (node.getType() == NodeType.CN) {
996
              List<Subject> nodeSubjects = node.getSubjectList();
997
              
998
              // check if the session subject is in the node subject list
999
              for (Subject nodeSubject : nodeSubjects) {
1000
                  logMetacat.debug("In isAdminAuthorized(), comparing subjects: " +
1001
                      nodeSubject.getValue() + " and " + subject.getValue());
1002
                  if ( nodeSubject.equals(subject) ) {
1003
                      allowed = true; // subject of session == target node subject
1004
                      break;
1005
                      
1006
                  }
1007
              }              
1008
          }
1009
      }
1010
      return allowed;
1011
  }
1012
  
1013
  /**
1014
   * Test if the user identified by the provided token has administrative authorization 
1015
   * on this node because they are calling themselves
1016
   * 
1017
   * @param session - the Session object containing the credentials for the Subject
1018
   * 
1019
   * @return true if the user is this node
1020
   * @throws ServiceFailure 
1021
   * @throws NotImplemented 
1022
   */
1023
  public boolean isNodeAdmin(Session session) throws NotImplemented, ServiceFailure {
1024

    
1025
      boolean allowed = false;
1026
      
1027
      // must have a session in order to check admin 
1028
      if (session == null) {
1029
         logMetacat.debug("In isNodeAdmin(), session is null ");
1030
         return false;
1031
      }
1032
      
1033
      logMetacat.debug("In isNodeAdmin(), MN authorization for " +
1034
           session.getSubject().getValue());
1035
      
1036
      Node node = MNodeService.getInstance(request).getCapabilities();
1037
      NodeReference nodeReference = node.getIdentifier();
1038
      logMetacat.debug("In isNodeAdmin(), Node reference is: " + nodeReference.getValue());
1039
      
1040
      Subject subject = session.getSubject();
1041
      
1042
      if (node.getType() == NodeType.MN) {
1043
          List<Subject> nodeSubjects = node.getSubjectList();
1044
          
1045
          // check if the session subject is in the node subject list
1046
          for (Subject nodeSubject : nodeSubjects) {
1047
              logMetacat.debug("In isNodeAdmin(), comparing subjects: " +
1048
                  nodeSubject.getValue() + " and " + subject.getValue());
1049
              if ( nodeSubject.equals(subject) ) {
1050
                  allowed = true; // subject of session == this node's subect
1051
                  break;
1052
              }
1053
          }              
1054
      }
1055
      
1056
      return allowed;
1057
  }
1058
  
1059
  /**
1060
   * Test if the user identified by the provided token has authorization 
1061
   * for the operation on the specified object.
1062
   * Allowed subjects include:
1063
   * 1. CNs
1064
   * 2. Authoritative node
1065
   * 3. Owner of the object
1066
   * 4. Users with the specified permission in the access rules.
1067
   * 
1068
   * @param session - the Session object containing the credentials for the Subject
1069
   * @param pid - The identifer of the resource for which access is being checked
1070
   * @param operation - The type of operation which is being requested for the given pid
1071
   *
1072
   * @return true if the operation is allowed
1073
   * 
1074
   * @throws ServiceFailure
1075
   * @throws InvalidToken
1076
   * @throws NotFound
1077
   * @throws NotAuthorized
1078
   * @throws NotImplemented
1079
   * @throws InvalidRequest
1080
   */
1081
  public boolean isAuthorized(Session session, Identifier pid, Permission permission)
1082
    throws ServiceFailure, InvalidToken, NotFound, NotAuthorized,
1083
    NotImplemented, InvalidRequest {
1084

    
1085
    boolean allowed = false;
1086
    
1087
    if (permission == null) {
1088
    	throw new InvalidRequest("1761", "Permission was not provided or is invalid");
1089
    }
1090
    
1091
    // always allow CN access
1092
    if ( isAdminAuthorized(session) ) {
1093
        allowed = true;
1094
        return allowed;
1095
        
1096
    }
1097
    
1098
    String serviceFailureCode = "1760";
1099
    Identifier sid = getPIDForSID(pid, serviceFailureCode);
1100
    if(sid != null) {
1101
        pid = sid;
1102
    }
1103
    
1104
    // the authoritative member node of the pid always has the access as well.
1105
    if (isAuthoritativeMNodeAdmin(session, pid)) {
1106
        allowed = true;
1107
        return allowed;
1108
    }
1109
    
1110
    //is it the owner of the object or the access rules allow the user?
1111
    allowed = userHasPermission(session,  pid, permission );
1112
    
1113
    // throw or return?
1114
    if (!allowed) {
1115
     // track the identities we have checked against
1116
      StringBuffer includedSubjects = new StringBuffer();
1117
      Set<Subject> subjects = AuthUtils.authorizedClientSubjects(session);
1118
      for (Subject s: subjects) {
1119
             includedSubjects.append(s.getValue() + "; ");
1120
        }    
1121
      throw new NotAuthorized("1820", permission + " not allowed on " + pid.getValue() + " for subject[s]: " + includedSubjects.toString() );
1122
    }
1123
    
1124
    return allowed;
1125
    
1126
  }
1127
  
1128
  
1129
  /*
1130
   * Determine if a user has the permission to perform the specified permission.
1131
   * 1. Owner can have any permission.
1132
   * 2. Access table allow the user has the permission
1133
   */
1134
  protected boolean userHasPermission(Session userSession, Identifier pid, Permission permission ) throws NotFound{
1135
      boolean allowed = false;
1136
      // permissions are hierarchical
1137
      List<Permission> expandedPermissions = null;
1138
      // get the subject[s] from the session
1139
      //defer to the shared util for recursively compiling the subjects   
1140
      Set<Subject> subjects = AuthUtils.authorizedClientSubjects(userSession);
1141
          
1142
      // get the system metadata
1143
      String pidStr = pid.getValue();
1144
      SystemMetadata systemMetadata = null;
1145
      try {
1146
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1147

    
1148
      } catch (Exception e) {
1149
          // convert Hazelcast RuntimeException to NotFound
1150
          logMetacat.error("An error occurred while getting system metadata for identifier " +
1151
              pid.getValue() + ". The error message was: " + e.getMessage());
1152
          throw new NotFound("1800", "No record found for " + pidStr);
1153
          
1154
      } 
1155
      
1156
      // throw not found if it was not found
1157
      if (systemMetadata == null) {
1158
          String localId = null;
1159
          String error = "No system metadata could be found for given PID: " + pidStr;
1160
          try {
1161
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1162
            
1163
           } catch (Exception e) {
1164
              logMetacat.warn("Couldn't find the local id for the pid "+pidStr);
1165
          }
1166
          
1167
          if(localId != null && EventLog.getInstance().isDeleted(localId)) {
1168
              error = error + ". "+DELETEDMESSAGE;
1169
          } else if (localId == null && EventLog.getInstance().isDeleted(pid.getValue())) {
1170
              error = error + ". "+DELETEDMESSAGE;
1171
          }
1172
          throw new NotFound("1800", error);
1173
      }
1174
          
1175
      // do we own it?
1176
      for (Subject s: subjects) {
1177
        logMetacat.debug("Comparing \t" + 
1178
                         systemMetadata.getRightsHolder().getValue() +
1179
                         " \tagainst \t" + s.getValue());
1180
          //includedSubjects.append(s.getValue() + "; ");
1181
          allowed = systemMetadata.getRightsHolder().equals(s);
1182
          if (allowed) {
1183
              return allowed;
1184
          }
1185
      }    
1186
      
1187
      // otherwise check the access rules
1188
      try {
1189
          List<AccessRule> allows = systemMetadata.getAccessPolicy().getAllowList();
1190
          search: // label break
1191
          for (AccessRule accessRule: allows) {
1192
            for (Subject s: subjects) {
1193
              logMetacat.debug("Checking allow access rule for subject: " + s.getValue());
1194
              if (accessRule.getSubjectList().contains(s)) {
1195
                  logMetacat.debug("Access rule contains subject: " + s.getValue());
1196
                  for (Permission p: accessRule.getPermissionList()) {
1197
                      logMetacat.debug("Checking permission: " + p.xmlValue());
1198
                      expandedPermissions = expandPermissions(p);
1199
                      allowed = expandedPermissions.contains(permission);
1200
                      if (allowed) {
1201
                          logMetacat.info("Permission granted: " + p.xmlValue() + " to " + s.getValue());
1202
                          break search; //label break
1203
                      }
1204
                  }
1205
                  
1206
              }
1207
            }
1208
          }
1209
      } catch (Exception e) {
1210
          // catch all for errors - safe side should be to deny the access
1211
          logMetacat.error("Problem checking authorization - defaulting to deny", e);
1212
          allowed = false;
1213
        
1214
      }
1215
      return allowed;
1216
  }
1217
  /*
1218
   * parse a logEntry and get the relevant field from it
1219
   * 
1220
   * @param fieldname
1221
   * @param entry
1222
   * @return
1223
   */
1224
  private String getLogEntryField(String fieldname, String entry) {
1225
    String begin = "<" + fieldname + ">";
1226
    String end = "</" + fieldname + ">";
1227
    // logMetacat.debug("looking for " + begin + " and " + end +
1228
    // " in entry " + entry);
1229
    String s = entry.substring(entry.indexOf(begin) + begin.length(), entry
1230
        .indexOf(end));
1231
    logMetacat.debug("entry " + fieldname + " : " + s);
1232
    return s;
1233
  }
1234

    
1235
  /** 
1236
   * Determine if a given object should be treated as an XML science metadata
1237
   * object. 
1238
   * 
1239
   * @param sysmeta - the SystemMetadata describing the object
1240
   * @return true if the object should be treated as science metadata
1241
   */
1242
  public static boolean isScienceMetadata(SystemMetadata sysmeta) {
1243
    
1244
    ObjectFormat objectFormat = null;
1245
    boolean isScienceMetadata = false;
1246
    
1247
    try {
1248
      objectFormat = ObjectFormatCache.getInstance().getFormat(sysmeta.getFormatId());
1249
      if ( objectFormat.getFormatType().equals("METADATA") ) {
1250
      	isScienceMetadata = true;
1251
      	
1252
      }
1253
      
1254
       
1255
    } catch (ServiceFailure e) {
1256
      logMetacat.debug("There was a problem determining if the object identified by" + 
1257
          sysmeta.getIdentifier().getValue() + 
1258
          " is science metadata: " + e.getMessage());
1259
    
1260
    } catch (NotFound e) {
1261
      logMetacat.debug("There was a problem determining if the object identified by" + 
1262
          sysmeta.getIdentifier().getValue() + 
1263
          " is science metadata: " + e.getMessage());
1264
    
1265
    }
1266
    
1267
    return isScienceMetadata;
1268

    
1269
  }
1270
  
1271
  /**
1272
   * Check fro whitespace in the given pid.
1273
   * null pids are also invalid by default
1274
   * @param pid
1275
   * @return
1276
   */
1277
  public static boolean isValidIdentifier(Identifier pid) {
1278
	  if (pid != null && pid.getValue() != null && pid.getValue().length() > 0) {
1279
		  return !pid.getValue().matches(".*\\s+.*");
1280
	  } 
1281
	  return false;
1282
  }
1283
  
1284
  
1285
  /**
1286
   * Insert or update an XML document into Metacat
1287
   * 
1288
   * @param xml - the XML document to insert or update
1289
   * @param pid - the identifier to be used for the resulting object
1290
   * 
1291
   * @return localId - the resulting docid of the document created or updated
1292
   * 
1293
   */
1294
  public String insertOrUpdateDocument(InputStream xml, String encoding,  Identifier pid, 
1295
    Session session, String insertOrUpdate) 
1296
    throws ServiceFailure, IOException {
1297
    
1298
  	logMetacat.debug("Starting to insert xml document...");
1299
    IdentifierManager im = IdentifierManager.getInstance();
1300

    
1301
    // generate pid/localId pair for sysmeta
1302
    String localId = null;
1303
    byte[] xmlBytes  = IOUtils.toByteArray(xml);
1304
    String xmlStr = new String(xmlBytes, encoding);
1305
    if(insertOrUpdate.equals("insert")) {
1306
      localId = im.generateLocalId(pid.getValue(), 1);
1307
      
1308
    } else {
1309
      //localid should already exist in the identifier table, so just find it
1310
      try {
1311
        logMetacat.debug("Updating pid " + pid.getValue());
1312
        logMetacat.debug("looking in identifier table for pid " + pid.getValue());
1313
        
1314
        localId = im.getLocalId(pid.getValue());
1315
        
1316
        logMetacat.debug("localId: " + localId);
1317
        //increment the revision
1318
        String docid = localId.substring(0, localId.lastIndexOf("."));
1319
        String revS = localId.substring(localId.lastIndexOf(".") + 1, localId.length());
1320
        int rev = new Integer(revS).intValue();
1321
        rev++;
1322
        docid = docid + "." + rev;
1323
        localId = docid;
1324
        logMetacat.debug("incremented localId: " + localId);
1325
      
1326
      } catch(McdbDocNotFoundException e) {
1327
        throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument(): " +
1328
            "pid " + pid.getValue() + 
1329
            " should have been in the identifier table, but it wasn't: " + 
1330
            e.getMessage());
1331
      
1332
      } catch (SQLException e) {
1333
          throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument() -"+
1334
                     " couldn't identify if the pid "+pid.getValue()+" is in the identifier table since "+e.getMessage());
1335
      }
1336
      
1337
    }
1338

    
1339
    params = new Hashtable<String, String[]>();
1340
    String[] action = new String[1];
1341
    action[0] = insertOrUpdate;
1342
    params.put("action", action);
1343
    String[] docid = new String[1];
1344
    docid[0] = localId;
1345
    params.put("docid", docid);
1346
    String[] doctext = new String[1];
1347
    doctext[0] = xmlStr;
1348
    params.put("doctext", doctext);
1349
    
1350
    String username = Constants.SUBJECT_PUBLIC;
1351
    String[] groupnames = null;
1352
    if (session != null ) {
1353
    	username = session.getSubject().getValue();
1354
    	if (session.getSubjectInfo() != null) {
1355
    		List<Group> groupList = session.getSubjectInfo().getGroupList();
1356
    		if (groupList != null) {
1357
    			groupnames = new String[groupList.size()];
1358
    			for (int i = 0; i < groupList.size(); i++ ) {
1359
    				groupnames[i] = groupList.get(i).getGroupName();
1360
    			}
1361
    		}
1362
    	}
1363
    }
1364
    
1365
    // do the insert or update action
1366
    handler = new MetacatHandler(new Timer());
1367
    String result = handler.handleInsertOrUpdateAction(request.getRemoteAddr(), request.getHeader("User-Agent"), null, 
1368
                        null, params, username, groupnames, false, false, xmlBytes);
1369
    
1370
    if(result.indexOf("<error>") != -1) {
1371
    	String detailCode = "";
1372
    	if ( insertOrUpdate.equals("insert") ) {
1373
    		// make sure to remove the mapping so that subsequent attempts do not fail with IdentifierNotUnique
1374
    		im.removeMapping(pid.getValue(), localId);
1375
    		detailCode = "1190";
1376
    		
1377
    	} else if ( insertOrUpdate.equals("update") ) {
1378
    		detailCode = "1310";
1379
    		
1380
    	}
1381
        throw new ServiceFailure(detailCode, 
1382
          "Error inserting or updating document: " + result);
1383
    }
1384
    logMetacat.debug("Finsished inserting xml document with id " + localId);
1385
    
1386
    return localId;
1387
  }
1388
  
1389
  /**
1390
   * Insert a data document
1391
   * 
1392
   * @param object
1393
   * @param pid
1394
   * @param sessionData
1395
   * @throws ServiceFailure
1396
   * @returns localId of the data object inserted
1397
   */
1398
  public String insertDataObject(InputStream object, Identifier pid, 
1399
          Session session) throws ServiceFailure {
1400
      
1401
    String username = Constants.SUBJECT_PUBLIC;
1402
    String[] groupnames = null;
1403
    if (session != null ) {
1404
    	username = session.getSubject().getValue();
1405
    	if (session.getSubjectInfo() != null) {
1406
    		List<Group> groupList = session.getSubjectInfo().getGroupList();
1407
    		if (groupList != null) {
1408
    			groupnames = new String[groupList.size()];
1409
    			for (int i = 0; i < groupList.size(); i++ ) {
1410
    				groupnames[i] = groupList.get(i).getGroupName();
1411
    			}
1412
    		}
1413
    	}
1414
    }
1415
  
1416
    // generate pid/localId pair for object
1417
    logMetacat.debug("Generating a pid/localId mapping");
1418
    IdentifierManager im = IdentifierManager.getInstance();
1419
    String localId = im.generateLocalId(pid.getValue(), 1);
1420
  
1421
    // Save the data file to disk using "localId" as the name
1422
    String datafilepath = null;
1423
	try {
1424
		datafilepath = PropertyService.getProperty("application.datafilepath");
1425
	} catch (PropertyNotFoundException e) {
1426
		ServiceFailure sf = new ServiceFailure("1190", "Lookup data file path" + e.getMessage());
1427
		sf.initCause(e);
1428
		throw sf;
1429
	}
1430
    boolean locked = false;
1431
	try {
1432
		locked = DocumentImpl.getDataFileLockGrant(localId);
1433
	} catch (Exception e) {
1434
		ServiceFailure sf = new ServiceFailure("1190", "Could not lock file for writing:" + e.getMessage());
1435
		sf.initCause(e);
1436
		throw sf;
1437
	}
1438

    
1439
    logMetacat.debug("Case DATA: starting to write to disk.");
1440
	if (locked) {
1441

    
1442
          File dataDirectory = new File(datafilepath);
1443
          dataDirectory.mkdirs();
1444
  
1445
          File newFile = writeStreamToFile(dataDirectory, localId, object);
1446
  
1447
          // TODO: Check that the file size matches SystemMetadata
1448
          // long size = newFile.length();
1449
          // if (size == 0) {
1450
          //     throw new IOException("Uploaded file is 0 bytes!");
1451
          // }
1452
  
1453
          // Register the file in the database (which generates an exception
1454
          // if the localId is not acceptable or other untoward things happen
1455
          try {
1456
            logMetacat.debug("Registering document...");
1457
            DocumentImpl.registerDocument(localId, "BIN", localId,
1458
                    username, groupnames);
1459
            logMetacat.debug("Registration step completed.");
1460
            
1461
          } catch (SQLException e) {
1462
            //newFile.delete();
1463
            logMetacat.debug("SQLE: " + e.getMessage());
1464
            e.printStackTrace(System.out);
1465
            throw new ServiceFailure("1190", "Registration failed: " + 
1466
            		e.getMessage());
1467
            
1468
          } catch (AccessionNumberException e) {
1469
            //newFile.delete();
1470
            logMetacat.debug("ANE: " + e.getMessage());
1471
            e.printStackTrace(System.out);
1472
            throw new ServiceFailure("1190", "Registration failed: " + 
1473
            	e.getMessage());
1474
            
1475
          } catch (Exception e) {
1476
            //newFile.delete();
1477
            logMetacat.debug("Exception: " + e.getMessage());
1478
            e.printStackTrace(System.out);
1479
            throw new ServiceFailure("1190", "Registration failed: " + 
1480
            	e.getMessage());
1481
          }
1482
  
1483
          logMetacat.debug("Logging the creation event.");
1484
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, "create");
1485
  
1486
          // Schedule replication for this data file, the "insert" action is important here!
1487
          logMetacat.debug("Scheduling replication.");
1488
          ForceReplicationHandler frh = new ForceReplicationHandler(localId, "insert", false, null);
1489
      }
1490
      
1491
      return localId;
1492
    
1493
  }
1494

    
1495
  /**
1496
   * Insert a systemMetadata document and return its localId
1497
   */
1498
  public void insertSystemMetadata(SystemMetadata sysmeta) 
1499
      throws ServiceFailure {
1500
      
1501
  	  logMetacat.debug("Starting to insert SystemMetadata...");
1502
      sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
1503
      logMetacat.debug("Inserting new system metadata with modified date " + 
1504
          sysmeta.getDateSysMetadataModified());
1505
      
1506
      //insert the system metadata
1507
      try {
1508
        // note: the calling subclass handles the map hazelcast lock/unlock
1509
      	HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
1510
      	// submit for indexing
1511
        MetacatSolrIndex.getInstance().submit(sysmeta.getIdentifier(), sysmeta, null, true);
1512
      } catch (Exception e) {
1513
          throw new ServiceFailure("1190", e.getMessage());
1514
          
1515
	    }  
1516
  }
1517
  
1518
  /**
1519
   * Retrieve the list of objects present on the MN that match the calling parameters
1520
   * 
1521
   * @param session - the Session object containing the credentials for the Subject
1522
   * @param startTime - Specifies the beginning of the time range from which 
1523
   *                    to return object (>=)
1524
   * @param endTime - Specifies the beginning of the time range from which 
1525
   *                  to return object (>=)
1526
   * @param objectFormat - Restrict results to the specified object format
1527
   * @param replicaStatus - Indicates if replicated objects should be returned in the list
1528
   * @param start - The zero-based index of the first value, relative to the 
1529
   *                first record of the resultset that matches the parameters.
1530
   * @param count - The maximum number of entries that should be returned in 
1531
   *                the response. The Member Node may return less entries 
1532
   *                than specified in this value.
1533
   * 
1534
   * @return objectList - the list of objects matching the criteria
1535
   * 
1536
   * @throws InvalidToken
1537
   * @throws ServiceFailure
1538
   * @throws NotAuthorized
1539
   * @throws InvalidRequest
1540
   * @throws NotImplemented
1541
   */
1542
  public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Identifier identifier, NodeReference nodeId, Integer start,
1543
          Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
1544

    
1545
      ObjectList objectList = null;
1546

    
1547
      try {
1548
          // safeguard against large requests
1549
          if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
1550
              count = MAXIMUM_DB_RECORD_COUNT;
1551
          }
1552
          boolean isSid = false;
1553
          if(identifier != null) {
1554
              isSid = IdentifierManager.getInstance().systemMetadataSIDExists(identifier);
1555
          }
1556
          objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, nodeId, start, count, identifier, isSid);
1557
      } catch (Exception e) {
1558
          throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1559
      }
1560

    
1561
      return objectList;
1562
  }
1563

    
1564
  /**
1565
   * Update a systemMetadata document
1566
   * 
1567
   * @param sysMeta - the system metadata object in the system to update
1568
   */
1569
    protected void updateSystemMetadata(SystemMetadata sysMeta)
1570
        throws ServiceFailure {
1571

    
1572
        logMetacat.debug("D1NodeService.updateSystemMetadata() called.");
1573
        sysMeta.setDateSysMetadataModified(new Date());
1574
        try {
1575
            HazelcastService.getInstance().getSystemMetadataMap().lock(sysMeta.getIdentifier());
1576
            HazelcastService.getInstance().getSystemMetadataMap().put(sysMeta.getIdentifier(), sysMeta);
1577
            // submit for indexing
1578
            MetacatSolrIndex.getInstance().submit(sysMeta.getIdentifier(), sysMeta, null, true);
1579
        } catch (Exception e) {
1580
            throw new ServiceFailure("4862", e.getMessage());
1581

    
1582
        } finally {
1583
            HazelcastService.getInstance().getSystemMetadataMap().unlock(sysMeta.getIdentifier());
1584

    
1585
        }
1586

    
1587
    }
1588
    
1589
    /**
1590
     * Update the system metadata of the specified pid
1591
     * @param session - the identity of the client which calls the method
1592
     * @param pid - the identifier of the object which will be updated
1593
     * @param sysmeta - the new system metadata  
1594
     * @return
1595
     * @throws NotImplemented
1596
     * @throws NotAuthorized
1597
     * @throws ServiceFailure
1598
     * @throws InvalidRequest
1599
     * @throws InvalidSystemMetadata
1600
     * @throws InvalidToken
1601
     */
1602
	public boolean updateSystemMetadata(Session session, Identifier pid,
1603
			SystemMetadata sysmeta) throws NotImplemented, NotAuthorized,
1604
			ServiceFailure, InvalidRequest, InvalidSystemMetadata, InvalidToken {
1605
		
1606
		// The lock to be used for this identifier
1607
      Lock lock = null;
1608
     
1609
      // verify that guid == SystemMetadata.getIdentifier()
1610
      logMetacat.debug("Comparing guid|sysmeta_guid: " + pid.getValue() + 
1611
          "|" + sysmeta.getIdentifier().getValue());
1612
      
1613
      if (!pid.getValue().equals(sysmeta.getIdentifier().getValue())) {
1614
          throw new InvalidRequest("4863", 
1615
              "The identifier in method call (" + pid.getValue() + 
1616
              ") does not match identifier in system metadata (" +
1617
              sysmeta.getIdentifier().getValue() + ").");
1618
      }
1619
      
1620
      //check the sid
1621
      SystemMetadata currentSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1622
      if(currentSysmeta == null ) {
1623
          //do we need throw an exception
1624
      } else {
1625
          Identifier currentSid = currentSysmeta.getSeriesId();
1626
          if(currentSid != null) {
1627
              //new sid must match the current sid
1628
              Identifier newSid = sysmeta.getSeriesId();
1629
              if (!isValidIdentifier(newSid)) {
1630
                  throw new InvalidSystemMetadata("4956", "The series id in the system metadata is invalid in the request.");
1631
              } else {
1632
                  if(!newSid.getValue().equals(currentSid.getValue())) {
1633
                      throw new InvalidSystemMetadata("4956", "The series id "+newSid.getValue() +" in the system metadata doesn't match the current sid "+currentSid.getValue());
1634
                  }
1635
              }
1636
          } else {
1637
              //current system metadata doesn't have a sid. So we can have those scenarios
1638
              //1. The new sid may be null as well
1639
              //2. If the new sid does exist, it may be an identifier which hasn't bee used.
1640
              //3. If the new sid does exist, it may be an sid which equals the SID it obsoletes
1641
              //4. If the new sid does exist, it may be an sid which equauls the SID it was obsoleted by
1642
              Identifier newSid = sysmeta.getSeriesId();
1643
              if(newSid != null) {
1644
                  //It matches the rules of the checkSidInModifyingSystemMetadata
1645
                  checkSidInModifyingSystemMetadata(sysmeta, "4956", "4868");
1646
              }
1647
          }
1648
      }
1649
      checkModifiedImmutableFields(currentSysmeta, sysmeta);
1650
      // do the actual update
1651
      this.updateSystemMetadata(sysmeta);
1652
      
1653
      try {
1654
    	  String localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1655
    	  EventLog.getInstance().log(request.getRemoteAddr(), 
1656
    	          request.getHeader("User-Agent"), session.getSubject().getValue(), 
1657
    	          localId, "updateSystemMetadata");
1658
      } catch (McdbDocNotFoundException e) {
1659
    	  // do nothing, no localId to log with
1660
    	  logMetacat.warn("Could not log 'updateSystemMetadata' event because no localId was found for pid: " + pid.getValue());
1661
      } catch (SQLException e) {
1662
          logMetacat.warn("Could not log 'updateSystemMetadata' event because the localId couldn't be identified for the pid: " + pid.getValue());
1663
      }
1664
      return true;
1665
	}
1666
	
1667
	
1668
	/*
1669
	 * Check if the newMeta modifies an immutable field. 
1670
	 */
1671
	private void checkModifiedImmutableFields(SystemMetadata orgMeta, SystemMetadata newMeta) throws InvalidRequest, InvalidSystemMetadata{
1672
	    if(orgMeta != null && newMeta != null) {
1673
	        if(newMeta.getIdentifier() == null) {
1674
	            throw new InvalidSystemMetadata("4956", "The new version of the system metadata is invalid since the identifier is null");
1675
	        }
1676
	        if(!orgMeta.getIdentifier().equals(newMeta.getIdentifier())) {
1677
	            throw new InvalidRequest("4869","The request is trying to modify an immutable field in the SystemMeta: the new system meta's identifier "+newMeta.getIdentifier().getValue()+" is "+
1678
	                  "different to the orginal one "+orgMeta.getIdentifier().getValue());
1679
	        }
1680
	        if(newMeta.getSize() == null) {
1681
	            throw new InvalidSystemMetadata("4956", "The new version of the system metadata is invalid since the size is null");
1682
	        }
1683
	        if(!orgMeta.getSize().equals(newMeta.getSize())) {
1684
	            throw new InvalidRequest("4869", "The request is trying to modify an immutable field in the SystemMeta: the new system meta's size "+newMeta.getSize().longValue()+" is "+
1685
	                      "different to the orginal one "+orgMeta.getSize().longValue());
1686
	        }
1687
	        if(newMeta.getChecksum()!= null && orgMeta.getChecksum() != null && !orgMeta.getChecksum().getValue().equals(newMeta.getChecksum().getValue())) {
1688
	            logMetacat.error("The request is trying to modify an immutable field in the SystemMeta: the new system meta's checksum "+newMeta.getChecksum().getValue()+" is "+
1689
                        "different to the orginal one "+orgMeta.getChecksum().getValue());
1690
	            throw new InvalidRequest("4869", "The request is trying to modify an immutable field in the SystemMeta: the new system meta's checksum "+newMeta.getChecksum().getValue()+" is "+
1691
                        "different to the orginal one "+orgMeta.getChecksum().getValue());
1692
	        }
1693
	        if(orgMeta.getSubmitter() != null && newMeta.getSubmitter() != null && !orgMeta.getSubmitter().equals(newMeta.getSubmitter())) {
1694
	            throw new InvalidRequest("4869", "The request is trying to modify an immutable field in the SystemMeta: the new system meta's submitter "+newMeta.getSubmitter().getValue()+" is "+
1695
                        "different to the orginal one "+orgMeta.getSubmitter().getValue());
1696
	        }
1697
	        
1698
	        if(orgMeta.getDateUploaded() != null && newMeta.getDateUploaded() != null && !orgMeta.getDateUploaded().equals(newMeta.getDateUploaded())) {
1699
	            throw new InvalidRequest("4869", "The request is trying to modify an immutable field in the SystemMeta: the new system meta's date of uploaded "+newMeta.getDateUploaded()+" is "+
1700
                        "different to the orginal one "+orgMeta.getDateUploaded());
1701
	        }
1702
	        
1703
	        if(orgMeta.getOriginMemberNode() != null && newMeta.getOriginMemberNode() != null && !orgMeta.getOriginMemberNode().equals(newMeta.getOriginMemberNode())) {
1704
	            throw new InvalidRequest("4869", "The request is trying to modify an immutable field in the SystemMeta: the new system meta's orginal member node  "+newMeta.getOriginMemberNode().getValue()+" is "+
1705
                        "different to the orginal one "+orgMeta.getOriginMemberNode().getValue());
1706
	        }
1707
	        
1708
	        if(orgMeta.getSeriesId() != null && newMeta.getSeriesId() != null && !orgMeta.getSeriesId().equals(newMeta.getSeriesId())) {
1709
                throw new InvalidRequest("4869", "The request is trying to modify an immutable field in the SystemMeta: the new system meta's series id  "+newMeta.getSeriesId().getValue()+" is "+
1710
                        "different to the orginal one "+orgMeta.getSeriesId().getValue());
1711
            }
1712
	        
1713
	    }
1714
	}
1715
  
1716
  /**
1717
   * Given a Permission, returns a list of all permissions that it encompasses
1718
   * Permissions are hierarchical so that WRITE also allows READ.
1719
   * @param permission
1720
   * @return list of included Permissions for the given permission
1721
   */
1722
  protected List<Permission> expandPermissions(Permission permission) {
1723
	  	List<Permission> expandedPermissions = new ArrayList<Permission>();
1724
	    if (permission.equals(Permission.READ)) {
1725
	    	expandedPermissions.add(Permission.READ);
1726
	    }
1727
	    if (permission.equals(Permission.WRITE)) {
1728
	    	expandedPermissions.add(Permission.READ);
1729
	    	expandedPermissions.add(Permission.WRITE);
1730
	    }
1731
	    if (permission.equals(Permission.CHANGE_PERMISSION)) {
1732
	    	expandedPermissions.add(Permission.READ);
1733
	    	expandedPermissions.add(Permission.WRITE);
1734
	    	expandedPermissions.add(Permission.CHANGE_PERMISSION);
1735
	    }
1736
	    return expandedPermissions;
1737
  }
1738

    
1739
  /*
1740
   * Write a stream to a file
1741
   * 
1742
   * @param dir - the directory to write to
1743
   * @param fileName - the file name to write to
1744
   * @param data - the object bytes as an input stream
1745
   * 
1746
   * @return newFile - the new file created
1747
   * 
1748
   * @throws ServiceFailure
1749
   */
1750
  private File writeStreamToFile(File dir, String fileName, InputStream data) 
1751
    throws ServiceFailure {
1752
    
1753
    File newFile = new File(dir, fileName);
1754
    logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
1755

    
1756
    try {
1757
        if (newFile.createNewFile()) {
1758
          // write data stream to desired file
1759
          OutputStream os = new FileOutputStream(newFile);
1760
          long length = IOUtils.copyLarge(data, os);
1761
          os.flush();
1762
          os.close();
1763
        } else {
1764
          logMetacat.debug("File creation failed, or file already exists.");
1765
          throw new ServiceFailure("1190", "File already exists: " + fileName);
1766
        }
1767
    } catch (FileNotFoundException e) {
1768
      logMetacat.debug("FNF: " + e.getMessage());
1769
      throw new ServiceFailure("1190", "File not found: " + fileName + " " 
1770
                + e.getMessage());
1771
    } catch (IOException e) {
1772
      logMetacat.debug("IOE: " + e.getMessage());
1773
      throw new ServiceFailure("1190", "File was not written: " + fileName 
1774
                + " " + e.getMessage());
1775
    }
1776

    
1777
    return newFile;
1778
  }
1779

    
1780
  /*
1781
   * Returns a list of nodes that have been registered with the DataONE infrastructure
1782
   * that match the given session subject
1783
   * @return nodes - List of nodes from the registry with a matching session subject
1784
   * 
1785
   * @throws ServiceFailure
1786
   * @throws NotImplemented
1787
   */
1788
  protected List<Node> listNodesBySubject(Subject subject) 
1789
      throws ServiceFailure, NotImplemented {
1790
      List<Node> nodeList = new ArrayList<Node>();
1791
      
1792
      CNode cn = D1Client.getCN();
1793
      List<Node> nodes = cn.listNodes().getNodeList();
1794
      
1795
      // find the node in the node list
1796
      for ( Node node : nodes ) {
1797
          
1798
          List<Subject> nodeSubjects = node.getSubjectList();
1799
          if (nodeSubjects != null) {    
1800
	          // check if the session subject is in the node subject list
1801
	          for (Subject nodeSubject : nodeSubjects) {
1802
	              if ( nodeSubject.equals(subject) ) { // subject of session == node subject
1803
	                  nodeList.add(node);  
1804
	              }                              
1805
	          }
1806
          }
1807
      }
1808
      
1809
      return nodeList;
1810
      
1811
  }
1812

    
1813
  /**
1814
   * Archives an object, where the object is either a 
1815
   * data object or a science metadata object.
1816
   * 
1817
   * @param session - the Session object containing the credentials for the Subject
1818
   * @param pid - The object identifier to be archived
1819
   * 
1820
   * @return pid - the identifier of the object used for the archiving
1821
   * 
1822
   * @throws InvalidToken
1823
   * @throws ServiceFailure
1824
   * @throws NotAuthorized
1825
   * @throws NotFound
1826
   * @throws NotImplemented
1827
   * @throws InvalidRequest
1828
   */
1829
  public Identifier archive(Session session, Identifier pid) 
1830
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1831

    
1832
      String localId = null;
1833
      boolean allowed = false;
1834
      String username = Constants.SUBJECT_PUBLIC;
1835
      String[] groupnames = null;
1836
      if (session == null) {
1837
      	throw new InvalidToken("1330", "No session has been provided");
1838
      } else {
1839
          username = session.getSubject().getValue();
1840
          if (session.getSubjectInfo() != null) {
1841
              List<Group> groupList = session.getSubjectInfo().getGroupList();
1842
              if (groupList != null) {
1843
                  groupnames = new String[groupList.size()];
1844
                  for (int i = 0; i < groupList.size(); i++) {
1845
                      groupnames[i] = groupList.get(i).getGroupName();
1846
                  }
1847
              }
1848
          }
1849
      }
1850

    
1851
      // do we have a valid pid?
1852
      if (pid == null || pid.getValue().trim().equals("")) {
1853
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
1854
      }
1855
      
1856
      String serviceFailureCode = "1350";
1857
      Identifier sid = getPIDForSID(pid, serviceFailureCode);
1858
      if(sid != null) {
1859
          pid = sid;
1860
      }
1861

    
1862
      // check for the existing identifier
1863
      try {
1864
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1865
      } catch (McdbDocNotFoundException e) {
1866
          throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
1867
      } catch (SQLException e) {
1868
          throw new ServiceFailure("1350", "The object with the provided identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
1869
      }
1870

    
1871
      // does the subject have archive (a D1 CHANGE_PERMISSION level) privileges on the pid?
1872
      try {
1873
			allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
1874
		} catch (InvalidRequest e) {
1875
          throw new ServiceFailure("1350", e.getDescription());
1876
		}
1877
          
1878

    
1879
      if (allowed) {
1880
          try {
1881
              // archive the document
1882
              DocumentImpl.delete(localId, null, null, null, false);
1883
              EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
1884

    
1885
              // archive it
1886
              SystemMetadata sysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1887
              sysMeta.setArchived(true);
1888
              sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
1889
              HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
1890
              // submit for indexing
1891
              // DocumentImpl call above should do this.
1892
              // see: https://projects.ecoinformatics.org/ecoinfo/issues/6030
1893
              //HazelcastService.getInstance().getIndexQueue().add(sysMeta);
1894
              
1895
          } catch (McdbDocNotFoundException e) {
1896
              throw new NotFound("1340", "The provided identifier was invalid.");
1897

    
1898
          } catch (SQLException e) {
1899
              throw new ServiceFailure("1350", "There was a problem archiving the object." + "The error message was: " + e.getMessage());
1900

    
1901
          } catch (InsufficientKarmaException e) {
1902
              throw new NotAuthorized("1320", "The provided identity does not have " + "permission to archive this object.");
1903

    
1904
          } catch (Exception e) { // for some reason DocumentImpl throws a general Exception
1905
              throw new ServiceFailure("1350", "There was a problem archiving the object." + "The error message was: " + e.getMessage());
1906
          }
1907

    
1908
      } else {
1909
          throw new NotAuthorized("1320", "The provided identity does not have " + "permission to archive the object on the Node.");
1910
      }
1911

    
1912
      return pid;
1913
  }
1914
  
1915
  
1916
  /**
1917
   * A utility method for v1 api to check the specified identifier exists as a pid
1918
   * @param identifier  the specified identifier
1919
   * @param serviceFailureCode  the detail error code for the service failure exception
1920
   * @param noFoundCode  the detail error code for the not found exception
1921
   * @throws ServiceFailure
1922
   * @throws NotFound
1923
   */
1924
  public void checkV1SystemMetaPidExist(Identifier identifier, String serviceFailureCode, String serviceFailureMessage,  
1925
          String noFoundCode, String notFoundMessage) throws ServiceFailure, NotFound {
1926
      boolean exists = false;
1927
      try {
1928
          exists = IdentifierManager.getInstance().systemMetadataPIDExists(identifier);
1929
      } catch (SQLException e) {
1930
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+e.getMessage());
1931
      }
1932
      if(!exists) {
1933
         //the v1 method only handles a pid. so it should throw a not-found exception.
1934
          // check if the pid was deleted.
1935
          try {
1936
              String localId = IdentifierManager.getInstance().getLocalId(identifier.getValue());
1937
              if(EventLog.getInstance().isDeleted(localId)) {
1938
                  notFoundMessage=notFoundMessage+" "+DELETEDMESSAGE;
1939
              } 
1940
            } catch (Exception e) {
1941
              logMetacat.info("Couldn't determine if the not-found identifier "+identifier.getValue()+" was deleted since "+e.getMessage());
1942
            }
1943
            throw new NotFound(noFoundCode, notFoundMessage);
1944
      }
1945
  }
1946
  
1947
  /**
1948
   * Utility method to get the PID for an SID. If the specified identifier is not an SID
1949
   * , null will be returned.
1950
   * @param sid  the specified sid
1951
   * @param serviceFailureCode  the detail error code for the service failure exception
1952
   * @return the pid for the sid. If the specified identifier is not an SID, null will be returned.
1953
   * @throws ServiceFailure
1954
   */
1955
  protected Identifier getPIDForSID(Identifier sid, String serviceFailureCode) throws ServiceFailure {
1956
      Identifier id = null;
1957
      String serviceFailureMessage = "The PID "+" couldn't be identified for the sid " + sid.getValue();
1958
      try {
1959
          //determine if the given pid is a sid or not.
1960
          if(IdentifierManager.getInstance().systemMetadataSIDExists(sid)) {
1961
              try {
1962
                  //set the header pid for the sid if the identifier is a sid.
1963
                  id = IdentifierManager.getInstance().getHeadPID(sid);
1964
              } catch (SQLException sqle) {
1965
                  throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+sqle.getMessage());
1966
              }
1967
              
1968
          }
1969
      } catch (SQLException e) {
1970
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage + " since "+e.getMessage());
1971
      }
1972
      return id;
1973
  }
1974

    
1975
  /*
1976
   * Determine if the sid is legitimate in CN.create and CN.registerSystemMetadata methods. It also is used as a part of rules of the updateSystemMetadata method. Here are the rules:
1977
   * A. If the sysmeta doesn't have an SID, nothing needs to be checked for the SID.
1978
   * B. If the sysmeta does have an SID, it may be an identifier which doesn't exist in the system.
1979
   * C. If the sysmeta does have an SID and it exists as an SID in the system, those scenarios are acceptable:
1980
   *    i. The sysmeta has an obsoletes field, the SID has the same value as the SID of the system metadata of the obsoleting pid.
1981
   *    ii. The sysmeta has an obsoletedBy field, the SID has the same value as the SID of the system metadata of the obsoletedBy pid. 
1982
   */
1983
  protected boolean checkSidInModifyingSystemMetadata(SystemMetadata sysmeta, String invalidSystemMetadataCode, String serviceFailureCode) throws InvalidSystemMetadata, ServiceFailure{
1984
      boolean pass = false;
1985
      if(sysmeta == null) {
1986
          throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The system metadata is null in the request.");
1987
      }
1988
      Identifier sid = sysmeta.getSeriesId();
1989
      if(sid != null) {
1990
          // the series id exists
1991
          if (!isValidIdentifier(sid)) {
1992
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id in the system metadata is invalid in the request.");
1993
          }
1994
          Identifier pid = sysmeta.getIdentifier();
1995
          if (!isValidIdentifier(pid)) {
1996
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The pid in the system metadata is invalid in the request.");
1997
          }
1998
          //the series id equals the pid (new pid hasn't been registered in the system, so IdentifierManager.getInstance().identifierExists method can't exclude this scenario )
1999
          if(sid.getValue().equals(pid.getValue())) {
2000
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
2001
          }
2002
          try {
2003
              if (IdentifierManager.getInstance().identifierExists(sid.getValue())) {
2004
                  //the sid exists in system
2005
                  if(sysmeta.getObsoletes() != null) {
2006
                      SystemMetadata obsoletesSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletes());
2007
                      if(obsoletesSysmeta != null) {
2008
                          Identifier obsoletesSid = obsoletesSysmeta.getSeriesId();
2009
                          if(obsoletesSid != null && obsoletesSid.getValue() != null && !obsoletesSid.getValue().trim().equals("")) {
2010
                              if(sid.getValue().equals(obsoletesSid.getValue())) {
2011
                                  pass = true;// the i of rule C
2012
                              }
2013
                          }
2014
                      } else {
2015
                           logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue()+
2016
                                                                         " which is the value of the obsoletes. So we can't check if the sid " +sid.getValue()+" is legitimate ");
2017
                      }
2018
                  }
2019
                  if(!pass) {
2020
                      // the sid doesn't match the sid of the obsoleting identifier. So we check the obsoletedBy
2021
                      if(sysmeta.getObsoletedBy() != null) {
2022
                          SystemMetadata obsoletedBySysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletedBy());
2023
                          if(obsoletedBySysmeta != null) {
2024
                              Identifier obsoletedBySid = obsoletedBySysmeta.getSeriesId();
2025
                              if(obsoletedBySid != null && obsoletedBySid.getValue() != null && !obsoletedBySid.getValue().trim().equals("")) {
2026
                                  if(sid.getValue().equals(obsoletedBySid.getValue())) {
2027
                                      pass = true;// the ii of the rule C
2028
                                  }
2029
                              }
2030
                          } else {
2031
                              logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue() 
2032
                                                                            +" which is the value of the obsoletedBy. So we can't check if the sid "+sid.getValue()+" is legitimate.");
2033
                          }
2034
                      }
2035
                  }
2036
                  if(!pass) {
2037
                      throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+
2038
                              " in the system metadata exists in the system. And it doesn't match either previous object's sid or the next object's sid.");
2039
                  }
2040
              } else {
2041
                  pass = true; //Rule B
2042
              }
2043
          } catch (SQLException e) {
2044
              throw new ServiceFailure(serviceFailureCode, "Can't determine if the sid in the system metadata is unique or not since "+e.getMessage());
2045
          }
2046
          
2047
      } else {
2048
          //no sid. Rule A.
2049
          pass = true;
2050
      }
2051
      return pass;
2052
      
2053
  }
2054
  
2055
  //@Override
2056
  public OptionList listViews(Session arg0) throws InvalidToken,
2057
          ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented {
2058
      OptionList views = new OptionList();
2059
      views.setKey("views");
2060
      views.setDescription("List of views for objects on the node");
2061
      Vector<String> skinNames = null;
2062
      try {
2063
          skinNames = SkinUtil.getSkinNames();
2064
      } catch (PropertyNotFoundException e) {
2065
          throw new ServiceFailure("2841", e.getMessage());
2066
      }
2067
      for (String skinName: skinNames) {
2068
          views.addOption(skinName);
2069
      }
2070
      return views;
2071
  }
2072
  
2073
  public OptionList listViews() throws InvalidToken,
2074
  ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented { 
2075
      return listViews(null);
2076
  }
2077

    
2078
  //@Override
2079
  public InputStream view(Session session, String format, Identifier pid)
2080
          throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
2081
          NotImplemented, NotFound {
2082
      InputStream resultInputStream = null;
2083
      
2084
      String serviceFailureCode = "2831";
2085
      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2086
      if(sid != null) {
2087
          pid = sid;
2088
      }
2089
      
2090
      SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2091
      InputStream object = this.get(session, pid);
2092

    
2093
      try {
2094
          // can only transform metadata, really
2095
          ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2096
          if (objectFormat.getFormatType().equals("METADATA")) {
2097
              // transform
2098
              DBTransform transformer = new DBTransform();
2099
              String documentContent = IOUtils.toString(object, "UTF-8");
2100
              String sourceType = objectFormat.getFormatId().getValue();
2101
              String targetType = "-//W3C//HTML//EN";
2102
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
2103
              Writer writer = new OutputStreamWriter(baos , "UTF-8");
2104
              // TODO: include more params?
2105
              Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2106
              String localId = null;
2107
              try {
2108
                  localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2109
              } catch (McdbDocNotFoundException e) {
2110
                  throw new NotFound("1020", e.getMessage());
2111
              }
2112
              params.put("qformat", new String[] {format});               
2113
              params.put("docid", new String[] {localId});
2114
              params.put("pid", new String[] {pid.getValue()});
2115
              transformer.transformXMLDocument(
2116
                      documentContent , 
2117
                      sourceType, 
2118
                      targetType , 
2119
                      format, 
2120
                      writer, 
2121
                      params, 
2122
                      null //sessionid
2123
                      );
2124
              
2125
              // finally, get the HTML back
2126
              resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2127
              ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2128
  
2129
          } else {
2130
              // just return the raw bytes
2131
              resultInputStream = object;
2132
          }
2133
      } catch (IOException e) {
2134
          // report as service failure
2135
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2136
          sf.initCause(e);
2137
          throw sf;
2138
      } catch (PropertyNotFoundException e) {
2139
          // report as service failure
2140
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2141
          sf.initCause(e);
2142
          throw sf;
2143
      } catch (SQLException e) {
2144
          // report as service failure
2145
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2146
          sf.initCause(e);
2147
          throw sf;
2148
      } catch (ClassNotFoundException e) {
2149
          // report as service failure
2150
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2151
          sf.initCause(e);
2152
          throw sf;
2153
      }
2154
      
2155
      return resultInputStream;
2156
  }   
2157

    
2158
}
(2-2/7)