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: 2017-04-06 08:54:04 -0700 (Thu, 06 Apr 2017) $'
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.PreparedStatement;
37
import java.sql.ResultSet;
38
import java.sql.SQLException;
39
import java.util.ArrayList;
40
import java.util.Calendar;
41
import java.util.Date;
42
import java.util.Hashtable;
43
import java.util.Iterator;
44
import java.util.List;
45
import java.util.Set;
46
import java.util.Timer;
47
import java.util.Vector;
48
import java.util.concurrent.locks.Lock;
49

    
50
import javax.servlet.http.HttpServletRequest;
51

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

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

    
112
public abstract class D1NodeService {
113
    
114
  public static final String DELETEDMESSAGE = "The object with the PID has been deleted from the node.";
115
    
116
  private static Logger logMetacat = Logger.getLogger(D1NodeService.class);
117

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

    
145
  /**
146
   * Constructor - used to set the metacatUrl from a subclass extending D1NodeService
147
   * 
148
   * @param metacatUrl - the URL of the metacat service, including the ending /d1
149
   */
150
  public D1NodeService(HttpServletRequest request) {
151
		this.request = request;
152
	}
153

    
154
  /**
155
   * retrieve the out-of-band session
156
   * @return
157
   */
158
  	public Session getSession() {
159
		return session;
160
	}
161
  	
162
  	/**
163
  	 * Set the out-of-band session
164
  	 * @param session
165
  	 */
166
	public void setSession(Session session) {
167
		this.session = session;
168
	}
169

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

    
197
    // get system metadata and construct the describe response
198
      SystemMetadata sysmeta = getSystemMetadata(session, pid);
199
      DescribeResponse describeResponse = 
200
      	new DescribeResponse(sysmeta.getFormatId(), sysmeta.getSize(), 
201
      			sysmeta.getDateSysMetadataModified(),
202
      			sysmeta.getChecksum(), sysmeta.getSerialVersion());
203

    
204
      return describeResponse;
205

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

    
234
      // do we have a valid pid?
235
      if (pid == null || pid.getValue().trim().equals("")) {
236
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
237
      }
238

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

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

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

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

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

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

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

    
340
    Identifier resultPid = null;
341
    String localId = null;
342
    boolean allowed = false;
343
    
344
    // check for null session
345
    if (session == null) {
346
    	throw new InvalidToken("4894", "Session is required to WRITE to the Node.");
347
    }
348
    Subject subject = session.getSubject();
349

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

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

    
434
        // save the sysmeta
435
        try {
436
            // lock and unlock of the pid happens in the subclass
437
            HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
438
        
439
        } catch (Exception e) {
440
            logMetacat.error("D1Node.create - There was problem to save the system metadata: " + pid.getValue(), e);
441
            throw new ServiceFailure("1190", "There was problem to save the system metadata: " + pid.getValue()+" since "+e.getMessage());
442
        }
443
        boolean isScienceMetadata = false;
444
      // Science metadata (XML) or science data object?
445
      // TODO: there are cases where certain object formats are science metadata
446
      // but are not XML (netCDF ...).  Handle this.
447
      if ( isScienceMetadata(sysmeta) ) {
448
        isScienceMetadata = true;
449
        // CASE METADATA:
450
      	//String objectAsXML = "";
451
        try {
452
	        //objectAsXML = IOUtils.toString(object, "UTF-8");
453
            String formatId = null;
454
            if(sysmeta.getFormatId() != null)  {
455
                formatId = sysmeta.getFormatId().getValue();
456
            }
457
	        localId = insertOrUpdateDocument(object,"UTF-8", pid, session, "insert", formatId);
458
	        //localId = im.getLocalId(pid.getValue());
459

    
460
        } catch (IOException e) {
461
            removeSystemMetaAndIdentifier(pid);
462
        	String msg = "The Node is unable to create the object "+pid.getValue() +
463
          " There was a problem converting the object to XML";
464
        	logMetacat.error(msg, e);
465
          throw new ServiceFailure("1190", msg + ": " + e.getMessage());
466

    
467
        } catch (ServiceFailure e) {
468
            removeSystemMetaAndIdentifier(pid);
469
            logMetacat.error("D1NodeService.create - the node couldn't create the object "+pid.getValue()+" since "+e.getMessage(), e);
470
            throw e;
471
        } catch (Exception e) {
472
            removeSystemMetaAndIdentifier(pid);
473
            logMetacat.error("The node is unable to create the object: "+pid.getValue()+ " since " + e.getMessage(), e);
474
            throw new ServiceFailure("1190", "The node is unable to create the object: " +pid.getValue()+" since "+ e.getMessage());
475
        }
476
                    
477
      } else {
478
	        
479
	      // DEFAULT CASE: DATA (needs to be checked and completed)
480
          try {
481
              localId = insertDataObject(object, pid, session);
482
          } catch (ServiceFailure e) {
483
              removeSystemMetaAndIdentifier(pid);
484
              throw e;
485
          } catch (Exception e) {
486
              removeSystemMetaAndIdentifier(pid);
487
              throw new ServiceFailure("1190", "The node is unable to create the object "+pid.getValue()+" since " + e.getMessage());
488
          }
489
	      
490
      }   
491
    
492
    //}
493

    
494
    logMetacat.debug("Done inserting new object: " + pid.getValue());
495
    
496
    // setting the resulting identifier failed. We will check if the object does exist.
497
    try {
498
        if (localId == null || !IdentifierManager.getInstance().objectFileExists(localId, isScienceMetadata) ) {
499
            removeSystemMetaAndIdentifier(pid);
500
          throw new ServiceFailure("1190", "The Node is unable to create the object. "+pid.getValue());
501
        }
502
    } catch (PropertyNotFoundException e) {
503
        removeSystemMetaAndIdentifier(pid);
504
        throw new ServiceFailure("1190", "The Node is unable to create the object. "+pid.getValue() + " since "+e.getMessage());
505
    }
506
   
507
    
508
    
509
  
510
    
511
    try {
512
        // submit for indexing
513
        MetacatSolrIndex.getInstance().submit(sysmeta.getIdentifier(), sysmeta, null, true);
514
    } catch (Exception e) {
515
        logMetacat.warn("Couldn't create solr index for object "+pid.getValue());
516
    }
517

    
518
    resultPid = pid;
519
    
520
    logMetacat.info("create() complete for object: " + pid.getValue());
521

    
522
    return resultPid;
523
  }
524
  
525
  /*
526
   * Roll-back method when inserting data object fails.
527
   */
528
  protected void removeSystemMetaAndIdentifier(Identifier id){
529
      if(id != null) {
530
          logMetacat.debug("D1NodeService.removeSystemMeta - the system metadata of object "+id.getValue()+" will removed from both hazelcast and db tables since the object creation failed");
531
          HazelcastService.getInstance().getSystemMetadataMap().remove(id);
532
          logMetacat.info("D1NodeService.removeSystemMeta - the system metadata of object "+id.getValue()+" has been removed from both hazelcast and db tables since the object creation failed");
533
          try {
534
              if(IdentifierManager.getInstance().mappingExists(id.getValue())) {
535
                 String localId = IdentifierManager.getInstance().getLocalId(id.getValue());
536
                 IdentifierManager.getInstance().removeMapping(id.getValue(), localId);
537
                 logMetacat.info("D1NodeService.removeSystemMeta - the identifier "+id.getValue()+" and local id "+localId+" have been removed from the identifier table since the object creation failed");
538
              }
539
          } catch (Exception e) {
540
              logMetacat.warn("D1NodeService.removeSysteMeta - can't decide if the mapping of  the pid "+id.getValue()+" exists on the identifier table.");
541
          }
542
      }
543
  }
544
  
545
  /*
546
   * Roll-back method when inserting data object fails.
547
   */
548
  protected void removeSolrIndex(SystemMetadata sysMeta) {
549
      sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
550
      sysMeta.setArchived(true);
551
      sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
552
      try {
553
          MetacatSolrIndex.getInstance().submit(sysMeta.getIdentifier(), sysMeta, null, false);
554
      } catch (Exception e) {
555
          logMetacat.warn("Can't remove the solr index for pid "+sysMeta.getIdentifier().getValue());
556
      }
557
      
558
  }
559

    
560
  /**
561
   * Return the log records associated with a given event between the start and 
562
   * end dates listed given a particular Subject listed in the Session
563
   * 
564
   * @param session - the Session object containing the credentials for the Subject
565
   * @param fromDate - the start date of the desired log records
566
   * @param toDate - the end date of the desired log records
567
   * @param event - restrict log records of a specific event type
568
   * @param start - zero based offset from the first record in the 
569
   *                set of matching log records. Used to assist with 
570
   *                paging the response.
571
   * @param count - maximum number of log records to return in the response. 
572
   *                Used to assist with paging the response.
573
   * 
574
   * @return the desired log records
575
   * 
576
   * @throws InvalidToken
577
   * @throws ServiceFailure
578
   * @throws NotAuthorized
579
   * @throws InvalidRequest
580
   * @throws NotImplemented
581
   */
582
  public Log getLogRecords(Session session, Date fromDate, Date toDate, 
583
      String event, String pidFilter, Integer start, Integer count) throws InvalidToken, ServiceFailure,
584
      NotAuthorized, InvalidRequest, NotImplemented {
585

    
586
	  // only admin access to this method
587
	  // see https://redmine.dataone.org/issues/2855
588
	  if (!isAdminAuthorized(session)) {
589
		  throw new NotAuthorized("1460", "Only the CN or admin is allowed to harvest logs from this node");
590
	  }
591
    Log log = new Log();
592
    IdentifierManager im = IdentifierManager.getInstance();
593
    EventLog el = EventLog.getInstance();
594
    if ( fromDate == null ) {
595
      logMetacat.debug("setting fromdate from null");
596
      fromDate = new Date(1);
597
    }
598
    if ( toDate == null ) {
599
      logMetacat.debug("setting todate from null");
600
      toDate = new Date();
601
    }
602

    
603
    if ( start == null ) {
604
    	start = 0;	
605
    }
606
    
607
    if ( count == null ) {
608
    	count = 1000;
609
    }
610
    
611
    // safeguard against large requests
612
    if (count > MAXIMUM_DB_RECORD_COUNT) {
613
    	count = MAXIMUM_DB_RECORD_COUNT;
614
    }
615

    
616
    String[] filterDocid = null;
617
    if (pidFilter != null && !pidFilter.trim().equals("")) {
618
        //check if the given identifier is a sid. If it is sid, choose the current pid of the sid.
619
        Identifier pid = new Identifier();
620
        pid.setValue(pidFilter);
621
        String serviceFailureCode = "1490";
622
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
623
        if(sid != null) {
624
            pid = sid;
625
        }
626
        pidFilter = pid.getValue();
627
		try {
628
	      String localId = im.getLocalId(pidFilter);
629
	      filterDocid = new String[] {localId};
630
	    } catch (Exception ex) { 
631
	    	String msg = "Could not find localId for given pidFilter '" + pidFilter + "'";
632
	        logMetacat.warn(msg, ex);
633
	        //throw new InvalidRequest("1480", msg);
634
	        return log; //return 0 record
635
	    }
636
    }
637
    
638
    logMetacat.debug("fromDate: " + fromDate);
639
    logMetacat.debug("toDate: " + toDate);
640

    
641
    log = el.getD1Report(null, null, filterDocid, event,
642
        new java.sql.Timestamp(fromDate.getTime()),
643
        new java.sql.Timestamp(toDate.getTime()), false, start, count);
644
    
645
    logMetacat.info("getLogRecords");
646
    return log;
647
  }
648
    
649
  /**
650
   * Return the object identified by the given object identifier
651
   * 
652
   * @param session - the Session object containing the credentials for the Subject
653
   * @param pid - the object identifier for the given object
654
   * 
655
   * TODO: The D1 Authorization API doesn't provide information on which 
656
   * authentication system the Subject belongs to, and so it's not possible to
657
   * discern which Person or Group is a valid KNB LDAP DN.  Fix this.
658
   * 
659
   * @return inputStream - the input stream of the given object
660
   * 
661
   * @throws InvalidToken
662
   * @throws ServiceFailure
663
   * @throws NotAuthorized
664
   * @throws InvalidRequest
665
   * @throws NotImplemented
666
   */
667
  public InputStream get(Session session, Identifier pid) 
668
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
669
    NotImplemented {
670
    
671
    String serviceFailureCode = "1030";
672
    Identifier sid = getPIDForSID(pid, serviceFailureCode);
673
    if(sid != null) {
674
        pid = sid;
675
    }
676
    
677
    InputStream inputStream = null; // bytes to be returned
678
    handler = new MetacatHandler(new Timer());
679
    boolean allowed = false;
680
    String localId; // the metacat docid for the pid
681
    
682
    // get the local docid from Metacat
683
    try {
684
      localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
685
    
686
    } catch (McdbDocNotFoundException e) {
687
      throw new NotFound("1020", "The object specified by " + 
688
                         pid.getValue() +
689
                         " does not exist at this node.");
690
    } catch (SQLException e) {
691
        throw new ServiceFailure("1030", "The object specified by "+ pid.getValue()+
692
                                  " couldn't be identified at this node since "+e.getMessage());
693
    }
694
    
695
    // check for authorization
696
    try {
697
		allowed = isAuthorized(session, pid, Permission.READ);
698
	} catch (InvalidRequest e) {
699
		throw new ServiceFailure("1030", e.getDescription());
700
	}
701
    
702
    // if the person is authorized, perform the read
703
    if (allowed) {
704
      try {
705
        inputStream = handler.read(localId);
706
      } catch (McdbDocNotFoundException de) {
707
          String error ="";
708
          if(EventLog.getInstance().isDeleted(localId)) {
709
                error=DELETEDMESSAGE;
710
          }
711
          throw new NotFound("1020", "The object specified by " + 
712
                           pid.getValue() +
713
                           " does not exist at this node. "+error);
714
      } catch (Exception e) {
715
        throw new ServiceFailure("1030", "The object specified by " + 
716
            pid.getValue() +
717
            " could not be returned due to error: " +
718
            e.getMessage()+". ");
719
      }
720
    }
721

    
722
    // if we fail to set the input stream
723
    if ( inputStream == null ) {
724
        String error ="";
725
        if(EventLog.getInstance().isDeleted(localId)) {
726
              error=DELETEDMESSAGE;
727
        }
728
        throw new NotFound("1020", "The object specified by " + 
729
                         pid.getValue() +
730
                         " does not exist at this node. "+error);
731
    }
732
    
733
	// log the read event
734
    String principal = Constants.SUBJECT_PUBLIC;
735
    if (session != null && session.getSubject() != null) {
736
    	principal = session.getSubject().getValue();
737
    }
738
    EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "read");
739
    
740
    return inputStream;
741
  }
742

    
743
  /**
744
   * Return the system metadata for a given object
745
   * 
746
   * @param session - the Session object containing the credentials for the Subject
747
   * @param pid - the object identifier for the given object
748
   * 
749
   * @return inputStream - the input stream of the given system metadata object
750
   * 
751
   * @throws InvalidToken
752
   * @throws ServiceFailure
753
   * @throws NotAuthorized
754
   * @throws NotFound
755
   * @throws InvalidRequest
756
   * @throws NotImplemented
757
   */
758
    public SystemMetadata getSystemMetadata(Session session, Identifier pid)
759
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
760
        NotImplemented {
761

    
762
        String serviceFailureCode = "1090";
763
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
764
        if(sid != null) {
765
            pid = sid;
766
        }
767
        boolean isAuthorized = false;
768
        SystemMetadata systemMetadata = null;
769
        List<Replica> replicaList = null;
770
        NodeReference replicaNodeRef = null;
771
        List<Node> nodeListBySubject = null;
772
        Subject subject = null;
773
        
774
        if (session != null ) {
775
            subject = session.getSubject();
776
        }
777
        
778
        // check normal authorization
779
        BaseException originalAuthorizationException = null;
780
        if (!isAuthorized) {
781
            try {
782
                isAuthorized = isAuthorized(session, pid, Permission.READ);
783

    
784
            } catch (InvalidRequest e) {
785
                throw new ServiceFailure("1090", e.getDescription());
786
            } catch (NotAuthorized nae) {
787
            	// catch this for later
788
            	originalAuthorizationException = nae;
789
			}
790
        }
791
        
792
        // get the system metadata first because we need the replica list for auth
793
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
794
        
795
        // check the replica information to expand access to MNs that might need it
796
        if (!isAuthorized) {
797
        	
798
	        try {
799
	        	
800
	            // if MNs are listed as replicas, allow access
801
	            if ( systemMetadata != null ) {
802
	                replicaList = systemMetadata.getReplicaList();
803
	                // only check if there are in fact replicas listed
804
	                if ( replicaList != null ) {
805
	                    
806
	                    if ( subject != null ) {
807
	                        // get the list of nodes with a matching node subject
808
	                        try {
809
	                            nodeListBySubject = listNodesBySubject(session.getSubject());
810
	
811
	                        } catch (BaseException e) {
812
	                            // Unexpected error contacting the CN via D1Client
813
	                            String msg = "Caught an unexpected error while trying "
814
	                                    + "to potentially authorize system metadata access "
815
	                                    + "based on the session subject. The error was "
816
	                                    + e.getMessage();
817
	                            logMetacat.error(msg);
818
	                            if (logMetacat.isDebugEnabled()) {
819
	                                e.printStackTrace();
820
	
821
	                            }
822
	                            // isAuthorized is still false 
823
	                        }
824
	
825
	                    }
826
	                    if (nodeListBySubject != null) {
827
	                        // compare node ids to replica node ids
828
	                        outer: for (Replica replica : replicaList) {
829
	                            replicaNodeRef = replica.getReplicaMemberNode();
830
	
831
	                            for (Node node : nodeListBySubject) {
832
	                                if (node.getIdentifier().equals(replicaNodeRef)) {
833
	                                    // node id via session subject matches a replica node
834
	                                    isAuthorized = true;
835
	                                    break outer;
836
	                                }
837
	                            }
838
	                        }
839
	                    }
840
	                }
841
	            }
842
	            
843
	            // if we still aren't authorized, then we are done
844
	            if (!isAuthorized) {
845
	                throw new NotAuthorized("1400", Permission.READ
846
	                        + " not allowed on " + pid.getValue());
847
	            }
848

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

    
977
      boolean allowed = false;
978
      
979
      // must have a session in order to check admin 
980
      if (session == null) {
981
         logMetacat.debug("In isAdminAuthorized(), session is null ");
982
         return false;
983
      }
984
      
985
      logMetacat.debug("In isAdminAuthorized(), checking CN or MN authorization for " +
986
           session.getSubject().getValue());
987
      
988
      // check if this is the node calling itself (MN)
989
      try {
990
          allowed = isNodeAdmin(session);
991
      } catch (Exception e) {
992
          logMetacat.warn("We can't determine if the session is a node subject. But we will contiune to check if it is a cn subject.");
993
      }
994
      
995
      
996
      // check the CN list
997
      if (!allowed) {
998
	      allowed = isCNAdmin(session);
999
      }
1000
      
1001
      return allowed;
1002
  }
1003
  
1004
  /*
1005
   * Determine if the specified session is a CN or not. Return true if it is; otherwise false.
1006
   */
1007
  protected boolean isCNAdmin (Session session) {
1008
      boolean allowed = false;
1009
      List<Node> nodes = null;
1010
      logMetacat.debug("D1NodeService.isCNAdmin - the beginning");
1011
      try {
1012
          // are we allowed to do this? only CNs are allowed
1013
          CNode cn = D1Client.getCN();
1014
          logMetacat.debug("D1NodeService.isCNAdmin - after getting the cn.");
1015
          nodes = cn.listNodes().getNodeList();
1016
          logMetacat.debug("D1NodeService.isCNAdmin - after getting the node list.");
1017
      }
1018
      catch (Throwable e) {
1019
          logMetacat.warn("Couldn't get the node list from the cn since "+e.getMessage()+". So we can't determine if the subject is a CN.");
1020
          return false;  
1021
      }
1022
          
1023
      if ( nodes == null ) {
1024
          return false;
1025
          //throw new ServiceFailure("4852", "Couldn't get node list.");
1026
      }
1027
      
1028
      // find the node in the node list
1029
      for ( Node node : nodes ) {
1030
          
1031
          NodeReference nodeReference = node.getIdentifier();
1032
          logMetacat.debug("In isCNAdmin(), a Node reference from the CN node list is: " + nodeReference.getValue());
1033
          
1034
          Subject subject = session.getSubject();
1035
          
1036
          if (node.getType() == NodeType.CN) {
1037
              List<Subject> nodeSubjects = node.getSubjectList();
1038
              
1039
              // check if the session subject is in the node subject list
1040
              for (Subject nodeSubject : nodeSubjects) {
1041
                  logMetacat.debug("In isCNAdmin(), comparing subjects: " +
1042
                      nodeSubject.getValue() + " and the user " + subject.getValue());
1043
                  if ( nodeSubject.equals(subject) ) {
1044
                      allowed = true; // subject of session == target node subject
1045
                      break;
1046
                      
1047
                  }
1048
              }              
1049
          }
1050
      }
1051
      logMetacat.debug("D1NodeService.isCNAdmin. Is it a cn admin? "+allowed);
1052
      return allowed;
1053
  }
1054
  
1055
  /**
1056
   * Test if the user identified by the provided token has administrative authorization 
1057
   * on this node because they are calling themselves
1058
   * 
1059
   * @param session - the Session object containing the credentials for the Subject
1060
   * 
1061
   * @return true if the user is this node
1062
   * @throws ServiceFailure 
1063
   * @throws NotImplemented 
1064
   */
1065
  public boolean isNodeAdmin(Session session) throws NotImplemented, ServiceFailure {
1066

    
1067
      boolean allowed = false;
1068
      
1069
      // must have a session in order to check admin 
1070
      if (session == null) {
1071
         logMetacat.debug("In isNodeAdmin(), session is null ");
1072
         return false;
1073
      }
1074
      
1075
      logMetacat.debug("In isNodeAdmin(), MN authorization for the user " +
1076
           session.getSubject().getValue());
1077
      
1078
      Node node = MNodeService.getInstance(request).getCapabilities();
1079
      NodeReference nodeReference = node.getIdentifier();
1080
      logMetacat.debug("In isNodeAdmin(), Node reference is: " + nodeReference.getValue());
1081
      
1082
      Subject subject = session.getSubject();
1083
      
1084
      if (node.getType() == NodeType.MN) {
1085
          List<Subject> nodeSubjects = node.getSubjectList();
1086
          
1087
          // check if the session subject is in the node subject list
1088
          for (Subject nodeSubject : nodeSubjects) {
1089
              logMetacat.debug("In isNodeAdmin(), comparing subjects: " +
1090
                  nodeSubject.getValue() + " and the user" + subject.getValue());
1091
              if ( nodeSubject.equals(subject) ) {
1092
                  allowed = true; // subject of session == this node's subect
1093
                  break;
1094
              }
1095
          }              
1096
      }
1097
      logMetacat.debug("In is NodeAdmin method. Is this a node admin? "+allowed);
1098
      return allowed;
1099
  }
1100
  
1101
  /**
1102
   * Test if the user identified by the provided token has authorization 
1103
   * for the operation on the specified object.
1104
   * Allowed subjects include:
1105
   * 1. CNs
1106
   * 2. Authoritative node
1107
   * 3. Owner of the object
1108
   * 4. Users with the specified permission in the access rules.
1109
   * 
1110
   * @param session - the Session object containing the credentials for the Subject
1111
   * @param pid - The identifer of the resource for which access is being checked
1112
   * @param operation - The type of operation which is being requested for the given pid
1113
   *
1114
   * @return true if the operation is allowed
1115
   * 
1116
   * @throws ServiceFailure
1117
   * @throws InvalidToken
1118
   * @throws NotFound
1119
   * @throws NotAuthorized
1120
   * @throws NotImplemented
1121
   * @throws InvalidRequest
1122
   */
1123
  public boolean isAuthorized(Session session, Identifier pid, Permission permission)
1124
    throws ServiceFailure, InvalidToken, NotFound, NotAuthorized,
1125
    NotImplemented, InvalidRequest {
1126

    
1127
    boolean allowed = false;
1128
    
1129
    if (permission == null) {
1130
    	throw new InvalidRequest("1761", "Permission was not provided or is invalid");
1131
    }
1132
    
1133
    // always allow CN access
1134
    if ( isAdminAuthorized(session) ) {
1135
        allowed = true;
1136
        return allowed;
1137
        
1138
    }
1139
    
1140
    String serviceFailureCode = "1760";
1141
    Identifier sid = getPIDForSID(pid, serviceFailureCode);
1142
    if(sid != null) {
1143
        pid = sid;
1144
    }
1145
    
1146
    // the authoritative member node of the pid always has the access as well.
1147
    if (isAuthoritativeMNodeAdmin(session, pid)) {
1148
        allowed = true;
1149
        return allowed;
1150
    }
1151
    
1152
    //is it the owner of the object or the access rules allow the user?
1153
    allowed = userHasPermission(session,  pid, permission );
1154
    
1155
    // throw or return?
1156
    if (!allowed) {
1157
     // track the identities we have checked against
1158
      StringBuffer includedSubjects = new StringBuffer();
1159
      Set<Subject> subjects = AuthUtils.authorizedClientSubjects(session);
1160
      for (Subject s: subjects) {
1161
             includedSubjects.append(s.getValue() + "; ");
1162
        }    
1163
      throw new NotAuthorized("1820", permission + " not allowed on " + pid.getValue() + " for subject[s]: " + includedSubjects.toString() );
1164
    }
1165
    
1166
    return allowed;
1167
    
1168
  }
1169
  
1170
  
1171
  /*
1172
   * Determine if a user has the permission to perform the specified permission.
1173
   * 1. Owner can have any permission.
1174
   * 2. Access table allow the user has the permission
1175
   */
1176
  public static boolean userHasPermission(Session userSession, Identifier pid, Permission permission ) throws NotFound, ServiceFailure, NotImplemented, InvalidRequest, InvalidToken, NotAuthorized {
1177
      boolean allowed = false;
1178
      // permissions are hierarchical
1179
      List<Permission> expandedPermissions = null;
1180
      // get the subject[s] from the session
1181
      //defer to the shared util for recursively compiling the subjects   
1182
      Set<Subject> subjects = AuthUtils.authorizedClientSubjects(userSession);
1183
          
1184
      // get the system metadata
1185
      String pidStr = pid.getValue();
1186
      SystemMetadata systemMetadata = null;
1187
      try {
1188
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1189

    
1190
      } catch (Exception e) {
1191
          // convert Hazelcast RuntimeException to NotFound
1192
          logMetacat.error("An error occurred while getting system metadata for identifier " +
1193
              pid.getValue() + ". The error message was: " + e.getMessage(), e);
1194
          throw new ServiceFailure("1090", "Can't get the system metadata for " + pidStr+ " since "+e.getMessage());
1195
          
1196
      } 
1197
      
1198
      // throw not found if it was not found
1199
      if (systemMetadata == null) {
1200
          String localId = null;
1201
          String error = "No system metadata could be found for given PID: " + pidStr;
1202
          try {
1203
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1204
            
1205
           } catch (Exception e) {
1206
              logMetacat.warn("Couldn't find the local id for the pid "+pidStr);
1207
          }
1208
          
1209
          if(localId != null && EventLog.getInstance().isDeleted(localId)) {
1210
              error = error + ". "+DELETEDMESSAGE;
1211
          } else if (localId == null && EventLog.getInstance().isDeleted(pid.getValue())) {
1212
              error = error + ". "+DELETEDMESSAGE;
1213
          }
1214
          throw new NotFound("1800", error);
1215
      }
1216
          
1217
      // do we own it?
1218
      for (Subject s: subjects) {
1219
        logMetacat.debug("Comparing \t" + 
1220
                         systemMetadata.getRightsHolder().getValue() +
1221
                         " \tagainst \t" + s.getValue());
1222
          //includedSubjects.append(s.getValue() + "; ");
1223
          allowed = systemMetadata.getRightsHolder().equals(s);
1224
          if (allowed) {
1225
              return allowed;
1226
          } else {
1227
              //check if the rightHolder is a group name. If it is, any member of the group can be considered a the right holder.
1228
              allowed = expandRightsHolder(systemMetadata.getRightsHolder(), s);
1229
              if(allowed) {
1230
                  return allowed;
1231
              }
1232
          }
1233
      }    
1234
      
1235
      // otherwise check the access rules
1236
      try {
1237
          List<AccessRule> allows = systemMetadata.getAccessPolicy().getAllowList();
1238
          search: // label break
1239
          for (AccessRule accessRule: allows) {
1240
            for (Subject s: subjects) {
1241
              logMetacat.debug("Checking allow access rule for subject: " + s.getValue());
1242
              if (accessRule.getSubjectList().contains(s)) {
1243
                  logMetacat.debug("Access rule contains subject: " + s.getValue());
1244
                  for (Permission p: accessRule.getPermissionList()) {
1245
                      logMetacat.debug("Checking permission: " + p.xmlValue());
1246
                      expandedPermissions = expandPermissions(p);
1247
                      allowed = expandedPermissions.contains(permission);
1248
                      if (allowed) {
1249
                          logMetacat.info("Permission granted: " + p.xmlValue() + " to " + s.getValue());
1250
                          break search; //label break
1251
                      }
1252
                  }
1253
                  
1254
              }
1255
            }
1256
          }
1257
      } catch (Exception e) {
1258
          // catch all for errors - safe side should be to deny the access
1259
          logMetacat.error("Problem checking authorization - defaulting to deny", e);
1260
          allowed = false;
1261
        
1262
      }
1263
      return allowed;
1264
  }
1265
  
1266
  
1267
  /**
1268
   * Check if the given userSession is the member of the right holder group (if the right holder is a group subject).
1269
   * If the right holder is not a group, it will be false of course.
1270
   * @param rightHolder the subject of the right holder.
1271
   * @param userSession the subject will be compared
1272
   * @return true if the user session is a member of the right holder group; false otherwise.
1273
 * @throws NotImplemented 
1274
 * @throws ServiceFailure 
1275
 * @throws NotAuthorized 
1276
 * @throws InvalidToken 
1277
 * @throws InvalidRequest 
1278
   */
1279
  public static boolean expandRightsHolder(Subject rightHolder, Subject userSession) throws ServiceFailure, NotImplemented, InvalidRequest, InvalidToken, NotAuthorized {
1280
      boolean is = false;
1281
      if(rightHolder != null && userSession != null && rightHolder.getValue() != null && !rightHolder.getValue().trim().equals("") && userSession.getValue() != null && !userSession.getValue().trim().equals("")) {
1282
          CNode cn = D1Client.getCN();
1283
          logMetacat.debug("D1NodeService.expandRightHolder - at the start of method: after getting the cn node and cn node is "+cn.getNodeBaseServiceUrl());
1284
          String query= rightHolder.getValue();
1285
          int start =0;
1286
          int count= 200;
1287
          String status = null;
1288
          Session session = null;
1289
          SubjectInfo subjects = cn.listSubjects(session, query, status, start, count);
1290

    
1291
          while(subjects != null) {
1292
              logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the returned result is not null");
1293
              List<Group> groups = subjects.getGroupList();
1294
              is = isInGroups(userSession, rightHolder, groups);
1295
              if(is) {
1296
                  //since we find it, return it.
1297
                  return is;
1298
              } else {
1299
                  //decide if we need to try the page query for another trying.
1300
                  int sizeOfGroups = 0;
1301
                  if(groups != null) {
1302
                     sizeOfGroups  = groups.size();
1303
                  }
1304
                  List<Person> persons = subjects.getPersonList();
1305
                  int sizeOfPersons = 0;
1306
                  if(persons != null) {
1307
                      sizeOfPersons = persons.size();
1308
                  }
1309
                  int totalSize = sizeOfGroups+sizeOfPersons;
1310
                  //logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the size of return result is "+totalSize);
1311
                 //we can't find the target on the first query, maybe query again.
1312
                  if(totalSize == count) {
1313
                      start = start+count;
1314
                      logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the size of return result equals the count "+totalSize+" .And we didn't find the target in the this query. So we have to use the page query with the start number "+start);
1315
                      subjects = cn.listSubjects(session, query, status, start, count);
1316
                  } else if (totalSize < count){
1317
                      logMetacat.debug("D1NodeService.expandRightHolder - we are already at the end of the returned restult since the size of returned results "+totalSize+
1318
                          " is less than the count "+count+". So we have to break the loop and finish the try.");
1319
                      break;
1320
                  } else if (totalSize >count) {
1321
                      logMetacat.warn("D1NodeService.expandRightHolder - Something is wrong on the implementation of the method listSubject since the size of returned results "+totalSize+
1322
                              " is greater than the count "+count+". So we have to break the loop and finish the try.");
1323
                      break;
1324
                  }
1325
              }
1326
              
1327
          } 
1328
          //logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the returned result is null");
1329
          if(!is) {
1330
              logMetacat.debug("D1NodeService.expandRightHolder - We can NOT find any member in the group "+query+" (if it is a group) matches the user "+userSession.getValue());
1331
          }
1332
      } else {
1333
          logMetacat.debug("D1NodeService.expandRightHolder - We can't determine if the use subject is a member of the right holder group since one of them is null or blank");
1334
      }
1335
     
1336
      return is;
1337
  }
1338
  
1339
  /*
1340
   * If the given useSession is a member of a group which is in the given list of groups and has the name of righHolder.
1341
   */
1342
  private static boolean isInGroups(Subject userSession, Subject rightHolder, List<Group> groups) {
1343
      boolean is = false;
1344
      if(groups != null) {
1345
          logMetacat.debug("D1NodeService.isInGroups -  the given groups' (the returned result including groups) size is "+groups.size());
1346
          for(Group group : groups) {
1347
              //logMetacat.debug("D1NodeService.expandRightHolder - group has the subject "+group.getSubject().getValue());
1348
              if(group != null && group.getSubject() != null && group.getSubject().equals(rightHolder)) {
1349
                  logMetacat.debug("D1NodeService.isInGroups - there is a group in the list having the subjecct "+group.getSubject().getValue()+" which matches the right holder's subject "+rightHolder.getValue());
1350
                  List<Subject> members = group.getHasMemberList();
1351
                  if(members != null ){
1352
                      logMetacat.debug("D1NodeService.isInGroups - the group "+group.getSubject().getValue()+" in the cn has members");
1353
                      for(Subject member : members) {
1354
                          logMetacat.debug("D1NodeService.isInGroups - compare the member "+member.getValue()+" with the user "+userSession.getValue());
1355
                          if(member.getValue() != null && !member.getValue().trim().equals("") && userSession.getValue() != null && member.getValue().equals(userSession.getValue())) {
1356
                              logMetacat.debug("D1NodeService.isInGroups - Find it! The member "+member.getValue()+" in the group "+group.getSubject().getValue()+" matches the user "+userSession.getValue());
1357
                              is = true;
1358
                              return is;
1359
                          }
1360
                      }
1361
                  }
1362
                  break;//we found the group but can't find the member matches the user. so break it.
1363
              }
1364
          }
1365
      } else {
1366
          logMetacat.debug("D1NodeService.isInGroups -  the given group is null (the returned result does NOT have a group");
1367
      }
1368
      return is;
1369
  }
1370
  /*
1371
   * parse a logEntry and get the relevant field from it
1372
   * 
1373
   * @param fieldname
1374
   * @param entry
1375
   * @return
1376
   */
1377
  private String getLogEntryField(String fieldname, String entry) {
1378
    String begin = "<" + fieldname + ">";
1379
    String end = "</" + fieldname + ">";
1380
    // logMetacat.debug("looking for " + begin + " and " + end +
1381
    // " in entry " + entry);
1382
    String s = entry.substring(entry.indexOf(begin) + begin.length(), entry
1383
        .indexOf(end));
1384
    logMetacat.debug("entry " + fieldname + " : " + s);
1385
    return s;
1386
  }
1387

    
1388
  /** 
1389
   * Determine if a given object should be treated as an XML science metadata
1390
   * object. 
1391
   * 
1392
   * @param sysmeta - the SystemMetadata describing the object
1393
   * @return true if the object should be treated as science metadata
1394
   */
1395
  public static boolean isScienceMetadata(SystemMetadata sysmeta) {
1396
    
1397
    ObjectFormat objectFormat = null;
1398
    boolean isScienceMetadata = false;
1399
    
1400
    try {
1401
      objectFormat = ObjectFormatCache.getInstance().getFormat(sysmeta.getFormatId());
1402
      if ( objectFormat.getFormatType().equals("METADATA") ) {
1403
      	isScienceMetadata = true;
1404
      	
1405
      }
1406
      
1407
       
1408
    /*} catch (ServiceFailure e) {
1409
      logMetacat.debug("There was a problem determining if the object identified by" + 
1410
          sysmeta.getIdentifier().getValue() + 
1411
          " is science metadata: " + e.getMessage());*/
1412
    
1413
    } catch (NotFound e) {
1414
      logMetacat.debug("There was a problem determining if the object identified by" + 
1415
          sysmeta.getIdentifier().getValue() + 
1416
          " is science metadata: " + e.getMessage());
1417
    
1418
    }
1419
    
1420
    return isScienceMetadata;
1421

    
1422
  }
1423
  
1424
  /**
1425
   * Check fro whitespace in the given pid.
1426
   * null pids are also invalid by default
1427
   * @param pid
1428
   * @return
1429
   */
1430
  public static boolean isValidIdentifier(Identifier pid) {
1431
	  if (pid != null && pid.getValue() != null && pid.getValue().length() > 0) {
1432
		  return !pid.getValue().matches(".*\\s+.*");
1433
	  } 
1434
	  return false;
1435
  }
1436
  
1437
  
1438
  /**
1439
   * Insert or update an XML document into Metacat
1440
   * 
1441
   * @param xml - the XML document to insert or update
1442
   * @param pid - the identifier to be used for the resulting object
1443
   * 
1444
   * @return localId - the resulting docid of the document created or updated
1445
   * 
1446
   */
1447
  public String insertOrUpdateDocument(InputStream xmlStream, String encoding,  Identifier pid, 
1448
    Session session, String insertOrUpdate, String formatId) 
1449
    throws ServiceFailure, IOException, PropertyNotFoundException{
1450
    
1451
  	logMetacat.debug("Starting to insert xml document...");
1452
    IdentifierManager im = IdentifierManager.getInstance();
1453

    
1454
    // generate pid/localId pair for sysmeta
1455
    String localId = null;
1456
    byte[] xmlBytes  = IOUtils.toByteArray(xmlStream);
1457
    IOUtils.closeQuietly(xmlStream);
1458
    String xmlStr = new String(xmlBytes, encoding);
1459
    if(insertOrUpdate.equals("insert")) {
1460
      localId = im.generateLocalId(pid.getValue(), 1);
1461
      
1462
    } else {
1463
      //localid should already exist in the identifier table, so just find it
1464
      try {
1465
        logMetacat.debug("Updating pid " + pid.getValue());
1466
        logMetacat.debug("looking in identifier table for pid " + pid.getValue());
1467
        
1468
        localId = im.getLocalId(pid.getValue());
1469
        
1470
        logMetacat.debug("localId: " + localId);
1471
        //increment the revision
1472
        String docid = localId.substring(0, localId.lastIndexOf("."));
1473
        String revS = localId.substring(localId.lastIndexOf(".") + 1, localId.length());
1474
        int rev = new Integer(revS).intValue();
1475
        rev++;
1476
        docid = docid + "." + rev;
1477
        localId = docid;
1478
        logMetacat.debug("incremented localId: " + localId);
1479
      
1480
      } catch(McdbDocNotFoundException e) {
1481
        throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument(): " +
1482
            "pid " + pid.getValue() + 
1483
            " should have been in the identifier table, but it wasn't: " + 
1484
            e.getMessage());
1485
      
1486
      } catch (SQLException e) {
1487
          throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument() -"+
1488
                     " couldn't identify if the pid "+pid.getValue()+" is in the identifier table since "+e.getMessage());
1489
      }
1490
      
1491
    }
1492

    
1493
    Hashtable<String, String[]> params = new Hashtable<String, String[]>();
1494
    String[] action = new String[1];
1495
    action[0] = insertOrUpdate;
1496
    params.put("action", action);
1497
    String[] docid = new String[1];
1498
    docid[0] = localId;
1499
    params.put("docid", docid);
1500
    String[] doctext = new String[1];
1501
    doctext[0] = xmlStr;
1502
    params.put("doctext", doctext);
1503
    
1504
    String username = Constants.SUBJECT_PUBLIC;
1505
    String[] groupnames = null;
1506
    if (session != null ) {
1507
    	username = session.getSubject().getValue();
1508
    	Set<Subject> otherSubjects = AuthUtils.authorizedClientSubjects(session);
1509
    	if (otherSubjects != null) {    		
1510
			groupnames = new String[otherSubjects.size()];
1511
			int i = 0;
1512
			Iterator<Subject> iter = otherSubjects.iterator();
1513
			while (iter.hasNext()) {
1514
				groupnames[i] = iter.next().getValue();
1515
				i++;
1516
			}
1517
    	}
1518
    }
1519
    
1520
    // do the insert or update action
1521
    handler = new MetacatHandler(new Timer());
1522
    String result = handler.handleInsertOrUpdateAction(request.getRemoteAddr(), request.getHeader("User-Agent"), null, 
1523
                        null, params, username, groupnames, false, false, xmlBytes, formatId);
1524
    boolean isScienceMetadata = true;
1525
    if(result.indexOf("<error>") != -1 || !IdentifierManager.getInstance().objectFileExists(localId, isScienceMetadata)) {
1526
    	String detailCode = "";
1527
    	if ( insertOrUpdate.equals("insert") ) {
1528
    		// make sure to remove the mapping so that subsequent attempts do not fail with IdentifierNotUnique
1529
    		im.removeMapping(pid.getValue(), localId);
1530
    		detailCode = "1190";
1531
    		
1532
    	} else if ( insertOrUpdate.equals("update") ) {
1533
    		detailCode = "1310";
1534
    		
1535
    	}
1536
    	logMetacat.error("D1NodeService.insertOrUpdateDocument - Error inserting or updating document: "+pid.getValue()+" since "+result);
1537
        throw new ServiceFailure(detailCode, 
1538
          "Error inserting or updating document: " +pid.getValue()+" since "+ result);
1539
    }
1540
    logMetacat.info("D1NodeService.insertOrUpdateDocument - Finsished inserting xml document with local id " + localId +" and its pid is "+pid.getValue());
1541
    
1542
    return localId;
1543
  }
1544
  
1545
  /**
1546
   * Insert a data document
1547
   * 
1548
   * @param object
1549
   * @param pid
1550
   * @param sessionData
1551
   * @throws ServiceFailure
1552
   * @returns localId of the data object inserted
1553
   */
1554
  public String insertDataObject(InputStream object, Identifier pid, 
1555
          Session session) throws ServiceFailure {
1556
      
1557
    String username = Constants.SUBJECT_PUBLIC;
1558
    String[] groupnames = null;
1559
    if (session != null ) {
1560
    	username = session.getSubject().getValue();
1561
    	Set<Subject> otherSubjects = AuthUtils.authorizedClientSubjects(session);
1562
    	if (otherSubjects != null) {    		
1563
			groupnames = new String[otherSubjects.size()];
1564
			int i = 0;
1565
			Iterator<Subject> iter = otherSubjects.iterator();
1566
			while (iter.hasNext()) {
1567
				groupnames[i] = iter.next().getValue();
1568
				i++;
1569
			}
1570
    	}
1571
    }
1572
  
1573
    // generate pid/localId pair for object
1574
    logMetacat.debug("Generating a pid/localId mapping");
1575
    IdentifierManager im = IdentifierManager.getInstance();
1576
    String localId = im.generateLocalId(pid.getValue(), 1);
1577
  
1578
    // Save the data file to disk using "localId" as the name
1579
    String datafilepath = null;
1580
	try {
1581
		datafilepath = PropertyService.getProperty("application.datafilepath");
1582
	} catch (PropertyNotFoundException e) {
1583
		ServiceFailure sf = new ServiceFailure("1190", "Lookup data file path" + e.getMessage());
1584
		sf.initCause(e);
1585
		throw sf;
1586
	}
1587
    boolean locked = false;
1588
	try {
1589
		locked = DocumentImpl.getDataFileLockGrant(localId);
1590
	} catch (Exception e) {
1591
		ServiceFailure sf = new ServiceFailure("1190", "Could not lock file for writing:" + e.getMessage());
1592
		sf.initCause(e);
1593
		throw sf;
1594
	}
1595

    
1596
    logMetacat.debug("Case DATA: starting to write to disk.");
1597
	if (locked) {
1598

    
1599
          File dataDirectory = new File(datafilepath);
1600
          dataDirectory.mkdirs();
1601
  
1602
          File newFile = writeStreamToFile(dataDirectory, localId, object);
1603
  
1604
          // TODO: Check that the file size matches SystemMetadata
1605
          // long size = newFile.length();
1606
          // if (size == 0) {
1607
          //     throw new IOException("Uploaded file is 0 bytes!");
1608
          // }
1609
  
1610
          // Register the file in the database (which generates an exception
1611
          // if the localId is not acceptable or other untoward things happen
1612
          try {
1613
            logMetacat.debug("Registering document...");
1614
            DocumentImpl.registerDocument(localId, "BIN", localId,
1615
                    username, groupnames);
1616
            logMetacat.debug("Registration step completed.");
1617
            
1618
          } catch (SQLException e) {
1619
            //newFile.delete();
1620
            logMetacat.debug("SQLE: " + e.getMessage());
1621
            e.printStackTrace(System.out);
1622
            throw new ServiceFailure("1190", "Registration failed: " + 
1623
            		e.getMessage());
1624
            
1625
          } catch (AccessionNumberException e) {
1626
            //newFile.delete();
1627
            logMetacat.debug("ANE: " + e.getMessage());
1628
            e.printStackTrace(System.out);
1629
            throw new ServiceFailure("1190", "Registration failed: " + 
1630
            	e.getMessage());
1631
            
1632
          } catch (Exception e) {
1633
            //newFile.delete();
1634
            logMetacat.debug("Exception: " + e.getMessage());
1635
            e.printStackTrace(System.out);
1636
            throw new ServiceFailure("1190", "Registration failed: " + 
1637
            	e.getMessage());
1638
          }
1639
  
1640
          logMetacat.debug("Logging the creation event.");
1641
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, "create");
1642
  
1643
          // Schedule replication for this data file, the "insert" action is important here!
1644
          logMetacat.debug("Scheduling replication.");
1645
          ForceReplicationHandler frh = new ForceReplicationHandler(localId, "insert", false, null);
1646
      }
1647
      
1648
      return localId;
1649
    
1650
  }
1651

    
1652
  /**
1653
   * Insert a systemMetadata document and return its localId
1654
   */
1655
  public void insertSystemMetadata(SystemMetadata sysmeta) 
1656
      throws ServiceFailure {
1657
      
1658
  	  logMetacat.debug("Starting to insert SystemMetadata...");
1659
      sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
1660
      logMetacat.debug("Inserting new system metadata with modified date " + 
1661
          sysmeta.getDateSysMetadataModified());
1662
      
1663
      //insert the system metadata
1664
      try {
1665
        // note: the calling subclass handles the map hazelcast lock/unlock
1666
      	HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
1667
      	// submit for indexing
1668
        MetacatSolrIndex.getInstance().submit(sysmeta.getIdentifier(), sysmeta, null, true);
1669
      } catch (Exception e) {
1670
          throw new ServiceFailure("1190", e.getMessage());
1671
          
1672
	    }  
1673
  }
1674
  
1675
  /**
1676
   * Retrieve the list of objects present on the MN that match the calling parameters
1677
   * 
1678
   * @param session - the Session object containing the credentials for the Subject
1679
   * @param startTime - Specifies the beginning of the time range from which 
1680
   *                    to return object (>=)
1681
   * @param endTime - Specifies the beginning of the time range from which 
1682
   *                  to return object (>=)
1683
   * @param objectFormat - Restrict results to the specified object format
1684
   * @param replicaStatus - Indicates if replicated objects should be returned in the list
1685
   * @param start - The zero-based index of the first value, relative to the 
1686
   *                first record of the resultset that matches the parameters.
1687
   * @param count - The maximum number of entries that should be returned in 
1688
   *                the response. The Member Node may return less entries 
1689
   *                than specified in this value.
1690
   * 
1691
   * @return objectList - the list of objects matching the criteria
1692
   * 
1693
   * @throws InvalidToken
1694
   * @throws ServiceFailure
1695
   * @throws NotAuthorized
1696
   * @throws InvalidRequest
1697
   * @throws NotImplemented
1698
   */
1699
  public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Identifier identifier, NodeReference nodeId, Integer start,
1700
          Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
1701

    
1702
      ObjectList objectList = null;
1703

    
1704
      try {
1705
          // safeguard against large requests
1706
          if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
1707
              count = MAXIMUM_DB_RECORD_COUNT;
1708
          }
1709
          boolean isSid = false;
1710
          if(identifier != null) {
1711
              isSid = IdentifierManager.getInstance().systemMetadataSIDExists(identifier);
1712
          }
1713
          objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, nodeId, start, count, identifier, isSid);
1714
      } catch (Exception e) {
1715
          throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1716
      }
1717

    
1718
      return objectList;
1719
  }
1720

    
1721

    
1722
  /**
1723
   * Update a systemMetadata document
1724
   * 
1725
   * @param sysMeta - the system metadata object in the system to update
1726
   */
1727
    protected void updateSystemMetadata(SystemMetadata sysMeta)
1728
        throws ServiceFailure {
1729
        logMetacat.debug("D1NodeService.updateSystemMetadata() called.");
1730
        try {
1731
            HazelcastService.getInstance().getSystemMetadataMap().lock(sysMeta.getIdentifier());
1732
            boolean needUpdateModificationDate = true;
1733
            updateSystemMetadataWithoutLock(sysMeta, needUpdateModificationDate);
1734
        } catch (Exception e) {
1735
            throw new ServiceFailure("4862", e.getMessage());
1736
        } finally {
1737
            HazelcastService.getInstance().getSystemMetadataMap().unlock(sysMeta.getIdentifier());
1738

    
1739
        }
1740

    
1741
    }
1742
    
1743
    /**
1744
     * Update system metadata without locking the system metadata in hazelcast server. So the caller should lock it first. 
1745
     * @param sysMeta
1746
     * @param needUpdateModificationDate
1747
     * @throws ServiceFailure
1748
     */
1749
    private void updateSystemMetadataWithoutLock(SystemMetadata sysMeta, boolean needUpdateModificationDate) throws ServiceFailure {
1750
        logMetacat.debug("D1NodeService.updateSystemMetadataWithoutLock() called.");
1751
        if(needUpdateModificationDate) {
1752
            logMetacat.debug("D1NodeService.updateSystemMetadataWithoutLock() - update the modification date.");
1753
            sysMeta.setDateSysMetadataModified(new Date());
1754
        }
1755
        
1756
        // submit for indexing
1757
        try {
1758
            HazelcastService.getInstance().getSystemMetadataMap().put(sysMeta.getIdentifier(), sysMeta);
1759
            MetacatSolrIndex.getInstance().submit(sysMeta.getIdentifier(), sysMeta, null, true);
1760
        } catch (Exception e) {
1761
            throw new ServiceFailure("4862", e.getMessage());
1762
            //logMetacat.warn("D1NodeService.updateSystemMetadataWithoutLock - we can't submit the change of the system metadata to the solr index since "+e.getMessage());
1763
        }
1764
    }
1765
    
1766
    /**
1767
     * Update the system metadata of the specified pid. The caller of this method should lock the system metadata in hazelcast server.
1768
     * @param session - the identity of the client which calls the method
1769
     * @param pid - the identifier of the object which will be updated
1770
     * @param sysmeta - the new system metadata  
1771
     * @return
1772
     * @throws NotImplemented
1773
     * @throws NotAuthorized
1774
     * @throws ServiceFailure
1775
     * @throws InvalidRequest
1776
     * @throws InvalidSystemMetadata
1777
     * @throws InvalidToken
1778
     */
1779
	protected boolean updateSystemMetadata(Session session, Identifier pid,
1780
			SystemMetadata sysmeta, boolean needUpdateModificationDate, SystemMetadata currentSysmeta, boolean fromCN) throws NotImplemented, NotAuthorized,
1781
			ServiceFailure, InvalidRequest, InvalidSystemMetadata, InvalidToken {
1782
		
1783
	  // The lock to be used for this identifier
1784
      Lock lock = null;
1785
     
1786
      // verify that guid == SystemMetadata.getIdentifier()
1787
      logMetacat.debug("Comparing guid|sysmeta_guid: " + pid.getValue() + 
1788
          "|" + sysmeta.getIdentifier().getValue());
1789
      
1790
      if (!pid.getValue().equals(sysmeta.getIdentifier().getValue())) {
1791
          throw new InvalidRequest("4863", 
1792
              "The identifier in method call (" + pid.getValue() + 
1793
              ") does not match identifier in system metadata (" +
1794
              sysmeta.getIdentifier().getValue() + ").");
1795
      }
1796
      //compare serial version.
1797
      
1798
      //check the sid
1799
      //SystemMetadata currentSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1800
      logMetacat.debug("The current dateUploaded is ============"+currentSysmeta.getDateUploaded());
1801
      logMetacat.debug("the dateUploaded in the new system metadata is "+sysmeta.getDateUploaded());
1802
      logMetacat.debug("The current dateUploaded is (by time) ============"+currentSysmeta.getDateUploaded().getTime());
1803
      logMetacat.debug("the dateUploaded in the new system metadata is (by time) "+sysmeta.getDateUploaded().getTime());
1804
      if(currentSysmeta == null ) {
1805
          //do we need throw an exception?
1806
          logMetacat.warn("D1NodeService.updateSystemMetadata: Currently there is no system metadata in this node associated with the pid "+pid.getValue());
1807
      } else {
1808
          
1809
          /*BigInteger newVersion = sysmeta.getSerialVersion();
1810
          if(newVersion == null) {
1811
              throw new InvalidRequest("4869", "The serial version can't be null in the new system metadata");
1812
          }
1813
          BigInteger currentVersion = currentSysmeta.getSerialVersion();
1814
          if(currentVersion != null && newVersion.compareTo(currentVersion) <= 0) {
1815
              throw new InvalidRequest("4869", "The serial version in the new system metadata is "+newVersion.toString()+
1816
                      " which is less than or equals the previous version "+currentVersion.toString()+". This is illegal in the updateSystemMetadata method.");
1817
          }*/
1818
          Identifier currentSid = currentSysmeta.getSeriesId();
1819
          if(currentSid != null) {
1820
              logMetacat.debug("In the branch that the sid is not null in the current system metadata and the current sid is "+currentSid.getValue());
1821
              //new sid must match the current sid
1822
              Identifier newSid = sysmeta.getSeriesId();
1823
              if (!isValidIdentifier(newSid)) {
1824
                  throw new InvalidRequest("4869", "The series id in the system metadata is invalid in the request.");
1825
              } else {
1826
                  if(!newSid.getValue().equals(currentSid.getValue())) {
1827
                      throw new InvalidRequest("4869", "The series id "+newSid.getValue() +" in the system metadata doesn't match the current sid "+currentSid.getValue());
1828
                  }
1829
              }
1830
          } else {
1831
              //current system metadata doesn't have a sid. So we can have those scenarios
1832
              //1. The new sid may be null as well
1833
              //2. If the new sid does exist, it may be an identifier which hasn't bee used.
1834
              //3. If the new sid does exist, it may be an sid which equals the SID it obsoletes
1835
              //4. If the new sid does exist, it may be an sid which equauls the SID it was obsoleted by
1836
              Identifier newSid = sysmeta.getSeriesId();
1837
              if(newSid != null) {
1838
                  //It matches the rules of the checkSidInModifyingSystemMetadata
1839
                  checkSidInModifyingSystemMetadata(sysmeta, "4956", "4868");
1840
              }
1841
          }
1842
          checkModifiedImmutableFields(currentSysmeta, sysmeta);
1843
          checkOneTimeSettableSysmMetaFields(currentSysmeta, sysmeta);
1844
          if(currentSysmeta.getObsoletes() == null && sysmeta.getObsoletes() != null) {
1845
              //we are setting a value to the obsoletes field, so we should make sure if there is not object obsoletes the value
1846
              String obsoletes = existsInObsoletes(sysmeta.getObsoletes());
1847
              if( obsoletes != null) {
1848
                  throw new InvalidSystemMetadata("4956", "There is an object with id "+obsoletes +
1849
                          " already obsoletes the pid "+sysmeta.getObsoletes().getValue() +". You can't set the object "+pid.getValue()+" to obsolete the pid "+sysmeta.getObsoletes().getValue()+" again.");
1850
              }
1851
          }
1852
          checkCircularObsoletesChain(sysmeta);
1853
          if(currentSysmeta.getObsoletedBy() == null && sysmeta.getObsoletedBy() != null) {
1854
              //we are setting a value to the obsoletedBy field, so we should make sure that the no another object obsoletes the pid we are updating. 
1855
              String obsoletedBy = existsInObsoletedBy(sysmeta.getObsoletedBy());
1856
              if( obsoletedBy != null) {
1857
                  throw new InvalidSystemMetadata("4956", "There is an object with id "+obsoletedBy +
1858
                          " already is obsoleted by the pid "+sysmeta.getObsoletedBy().getValue() +". You can't set the pid "+pid.getValue()+" to be obsoleted by the pid "+sysmeta.getObsoletedBy().getValue()+" again.");
1859
              }
1860
          }
1861
          checkCircularObsoletedByChain(sysmeta);
1862
      }
1863
      
1864
      // do the actual update
1865
      if(sysmeta.getArchived() != null && sysmeta.getArchived() == true && 
1866
                 ((currentSysmeta.getArchived() != null && currentSysmeta.getArchived() == false ) || currentSysmeta.getArchived() == null)) {
1867
          boolean logArchive = false;//we log it as the update system metadata event. So don't log it again.
1868
          if(fromCN) {
1869
              logMetacat.debug("D1Node.update - this is to archive a cn object "+pid.getValue());
1870
              try {
1871
                  archiveCNObject(logArchive, session, pid, sysmeta, needUpdateModificationDate);
1872
              } catch (NotFound e) {
1873
                  throw new InvalidRequest("4869", "Can't find the pid "+pid.getValue()+" for archive.");
1874
              }
1875
          } else {
1876
              logMetacat.debug("D1Node.update - this is to archive a MN object "+pid.getValue());
1877
              try {
1878
                  archiveObject(logArchive, session, pid, sysmeta, needUpdateModificationDate);
1879
              } catch (NotFound e) {
1880
                  throw new InvalidRequest("4869", "Can't find the pid "+pid.getValue()+" for archive.");
1881
              }
1882
          }
1883
      } else {
1884
          logMetacat.debug("D1Node.update - regularly update the system metadata of the pid "+pid.getValue());
1885
          updateSystemMetadataWithoutLock(sysmeta, needUpdateModificationDate);
1886
      }
1887

    
1888
      try {
1889
    	  String localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1890
    	  EventLog.getInstance().log(request.getRemoteAddr(), 
1891
    	          request.getHeader("User-Agent"), session.getSubject().getValue(), 
1892
    	          localId, "updateSystemMetadata");
1893
      } catch (McdbDocNotFoundException e) {
1894
    	  // do nothing, no localId to log with
1895
    	  logMetacat.warn("Could not log 'updateSystemMetadata' event because no localId was found for pid: " + pid.getValue());
1896
      } catch (SQLException e) {
1897
          logMetacat.warn("Could not log 'updateSystemMetadata' event because the localId couldn't be identified for the pid: " + pid.getValue());
1898
      }
1899
      return true;
1900
	}
1901
	
1902
	
1903
	/*
1904
	 * Check if the newMeta modifies an immutable field. 
1905
	 */
1906
	private void checkModifiedImmutableFields(SystemMetadata orgMeta, SystemMetadata newMeta) throws InvalidRequest{
1907
	    logMetacat.debug("in the start of the checkModifiedImmutableFields method");
1908
	    if(orgMeta != null && newMeta != null) {
1909
	        logMetacat.debug("in the checkModifiedImmutableFields method when the org and new system metadata is not null");
1910
	        if(newMeta.getIdentifier() == null) {
1911
	            throw new InvalidRequest("4869", "The new version of the system metadata is invalid since the identifier is null");
1912
	        }
1913
	        if(!orgMeta.getIdentifier().equals(newMeta.getIdentifier())) {
1914
	            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 "+
1915
	                  "different to the orginal one "+orgMeta.getIdentifier().getValue());
1916
	        }
1917
	        if(newMeta.getSize() == null) {
1918
	            throw new InvalidRequest("4869", "The new version of the system metadata is invalid since the size is null");
1919
	        }
1920
	        if(!orgMeta.getSize().equals(newMeta.getSize())) {
1921
	            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 "+
1922
	                      "different to the orginal one "+orgMeta.getSize().longValue());
1923
	        }
1924
	        if(newMeta.getChecksum()!= null && orgMeta.getChecksum() != null && !orgMeta.getChecksum().getValue().equals(newMeta.getChecksum().getValue())) {
1925
	            logMetacat.error("The request is trying to modify an immutable field in the SystemMeta: the new system meta's checksum "+newMeta.getChecksum().getValue()+" is "+
1926
                        "different to the orginal one "+orgMeta.getChecksum().getValue());
1927
	            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 "+
1928
                        "different to the orginal one "+orgMeta.getChecksum().getValue());
1929
	        }
1930
	        if(orgMeta.getSubmitter() != null) {
1931
	            logMetacat.debug("in the checkModifiedImmutableFields method and orgMeta.getSubmitter is not null and the orginal submiter is "+orgMeta.getSubmitter().getValue());
1932
	        }
1933
	        
1934
	        if(newMeta.getSubmitter() != null) {
1935
                logMetacat.debug("in the checkModifiedImmutableFields method and newMeta.getSubmitter is not null and the submiter in the new system metadata is "+newMeta.getSubmitter().getValue());
1936
            }
1937
	        if(orgMeta.getSubmitter() != null && newMeta.getSubmitter() != null && !orgMeta.getSubmitter().equals(newMeta.getSubmitter())) {
1938
	            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 "+
1939
                        "different to the orginal one "+orgMeta.getSubmitter().getValue());
1940
	        }
1941
	        
1942
	        if(orgMeta.getDateUploaded() != null && newMeta.getDateUploaded() != null && orgMeta.getDateUploaded().getTime() != newMeta.getDateUploaded().getTime()) {
1943
	            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 "+
1944
                        "different to the orginal one "+orgMeta.getDateUploaded());
1945
	        }
1946
	        
1947
	        if(orgMeta.getOriginMemberNode() != null && newMeta.getOriginMemberNode() != null && !orgMeta.getOriginMemberNode().equals(newMeta.getOriginMemberNode())) {
1948
	            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 "+
1949
                        "different to the orginal one "+orgMeta.getOriginMemberNode().getValue());
1950
	        }
1951
	        
1952
	        if (orgMeta.getOriginMemberNode() != null && newMeta.getOriginMemberNode() == null ) {
1953
	            throw new InvalidRequest("4869", "The request is trying to modify an immutable field in the SystemMeta: the new system meta's orginal member node is null and it "+" is "+
1954
                        "different to the orginal one "+orgMeta.getOriginMemberNode().getValue());
1955
	        }
1956
	        
1957
	        if(orgMeta.getSeriesId() != null && newMeta.getSeriesId() != null && !orgMeta.getSeriesId().equals(newMeta.getSeriesId())) {
1958
                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 "+
1959
                        "different to the orginal one "+orgMeta.getSeriesId().getValue());
1960
            }
1961
	        
1962
	    }
1963
	}
1964
	
1965
	/*
1966
	 * Some fields in the system metadata, such as obsoletes or obsoletedBy can be set only once. 
1967
	 * After set, they are not allowed to be changed.
1968
	 */
1969
	private void checkOneTimeSettableSysmMetaFields(SystemMetadata orgMeta, SystemMetadata newMeta) throws InvalidRequest {
1970
	    if(orgMeta.getObsoletedBy() != null ) {
1971
	        if(newMeta.getObsoletedBy() == null || !orgMeta.getObsoletedBy().equals(newMeta.getObsoletedBy())) {
1972
	            throw new InvalidRequest("4869", "The request is trying to reset the obsoletedBy field in the system metadata of the object "
1973
	                    + orgMeta.getIdentifier().getValue() +". This is illegal since the obsoletedBy filed is set, you can't change it again.");
1974
	        }
1975
        }
1976
	    if(orgMeta.getObsoletes() != null) {
1977
	        if(newMeta.getObsoletes() == null || !orgMeta.getObsoletes().equals(newMeta.getObsoletes())) {
1978
	            throw new InvalidRequest("4869", "The request is trying to reset the obsoletes field in the system metadata of the object"+
1979
	               orgMeta.getIdentifier().getValue()+". This is illegal since the obsoletes filed is set, you can't change it again.");
1980
	        }
1981
	    }
1982
	}
1983
	
1984
	
1985
	/**
1986
	 * Try to check the scenario of a circular obsoletes chain: 
1987
	 * A obsoletes B
1988
	 * B obsoletes C
1989
	 * C obsoletes A
1990
	 * @param sys
1991
	 * @throws InvalidRequest
1992
	 */
1993
	private void checkCircularObsoletesChain(SystemMetadata sys) throws InvalidRequest {
1994
	    if(sys != null && sys.getObsoletes() != null && sys.getObsoletes().getValue() != null && !sys.getObsoletes().getValue().trim().equals("")) {
1995
	        logMetacat.debug("D1NodeService.checkCircularObsoletesChain - the object "+sys.getIdentifier().getValue() +" obsoletes "+sys.getObsoletes().getValue());
1996
	        if(sys.getObsoletes().getValue().equals(sys.getIdentifier().getValue())) {
1997
	            // the obsoletes field points to itself and creates a circular chain
1998
	            throw new InvalidRequest("4869", "The obsoletes field and identifier of the system metadata has the same value "+sys.getObsoletes().getValue()+
1999
	                    ". This creates a circular chain and it is illegal.");
2000
	        } else {
2001
	            Vector <Identifier> pidList = new Vector<Identifier>();
2002
	            pidList.add(sys.getIdentifier());
2003
	            SystemMetadata obsoletesSym = HazelcastService.getInstance().getSystemMetadataMap().get(sys.getObsoletes());
2004
	            while (obsoletesSym != null && obsoletesSym.getObsoletes() != null && obsoletesSym.getObsoletes().getValue() != null && !obsoletesSym.getObsoletes().getValue().trim().equals("")) {
2005
	                pidList.add(obsoletesSym.getIdentifier());
2006
	                logMetacat.debug("D1NodeService.checkCircularObsoletesChain - the object "+obsoletesSym.getIdentifier().getValue() +" obsoletes "+obsoletesSym.getObsoletes().getValue());
2007
	                /*for(Identifier id: pidList) {
2008
	                    logMetacat.debug("D1NodeService.checkCircularObsoletesChain - the pid in the chanin"+id.getValue());
2009
	                }*/
2010
	                if(pidList.contains(obsoletesSym.getObsoletes())) {
2011
	                    logMetacat.error("D1NodeService.checkCircularObsoletesChain - when Metacat updated the system metadata of object "+sys.getIdentifier().getValue()+", it found the obsoletes field value "+sys.getObsoletes().getValue()+
2012
	                            " in its new system metadata creating a circular chain at the object "+obsoletesSym.getObsoletes().getValue()+". This is illegal");
2013
	                    throw new InvalidRequest("4869", "When Metacat updated the system metadata of object "+sys.getIdentifier().getValue()+", it found the obsoletes field value "+sys.getObsoletes().getValue()+
2014
                                " in its new system metadata creating a circular chain at the object "+obsoletesSym.getObsoletes().getValue()+". This is illegal");
2015
	                } else {
2016
	                    obsoletesSym = HazelcastService.getInstance().getSystemMetadataMap().get(obsoletesSym.getObsoletes());
2017
	                }
2018
	            }
2019
	        }
2020
	    }
2021
	}
2022
	
2023
	
2024
	/**
2025
     * Try to check the scenario of a circular obsoletedBy chain: 
2026
     * A obsoletedBy B
2027
     * B obsoletedBy C
2028
     * C obsoletedBy A
2029
     * @param sys
2030
     * @throws InvalidRequest
2031
     */
2032
    private void checkCircularObsoletedByChain(SystemMetadata sys) throws InvalidRequest {
2033
        if(sys != null && sys.getObsoletedBy() != null && sys.getObsoletedBy().getValue() != null && !sys.getObsoletedBy().getValue().trim().equals("")) {
2034
            logMetacat.debug("D1NodeService.checkCircularObsoletedByChain - the object "+sys.getIdentifier().getValue() +" is obsoletedBy "+sys.getObsoletedBy().getValue());
2035
            if(sys.getObsoletedBy().getValue().equals(sys.getIdentifier().getValue())) {
2036
                // the obsoletedBy field points to itself and creates a circular chain
2037
                throw new InvalidRequest("4869", "The obsoletedBy field and identifier of the system metadata has the same value "+sys.getObsoletedBy().getValue()+
2038
                        ". This creates a circular chain and it is illegal.");
2039
            } else {
2040
                Vector <Identifier> pidList = new Vector<Identifier>();
2041
                pidList.add(sys.getIdentifier());
2042
                SystemMetadata obsoletedBySym = HazelcastService.getInstance().getSystemMetadataMap().get(sys.getObsoletedBy());
2043
                while (obsoletedBySym != null && obsoletedBySym.getObsoletedBy() != null && obsoletedBySym.getObsoletedBy().getValue() != null && !obsoletedBySym.getObsoletedBy().getValue().trim().equals("")) {
2044
                    pidList.add(obsoletedBySym.getIdentifier());
2045
                    logMetacat.debug("D1NodeService.checkCircularObsoletedByChain - the object "+obsoletedBySym.getIdentifier().getValue() +" is obsoletedBy "+obsoletedBySym.getObsoletedBy().getValue());
2046
                    /*for(Identifier id: pidList) {
2047
                        logMetacat.debug("D1NodeService.checkCircularObsoletedByChain - the pid in the chanin"+id.getValue());
2048
                    }*/
2049
                    if(pidList.contains(obsoletedBySym.getObsoletedBy())) {
2050
                        logMetacat.error("D1NodeService.checkCircularObsoletedByChain - When Metacat updated the system metadata of object "+sys.getIdentifier().getValue()+", it found the obsoletedBy field value "+sys.getObsoletedBy().getValue()+
2051
                                " in its new system metadata creating a circular chain at the object "+obsoletedBySym.getObsoletedBy().getValue()+". This is illegal");
2052
                        throw new InvalidRequest("4869",  "When Metacat updated the system metadata of object "+sys.getIdentifier().getValue()+", it found the obsoletedBy field value "+sys.getObsoletedBy().getValue()+
2053
                                " in its new system metadata creating a circular chain at the object "+obsoletedBySym.getObsoletedBy().getValue()+". This is illegal");
2054
                    } else {
2055
                        obsoletedBySym = HazelcastService.getInstance().getSystemMetadataMap().get(obsoletedBySym.getObsoletedBy());
2056
                    }
2057
                }
2058
            }
2059
        }
2060
    }
2061
  
2062
  /**
2063
   * Given a Permission, returns a list of all permissions that it encompasses
2064
   * Permissions are hierarchical so that WRITE also allows READ.
2065
   * @param permission
2066
   * @return list of included Permissions for the given permission
2067
   */
2068
  protected static List<Permission> expandPermissions(Permission permission) {
2069
	  	List<Permission> expandedPermissions = new ArrayList<Permission>();
2070
	    if (permission.equals(Permission.READ)) {
2071
	    	expandedPermissions.add(Permission.READ);
2072
	    }
2073
	    if (permission.equals(Permission.WRITE)) {
2074
	    	expandedPermissions.add(Permission.READ);
2075
	    	expandedPermissions.add(Permission.WRITE);
2076
	    }
2077
	    if (permission.equals(Permission.CHANGE_PERMISSION)) {
2078
	    	expandedPermissions.add(Permission.READ);
2079
	    	expandedPermissions.add(Permission.WRITE);
2080
	    	expandedPermissions.add(Permission.CHANGE_PERMISSION);
2081
	    }
2082
	    return expandedPermissions;
2083
  }
2084

    
2085
  /*
2086
   * Write a stream to a file
2087
   * 
2088
   * @param dir - the directory to write to
2089
   * @param fileName - the file name to write to
2090
   * @param data - the object bytes as an input stream
2091
   * 
2092
   * @return newFile - the new file created
2093
   * 
2094
   * @throws ServiceFailure
2095
   */
2096
  private File writeStreamToFile(File dir, String fileName, InputStream dataStream) 
2097
    throws ServiceFailure {
2098
    
2099
    File newFile = new File(dir, fileName);
2100
    logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
2101

    
2102
    try {
2103
        if (newFile.createNewFile()) {
2104
          // write data stream to desired file
2105
          OutputStream os = new FileOutputStream(newFile);
2106
          long length = IOUtils.copyLarge(dataStream, os);
2107
          os.flush();
2108
          os.close();
2109
        } else {
2110
          logMetacat.debug("File creation failed, or file already exists.");
2111
          throw new ServiceFailure("1190", "File already exists: " + fileName);
2112
        }
2113
    } catch (FileNotFoundException e) {
2114
      logMetacat.debug("FNF: " + e.getMessage());
2115
      throw new ServiceFailure("1190", "File not found: " + fileName + " " 
2116
                + e.getMessage());
2117
    } catch (IOException e) {
2118
      logMetacat.debug("IOE: " + e.getMessage());
2119
      throw new ServiceFailure("1190", "File was not written: " + fileName 
2120
                + " " + e.getMessage());
2121
    } finally {
2122
        IOUtils.closeQuietly(dataStream);
2123
    }
2124

    
2125
    return newFile;
2126
  }
2127

    
2128
  /*
2129
   * Returns a list of nodes that have been registered with the DataONE infrastructure
2130
   * that match the given session subject
2131
   * @return nodes - List of nodes from the registry with a matching session subject
2132
   * 
2133
   * @throws ServiceFailure
2134
   * @throws NotImplemented
2135
   */
2136
  protected List<Node> listNodesBySubject(Subject subject) 
2137
      throws ServiceFailure, NotImplemented {
2138
      List<Node> nodeList = new ArrayList<Node>();
2139
      
2140
      CNode cn = D1Client.getCN();
2141
      List<Node> nodes = cn.listNodes().getNodeList();
2142
      
2143
      // find the node in the node list
2144
      for ( Node node : nodes ) {
2145
          
2146
          List<Subject> nodeSubjects = node.getSubjectList();
2147
          if (nodeSubjects != null) {    
2148
	          // check if the session subject is in the node subject list
2149
	          for (Subject nodeSubject : nodeSubjects) {
2150
	              if ( nodeSubject.equals(subject) ) { // subject of session == node subject
2151
	                  nodeList.add(node);  
2152
	              }                              
2153
	          }
2154
          }
2155
      }
2156
      
2157
      return nodeList;
2158
      
2159
  }
2160

    
2161
  /**
2162
   * Archives an object, where the object is either a 
2163
   * data object or a science metadata object.
2164
   * Note: it doesn't check the authorization; it doesn't lock the system metadata;it only accept pid.
2165
   * @param session - the Session object containing the credentials for the Subject
2166
   * @param pid - The object identifier to be archived
2167
   * 
2168
   * @return pid - the identifier of the object used for the archiving
2169
   * 
2170
   * @throws InvalidToken
2171
   * @throws ServiceFailure
2172
   * @throws NotAuthorized
2173
   * @throws NotFound
2174
   * @throws NotImplemented
2175
   * @throws InvalidRequest
2176
   */
2177
  protected Identifier archiveObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2178
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2179

    
2180
      String localId = null;
2181
      boolean allowed = false;
2182
      String username = Constants.SUBJECT_PUBLIC;
2183
      if (session == null) {
2184
      	throw new InvalidToken("1330", "No session has been provided");
2185
      } else {
2186
          username = session.getSubject().getValue();
2187
      }
2188
      // do we have a valid pid?
2189
      if (pid == null || pid.getValue().trim().equals("")) {
2190
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
2191
      }
2192
      
2193
      if(sysMeta == null) {
2194
          throw new NotFound("2911", "There is no system metadata associated with "+pid.getValue());
2195
      }
2196
      
2197
      // check for the existing identifier
2198
      try {
2199
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2200
      } catch (McdbDocNotFoundException e) {
2201
          throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
2202
      } catch (SQLException e) {
2203
          throw new ServiceFailure("1350", "The object with the provided identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
2204
      }
2205

    
2206

    
2207
          try {
2208
              // archive the document
2209
              DocumentImpl.delete(localId, null, null, null, false);
2210
              if(log) {
2211
                   try {
2212
                      EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
2213
                   } catch (Exception e) {
2214
                      logMetacat.warn("D1NodeService.archiveObject - can't log the delete event since "+e.getMessage());
2215
                   }
2216
              }
2217
             
2218
              
2219
              // archive it
2220
              sysMeta.setArchived(true);
2221
              if(needModifyDate) {
2222
                  sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2223
                  sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2224
              }
2225
              HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2226
              
2227
              // submit for indexing
2228
              // DocumentImpl call above should do this.
2229
              // see: https://projects.ecoinformatics.org/ecoinfo/issues/6030
2230
              //HazelcastService.getInstance().getIndexQueue().add(sysMeta);
2231
              
2232
          } catch (McdbDocNotFoundException e) {
2233
              try {
2234
                  AccessionNumber acc = new AccessionNumber(localId, "NOACTION");
2235
                  String docid = acc.getDocid();
2236
                  int rev = 1;
2237
                  if (acc.getRev() != null) {
2238
                    rev = (new Integer(acc.getRev()).intValue());
2239
                  }
2240
                  if(IdentifierManager.getInstance().existsInXmlLRevisionTable(docid, rev)) {
2241
                      //somehow the document is in the xml_revision table.
2242
                      // archive it
2243
                      sysMeta.setArchived(true);
2244
                      if(needModifyDate) {
2245
                          sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2246
                          sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2247
                      }
2248
                      HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2249
                  } else {
2250
                      throw new NotFound("1340", "The provided identifier "+ pid.getValue()+" is invalid");
2251
                  }
2252
              } catch (SQLException ee) {
2253
                  ee.printStackTrace();
2254
                  throw new NotFound("1340", "The provided identifier "+ pid.getValue()+" is invalid");
2255
              } catch (AccessionNumberException ee) {
2256
                  ee.printStackTrace();
2257
                  throw new NotFound("1340", "The provided identifier "+ pid.getValue()+" is invalid");
2258
              }
2259
          } catch (SQLException e) {
2260
              throw new ServiceFailure("1350", "There was a problem archiving the object." + "The error message was: " + e.getMessage());
2261

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

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

    
2269

    
2270
      return pid;
2271
  }
2272
  
2273
  /**
2274
   * Archive a object on cn and notify the replica. This method doesn't lock the system metadata map. The caller should lock it.
2275
   * This method doesn't check the authorization; this method only accept a pid.
2276
   * It wouldn't notify the replca that the system metadata has been changed.
2277
   * @param session
2278
   * @param pid
2279
   * @param sysMeta
2280
   * @param notifyReplica
2281
   * @return
2282
   * @throws InvalidToken
2283
   * @throws ServiceFailure
2284
   * @throws NotAuthorized
2285
   * @throws NotFound
2286
   * @throws NotImplemented
2287
   */
2288
  protected void archiveCNObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2289
          throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2290

    
2291
          String localId = null; // The corresponding docid for this pid
2292
          
2293
          // Check for the existing identifier
2294
          try {
2295
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2296
              archiveObject(log, session, pid, sysMeta, needModifyDate);
2297
          
2298
          } catch (McdbDocNotFoundException e) {
2299
              // This object is not registered in the identifier table. Assume it is of formatType DATA,
2300
              // and set the archive flag. (i.e. the *object* doesn't exist on the CN)
2301
              
2302
              try {
2303
                  if ( sysMeta != null ) {
2304
                    sysMeta.setArchived(true);
2305
                    if (needModifyDate) {
2306
                        sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2307
                        sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2308
                    }
2309
                    HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2310
                      
2311
                  } else {
2312
                      throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2313
                          ". Couldn't obtain the system metadata record.");
2314
                      
2315
                  }
2316
                  
2317
              } catch (RuntimeException re) {
2318
                  throw new ServiceFailure("4972", "Couldn't archive " + pid.getValue() + 
2319
                      ". The error message was: " + re.getMessage());
2320
                  
2321
              } 
2322

    
2323
          } catch (SQLException e) {
2324
              throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2325
                      ". The local id of the object with the identifier can't be identified since "+e.getMessage());
2326
          }
2327
          
2328
    }
2329
  
2330
  
2331
  /**
2332
   * A utility method for v1 api to check the specified identifier exists as a pid
2333
   * @param identifier  the specified identifier
2334
   * @param serviceFailureCode  the detail error code for the service failure exception
2335
   * @param noFoundCode  the detail error code for the not found exception
2336
   * @throws ServiceFailure
2337
   * @throws NotFound
2338
   */
2339
  public void checkV1SystemMetaPidExist(Identifier identifier, String serviceFailureCode, String serviceFailureMessage,  
2340
          String noFoundCode, String notFoundMessage) throws ServiceFailure, NotFound {
2341
      boolean exists = false;
2342
      try {
2343
          exists = IdentifierManager.getInstance().systemMetadataPIDExists(identifier);
2344
      } catch (SQLException e) {
2345
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+e.getMessage());
2346
      }
2347
      if(!exists) {
2348
         //the v1 method only handles a pid. so it should throw a not-found exception.
2349
          // check if the pid was deleted.
2350
          try {
2351
              String localId = IdentifierManager.getInstance().getLocalId(identifier.getValue());
2352
              if(EventLog.getInstance().isDeleted(localId)) {
2353
                  notFoundMessage=notFoundMessage+" "+DELETEDMESSAGE;
2354
              } 
2355
            } catch (Exception e) {
2356
              logMetacat.info("Couldn't determine if the not-found identifier "+identifier.getValue()+" was deleted since "+e.getMessage());
2357
            }
2358
            throw new NotFound(noFoundCode, notFoundMessage);
2359
      }
2360
  }
2361
  
2362
  /**
2363
   * Utility method to get the PID for an SID. If the specified identifier is not an SID
2364
   * , null will be returned.
2365
   * @param sid  the specified sid
2366
   * @param serviceFailureCode  the detail error code for the service failure exception
2367
   * @return the pid for the sid. If the specified identifier is not an SID, null will be returned.
2368
   * @throws ServiceFailure
2369
   */
2370
  protected Identifier getPIDForSID(Identifier sid, String serviceFailureCode) throws ServiceFailure {
2371
      Identifier id = null;
2372
      String serviceFailureMessage = "The PID "+" couldn't be identified for the sid " + sid.getValue();
2373
      try {
2374
          //determine if the given pid is a sid or not.
2375
          if(IdentifierManager.getInstance().systemMetadataSIDExists(sid)) {
2376
              try {
2377
                  //set the header pid for the sid if the identifier is a sid.
2378
                  id = IdentifierManager.getInstance().getHeadPID(sid);
2379
              } catch (SQLException sqle) {
2380
                  throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+sqle.getMessage());
2381
              }
2382
              
2383
          }
2384
      } catch (SQLException e) {
2385
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage + " since "+e.getMessage());
2386
      }
2387
      return id;
2388
  }
2389

    
2390
  /*
2391
   * 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:
2392
   * A. If the sysmeta doesn't have an SID, nothing needs to be checked for the SID.
2393
   * B. If the sysmeta does have an SID, it may be an identifier which doesn't exist in the system.
2394
   * C. If the sysmeta does have an SID and it exists as an SID in the system, those scenarios are acceptable:
2395
   *    i. The sysmeta has an obsoletes field, the SID has the same value as the SID of the system metadata of the obsoleting pid.
2396
   *    ii. The sysmeta has an obsoletedBy field, the SID has the same value as the SID of the system metadata of the obsoletedBy pid. 
2397
   */
2398
  protected boolean checkSidInModifyingSystemMetadata(SystemMetadata sysmeta, String invalidSystemMetadataCode, String serviceFailureCode) throws InvalidSystemMetadata, ServiceFailure{
2399
      boolean pass = false;
2400
      if(sysmeta == null) {
2401
          throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The system metadata is null in the request.");
2402
      }
2403
      Identifier sid = sysmeta.getSeriesId();
2404
      if(sid != null) {
2405
          // the series id exists
2406
          if (!isValidIdentifier(sid)) {
2407
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id in the system metadata is invalid in the request.");
2408
          }
2409
          Identifier pid = sysmeta.getIdentifier();
2410
          if (!isValidIdentifier(pid)) {
2411
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The pid in the system metadata is invalid in the request.");
2412
          }
2413
          //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 )
2414
          if(sid.getValue().equals(pid.getValue())) {
2415
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
2416
          }
2417
          try {
2418
              if (IdentifierManager.getInstance().identifierExists(sid.getValue())) {
2419
                  //the sid exists in system
2420
                  if(sysmeta.getObsoletes() != null) {
2421
                      SystemMetadata obsoletesSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletes());
2422
                      if(obsoletesSysmeta != null) {
2423
                          Identifier obsoletesSid = obsoletesSysmeta.getSeriesId();
2424
                          if(obsoletesSid != null && obsoletesSid.getValue() != null && !obsoletesSid.getValue().trim().equals("")) {
2425
                              if(sid.getValue().equals(obsoletesSid.getValue())) {
2426
                                  pass = true;// the i of rule C
2427
                              }
2428
                          }
2429
                      } else {
2430
                           logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue()+
2431
                                                                         " which is the value of the obsoletes. So we can't check if the sid " +sid.getValue()+" is legitimate ");
2432
                      }
2433
                  }
2434
                  if(!pass) {
2435
                      // the sid doesn't match the sid of the obsoleting identifier. So we check the obsoletedBy
2436
                      if(sysmeta.getObsoletedBy() != null) {
2437
                          SystemMetadata obsoletedBySysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletedBy());
2438
                          if(obsoletedBySysmeta != null) {
2439
                              Identifier obsoletedBySid = obsoletedBySysmeta.getSeriesId();
2440
                              if(obsoletedBySid != null && obsoletedBySid.getValue() != null && !obsoletedBySid.getValue().trim().equals("")) {
2441
                                  if(sid.getValue().equals(obsoletedBySid.getValue())) {
2442
                                      pass = true;// the ii of the rule C
2443
                                  }
2444
                              }
2445
                          } else {
2446
                              logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue() 
2447
                                                                            +" which is the value of the obsoletedBy. So we can't check if the sid "+sid.getValue()+" is legitimate.");
2448
                          }
2449
                      }
2450
                  }
2451
                  if(!pass) {
2452
                      throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+
2453
                              " in the system metadata exists in the system. And it doesn't match either previous object's sid or the next object's sid.");
2454
                  }
2455
              } else {
2456
                  pass = true; //Rule B
2457
              }
2458
          } catch (SQLException e) {
2459
              throw new ServiceFailure(serviceFailureCode, "Can't determine if the sid in the system metadata is unique or not since "+e.getMessage());
2460
          }
2461
          
2462
      } else {
2463
          //no sid. Rule A.
2464
          pass = true;
2465
      }
2466
      return pass;
2467
      
2468
  }
2469
  
2470
  //@Override
2471
  public OptionList listViews(Session arg0) throws InvalidToken,
2472
          ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented {
2473
      OptionList views = new OptionList();
2474
      views.setKey("views");
2475
      views.setDescription("List of views for objects on the node");
2476
      Vector<String> skinNames = null;
2477
      try {
2478
          skinNames = SkinUtil.getSkinNames();
2479
      } catch (PropertyNotFoundException e) {
2480
          throw new ServiceFailure("2841", e.getMessage());
2481
      }
2482
      for (String skinName: skinNames) {
2483
          views.addOption(skinName);
2484
      }
2485
      return views;
2486
  }
2487
  
2488
  public OptionList listViews() throws InvalidToken,
2489
  ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented { 
2490
      return listViews(null);
2491
  }
2492

    
2493
  //@Override
2494
  public InputStream view(Session session, String format, Identifier pid)
2495
          throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
2496
          NotImplemented, NotFound {
2497
      InputStream resultInputStream = null;
2498
      
2499
      String serviceFailureCode = "2831";
2500
      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2501
      if(sid != null) {
2502
          pid = sid;
2503
      }
2504
      
2505
      SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2506
      InputStream object = this.get(session, pid);
2507

    
2508
      try {
2509
          // can only transform metadata, really
2510
          ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2511
          if (objectFormat.getFormatType().equals("METADATA")) {
2512
              // transform
2513
              DBTransform transformer = new DBTransform();
2514
              String documentContent = IOUtils.toString(object, "UTF-8");
2515
              String sourceType = objectFormat.getFormatId().getValue();
2516
              String targetType = "-//W3C//HTML//EN";
2517
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
2518
              Writer writer = new OutputStreamWriter(baos , "UTF-8");
2519
              // TODO: include more params?
2520
              Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2521
              String localId = null;
2522
              try {
2523
                  localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2524
              } catch (McdbDocNotFoundException e) {
2525
                  throw new NotFound("1020", e.getMessage());
2526
              }
2527
              params.put("qformat", new String[] {format});               
2528
              params.put("docid", new String[] {localId});
2529
              params.put("pid", new String[] {pid.getValue()});
2530
              transformer.transformXMLDocument(
2531
                      documentContent , 
2532
                      sourceType, 
2533
                      targetType , 
2534
                      format, 
2535
                      writer, 
2536
                      params, 
2537
                      null //sessionid
2538
                      );
2539
              
2540
              // finally, get the HTML back
2541
              resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2542
              ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2543
  
2544
          } else {
2545
              // just return the raw bytes
2546
              resultInputStream = object;
2547
          }
2548
      } catch (IOException e) {
2549
          // report as service failure
2550
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2551
          sf.initCause(e);
2552
          throw sf;
2553
      } catch (PropertyNotFoundException e) {
2554
          // report as service failure
2555
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2556
          sf.initCause(e);
2557
          throw sf;
2558
      } catch (SQLException e) {
2559
          // report as service failure
2560
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2561
          sf.initCause(e);
2562
          throw sf;
2563
      } catch (ClassNotFoundException e) {
2564
          // report as service failure
2565
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2566
          sf.initCause(e);
2567
          throw sf;
2568
      }
2569
      
2570
      return resultInputStream;
2571
  } 
2572
  
2573
  /*
2574
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2575
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2576
   * the guid of the first row.
2577
   */
2578
  protected String existsInObsoletes(Identifier id) throws InvalidRequest, ServiceFailure{
2579
      String guid = existsInFields("obsoletes", id);
2580
      return guid;
2581
  }
2582
  
2583
  /*
2584
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2585
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2586
   * the guid of the first row.
2587
   */
2588
  protected String existsInObsoletedBy(Identifier id) throws InvalidRequest, ServiceFailure{
2589
      String guid = existsInFields("obsoleted_by", id);
2590
      return guid;
2591
  }
2592

    
2593
  /*
2594
   * Determine if the given identifier exists in the given column in the system metadata table. 
2595
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2596
   * the guid of the first row.
2597
   */
2598
  private String existsInFields(String column, Identifier id) throws InvalidRequest, ServiceFailure {
2599
      String guid = null;
2600
      if(id == null ) {
2601
          throw new InvalidRequest("4863", "The given identifier is null and we can't determine if the guid exists in the field "+column+" in the systemmetadata table");
2602
      }
2603
      String sql = "SELECT guid FROM systemmetadata WHERE "+column+" = ?";
2604
      int serialNumber = -1;
2605
      DBConnection dbConn = null;
2606
      PreparedStatement stmt = null;
2607
      ResultSet result = null;
2608
      try {
2609
          dbConn = 
2610
                  DBConnectionPool.getDBConnection("D1NodeService.existsInFields");
2611
          serialNumber = dbConn.getCheckOutSerialNumber();
2612
          stmt = dbConn.prepareStatement(sql);
2613
          stmt.setString(1, id.getValue());
2614
          result = stmt.executeQuery();
2615
          if(result.next()) {
2616
              guid = result.getString(1);
2617
          }
2618
          stmt.close();
2619
      } catch (SQLException e) {
2620
          e.printStackTrace();
2621
          throw new ServiceFailure("4862","We can't determine if the id "+id.getValue()+" exists in field "+column+" in the systemmetadata table since "+e.getMessage());
2622
      } finally {
2623
          // Return database connection to the pool
2624
          DBConnectionPool.returnDBConnection(dbConn, serialNumber);
2625
          if(stmt != null) {
2626
              try {
2627
                  stmt.close();
2628
              } catch (SQLException e) {
2629
                  logMetacat.warn("We can close the PreparedStatment in D1NodeService.existsInFields since "+e.getMessage());
2630
              }
2631
          }
2632
          
2633
      }
2634
      return guid;
2635
      
2636
  }
2637
}
(2-2/8)