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-03-01 14:16:35 -0800 (Wed, 01 Mar 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.SubjectInfo;
76
import org.dataone.service.types.v2.Log;
77
import org.dataone.service.types.v2.Node;
78
import org.dataone.service.types.v2.OptionList;
79
import org.dataone.service.types.v1.Event;
80
import org.dataone.service.types.v1.NodeReference;
81
import org.dataone.service.types.v1.NodeType;
82
import org.dataone.service.types.v2.ObjectFormat;
83
import org.dataone.service.types.v1.Permission;
84
import org.dataone.service.types.v1.Replica;
85
import org.dataone.service.types.v1.Session;
86
import org.dataone.service.types.v1.Subject;
87
import org.dataone.service.types.v2.SystemMetadata;
88
import org.dataone.service.types.v1.util.AuthUtils;
89
import org.dataone.service.types.v1.util.ChecksumUtil;
90
import org.dataone.service.util.Constants;
91

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

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

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

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

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

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

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

    
203
      return describeResponse;
204

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1189
      } catch (Exception e) {
1190
          // convert Hazelcast RuntimeException to NotFound
1191
          logMetacat.error("An error occurred while getting system metadata for identifier " +
1192
              pid.getValue() + ". The error message was: " + e.getMessage(), e);
1193
          throw new ServiceFailure("1090", "Can't get the system metadata for " + pidStr+ " since "+e.getMessage());
1194
          
1195
      } 
1196
      
1197
      // throw not found if it was not found
1198
      if (systemMetadata == null) {
1199
          String localId = null;
1200
          String error = "No system metadata could be found for given PID: " + pidStr;
1201
          try {
1202
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1203
            
1204
           } catch (Exception e) {
1205
              logMetacat.warn("Couldn't find the local id for the pid "+pidStr);
1206
          }
1207
          
1208
          if(localId != null && EventLog.getInstance().isDeleted(localId)) {
1209
              error = error + ". "+DELETEDMESSAGE;
1210
          } else if (localId == null && EventLog.getInstance().isDeleted(pid.getValue())) {
1211
              error = error + ". "+DELETEDMESSAGE;
1212
          }
1213
          throw new NotFound("1800", error);
1214
      }
1215
          
1216
      // do we own it?
1217
      for (Subject s: subjects) {
1218
        logMetacat.debug("Comparing \t" + 
1219
                         systemMetadata.getRightsHolder().getValue() +
1220
                         " \tagainst \t" + s.getValue());
1221
          //includedSubjects.append(s.getValue() + "; ");
1222
          allowed = systemMetadata.getRightsHolder().equals(s);
1223
          if (allowed) {
1224
              return allowed;
1225
          } else {
1226
              //check if the rightHolder is a group name. If it is, any member of the group can be considered a the right holder.
1227
              allowed = expandRightsHolder(systemMetadata.getRightsHolder(), s);
1228
              if(allowed) {
1229
                  return allowed;
1230
              }
1231
          }
1232
      }    
1233
      
1234
      // otherwise check the access rules
1235
      try {
1236
          List<AccessRule> allows = systemMetadata.getAccessPolicy().getAllowList();
1237
          search: // label break
1238
          for (AccessRule accessRule: allows) {
1239
            for (Subject s: subjects) {
1240
              logMetacat.debug("Checking allow access rule for subject: " + s.getValue());
1241
              if (accessRule.getSubjectList().contains(s)) {
1242
                  logMetacat.debug("Access rule contains subject: " + s.getValue());
1243
                  for (Permission p: accessRule.getPermissionList()) {
1244
                      logMetacat.debug("Checking permission: " + p.xmlValue());
1245
                      expandedPermissions = expandPermissions(p);
1246
                      allowed = expandedPermissions.contains(permission);
1247
                      if (allowed) {
1248
                          logMetacat.info("Permission granted: " + p.xmlValue() + " to " + s.getValue());
1249
                          break search; //label break
1250
                      }
1251
                  }
1252
                  
1253
              }
1254
            }
1255
          }
1256
      } catch (Exception e) {
1257
          // catch all for errors - safe side should be to deny the access
1258
          logMetacat.error("Problem checking authorization - defaulting to deny", e);
1259
          allowed = false;
1260
        
1261
      }
1262
      return allowed;
1263
  }
1264
  
1265
  
1266
  /**
1267
   * Check if the given userSession is the member of the right holder group (if the right holder is a group subject).
1268
   * If the right holder is not a group, it will be false of course.
1269
   * @param rightHolder the subject of the right holder.
1270
   * @param userSession the subject will be compared
1271
   * @return true if the user session is a member of the right holder group; false otherwise.
1272
 * @throws NotImplemented 
1273
 * @throws ServiceFailure 
1274
 * @throws NotAuthorized 
1275
 * @throws InvalidToken 
1276
 * @throws InvalidRequest 
1277
   */
1278
  public static boolean expandRightsHolder(Subject rightHolder, Subject userSession) throws ServiceFailure, NotImplemented, InvalidRequest, InvalidToken, NotAuthorized {
1279
      boolean is = false;
1280
      if(rightHolder != null && userSession != null && rightHolder.getValue() != null && !rightHolder.getValue().trim().equals("") && userSession.getValue() != null && !userSession.getValue().trim().equals("")) {
1281
          CNode cn = D1Client.getCN();
1282
          logMetacat.debug("D1NodeService.expandRightHolder - after getting the cn node and cn node is "+cn.getNodeBaseServiceUrl());
1283
          String query= rightHolder.getValue();
1284
          int start =0;
1285
          int count=-1;
1286
          String status = null;
1287
          Session session = null;
1288
          SubjectInfo subjects = cn.listSubjects(session, query, status, start, count);
1289
          if(subjects != null) {
1290
              logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the returned result is not null");
1291
              List<Group> groups = subjects.getGroupList();
1292
              if(groups != null) {
1293
                  logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the returned result does include groups and the size of groups is "+groups.size());
1294
                  for(Group group : groups) {
1295
                      //logMetacat.debug("D1NodeService.expandRightHolder - group has the subject "+group.getSubject().getValue());
1296
                      if(group != null && group.getSubject() != null && group.getSubject().equals(rightHolder)) {
1297
                          logMetacat.debug("D1NodeService.expandRightHolder - there is a group in the list having the subjecct "+group.getSubject().getValue()+" which matches the right holder's subject "+rightHolder.getValue());
1298
                          List<Subject> members = group.getHasMemberList();
1299
                          if(members != null ){
1300
                              logMetacat.debug("D1NodeService.expandRightHolder - the group "+group.getSubject().getValue()+" in the cn has members");
1301
                              for(Subject member : members) {
1302
                                  logMetacat.debug("D1NodeService.expandRightHolder - compare the member "+member.getValue()+" with the user "+userSession.getValue());
1303
                                  if(member.getValue() != null && !member.getValue().trim().equals("") && userSession.getValue() != null && member.getValue().equals(userSession.getValue())) {
1304
                                      logMetacat.debug("D1NodeService.expandRightHolder - Find it! The member "+member.getValue()+" in the group "+group.getSubject().getValue()+" matches the user "+userSession.getValue());
1305
                                      is = true;
1306
                                      return is;
1307
                                  }
1308
                              }
1309
                          }
1310
                          break;//we found the group but can't find the member matches the user. so break it.
1311
                      }
1312
                  }
1313
              } else {
1314
                  logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the returned result does NOT have a group");
1315
              }
1316
          } else {
1317
              logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the returned result is null");
1318
          }
1319
          if(!is) {
1320
              logMetacat.debug("D1NodeService.expandRightHolder - We can NOT find any member in the group "+query+" (if it is a group) matches the user "+userSession.getValue());
1321
          }
1322
      } else {
1323
          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");
1324
      }
1325
     
1326
      return is;
1327
  }
1328
  /*
1329
   * parse a logEntry and get the relevant field from it
1330
   * 
1331
   * @param fieldname
1332
   * @param entry
1333
   * @return
1334
   */
1335
  private String getLogEntryField(String fieldname, String entry) {
1336
    String begin = "<" + fieldname + ">";
1337
    String end = "</" + fieldname + ">";
1338
    // logMetacat.debug("looking for " + begin + " and " + end +
1339
    // " in entry " + entry);
1340
    String s = entry.substring(entry.indexOf(begin) + begin.length(), entry
1341
        .indexOf(end));
1342
    logMetacat.debug("entry " + fieldname + " : " + s);
1343
    return s;
1344
  }
1345

    
1346
  /** 
1347
   * Determine if a given object should be treated as an XML science metadata
1348
   * object. 
1349
   * 
1350
   * @param sysmeta - the SystemMetadata describing the object
1351
   * @return true if the object should be treated as science metadata
1352
   */
1353
  public static boolean isScienceMetadata(SystemMetadata sysmeta) {
1354
    
1355
    ObjectFormat objectFormat = null;
1356
    boolean isScienceMetadata = false;
1357
    
1358
    try {
1359
      objectFormat = ObjectFormatCache.getInstance().getFormat(sysmeta.getFormatId());
1360
      if ( objectFormat.getFormatType().equals("METADATA") ) {
1361
      	isScienceMetadata = true;
1362
      	
1363
      }
1364
      
1365
       
1366
    /*} catch (ServiceFailure e) {
1367
      logMetacat.debug("There was a problem determining if the object identified by" + 
1368
          sysmeta.getIdentifier().getValue() + 
1369
          " is science metadata: " + e.getMessage());*/
1370
    
1371
    } catch (NotFound e) {
1372
      logMetacat.debug("There was a problem determining if the object identified by" + 
1373
          sysmeta.getIdentifier().getValue() + 
1374
          " is science metadata: " + e.getMessage());
1375
    
1376
    }
1377
    
1378
    return isScienceMetadata;
1379

    
1380
  }
1381
  
1382
  /**
1383
   * Check fro whitespace in the given pid.
1384
   * null pids are also invalid by default
1385
   * @param pid
1386
   * @return
1387
   */
1388
  public static boolean isValidIdentifier(Identifier pid) {
1389
	  if (pid != null && pid.getValue() != null && pid.getValue().length() > 0) {
1390
		  return !pid.getValue().matches(".*\\s+.*");
1391
	  } 
1392
	  return false;
1393
  }
1394
  
1395
  
1396
  /**
1397
   * Insert or update an XML document into Metacat
1398
   * 
1399
   * @param xml - the XML document to insert or update
1400
   * @param pid - the identifier to be used for the resulting object
1401
   * 
1402
   * @return localId - the resulting docid of the document created or updated
1403
   * 
1404
   */
1405
  public String insertOrUpdateDocument(InputStream xmlStream, String encoding,  Identifier pid, 
1406
    Session session, String insertOrUpdate, String formatId) 
1407
    throws ServiceFailure, IOException, PropertyNotFoundException{
1408
    
1409
  	logMetacat.debug("Starting to insert xml document...");
1410
    IdentifierManager im = IdentifierManager.getInstance();
1411

    
1412
    // generate pid/localId pair for sysmeta
1413
    String localId = null;
1414
    byte[] xmlBytes  = IOUtils.toByteArray(xmlStream);
1415
    IOUtils.closeQuietly(xmlStream);
1416
    String xmlStr = new String(xmlBytes, encoding);
1417
    if(insertOrUpdate.equals("insert")) {
1418
      localId = im.generateLocalId(pid.getValue(), 1);
1419
      
1420
    } else {
1421
      //localid should already exist in the identifier table, so just find it
1422
      try {
1423
        logMetacat.debug("Updating pid " + pid.getValue());
1424
        logMetacat.debug("looking in identifier table for pid " + pid.getValue());
1425
        
1426
        localId = im.getLocalId(pid.getValue());
1427
        
1428
        logMetacat.debug("localId: " + localId);
1429
        //increment the revision
1430
        String docid = localId.substring(0, localId.lastIndexOf("."));
1431
        String revS = localId.substring(localId.lastIndexOf(".") + 1, localId.length());
1432
        int rev = new Integer(revS).intValue();
1433
        rev++;
1434
        docid = docid + "." + rev;
1435
        localId = docid;
1436
        logMetacat.debug("incremented localId: " + localId);
1437
      
1438
      } catch(McdbDocNotFoundException e) {
1439
        throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument(): " +
1440
            "pid " + pid.getValue() + 
1441
            " should have been in the identifier table, but it wasn't: " + 
1442
            e.getMessage());
1443
      
1444
      } catch (SQLException e) {
1445
          throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument() -"+
1446
                     " couldn't identify if the pid "+pid.getValue()+" is in the identifier table since "+e.getMessage());
1447
      }
1448
      
1449
    }
1450

    
1451
    Hashtable<String, String[]> params = new Hashtable<String, String[]>();
1452
    String[] action = new String[1];
1453
    action[0] = insertOrUpdate;
1454
    params.put("action", action);
1455
    String[] docid = new String[1];
1456
    docid[0] = localId;
1457
    params.put("docid", docid);
1458
    String[] doctext = new String[1];
1459
    doctext[0] = xmlStr;
1460
    params.put("doctext", doctext);
1461
    
1462
    String username = Constants.SUBJECT_PUBLIC;
1463
    String[] groupnames = null;
1464
    if (session != null ) {
1465
    	username = session.getSubject().getValue();
1466
    	Set<Subject> otherSubjects = AuthUtils.authorizedClientSubjects(session);
1467
    	if (otherSubjects != null) {    		
1468
			groupnames = new String[otherSubjects.size()];
1469
			int i = 0;
1470
			Iterator<Subject> iter = otherSubjects.iterator();
1471
			while (iter.hasNext()) {
1472
				groupnames[i] = iter.next().getValue();
1473
				i++;
1474
			}
1475
    	}
1476
    }
1477
    
1478
    // do the insert or update action
1479
    handler = new MetacatHandler(new Timer());
1480
    String result = handler.handleInsertOrUpdateAction(request.getRemoteAddr(), request.getHeader("User-Agent"), null, 
1481
                        null, params, username, groupnames, false, false, xmlBytes, formatId);
1482
    boolean isScienceMetadata = true;
1483
    if(result.indexOf("<error>") != -1 || !IdentifierManager.getInstance().objectFileExists(localId, isScienceMetadata)) {
1484
    	String detailCode = "";
1485
    	if ( insertOrUpdate.equals("insert") ) {
1486
    		// make sure to remove the mapping so that subsequent attempts do not fail with IdentifierNotUnique
1487
    		im.removeMapping(pid.getValue(), localId);
1488
    		detailCode = "1190";
1489
    		
1490
    	} else if ( insertOrUpdate.equals("update") ) {
1491
    		detailCode = "1310";
1492
    		
1493
    	}
1494
    	logMetacat.error("D1NodeService.insertOrUpdateDocument - Error inserting or updating document: "+pid.getValue()+" since "+result);
1495
        throw new ServiceFailure(detailCode, 
1496
          "Error inserting or updating document: " +pid.getValue()+" since "+ result);
1497
    }
1498
    logMetacat.info("D1NodeService.insertOrUpdateDocument - Finsished inserting xml document with local id " + localId +" and its pid is "+pid.getValue());
1499
    
1500
    return localId;
1501
  }
1502
  
1503
  /**
1504
   * Insert a data document
1505
   * 
1506
   * @param object
1507
   * @param pid
1508
   * @param sessionData
1509
   * @throws ServiceFailure
1510
   * @returns localId of the data object inserted
1511
   */
1512
  public String insertDataObject(InputStream object, Identifier pid, 
1513
          Session session) throws ServiceFailure {
1514
      
1515
    String username = Constants.SUBJECT_PUBLIC;
1516
    String[] groupnames = null;
1517
    if (session != null ) {
1518
    	username = session.getSubject().getValue();
1519
    	Set<Subject> otherSubjects = AuthUtils.authorizedClientSubjects(session);
1520
    	if (otherSubjects != null) {    		
1521
			groupnames = new String[otherSubjects.size()];
1522
			int i = 0;
1523
			Iterator<Subject> iter = otherSubjects.iterator();
1524
			while (iter.hasNext()) {
1525
				groupnames[i] = iter.next().getValue();
1526
				i++;
1527
			}
1528
    	}
1529
    }
1530
  
1531
    // generate pid/localId pair for object
1532
    logMetacat.debug("Generating a pid/localId mapping");
1533
    IdentifierManager im = IdentifierManager.getInstance();
1534
    String localId = im.generateLocalId(pid.getValue(), 1);
1535
  
1536
    // Save the data file to disk using "localId" as the name
1537
    String datafilepath = null;
1538
	try {
1539
		datafilepath = PropertyService.getProperty("application.datafilepath");
1540
	} catch (PropertyNotFoundException e) {
1541
		ServiceFailure sf = new ServiceFailure("1190", "Lookup data file path" + e.getMessage());
1542
		sf.initCause(e);
1543
		throw sf;
1544
	}
1545
    boolean locked = false;
1546
	try {
1547
		locked = DocumentImpl.getDataFileLockGrant(localId);
1548
	} catch (Exception e) {
1549
		ServiceFailure sf = new ServiceFailure("1190", "Could not lock file for writing:" + e.getMessage());
1550
		sf.initCause(e);
1551
		throw sf;
1552
	}
1553

    
1554
    logMetacat.debug("Case DATA: starting to write to disk.");
1555
	if (locked) {
1556

    
1557
          File dataDirectory = new File(datafilepath);
1558
          dataDirectory.mkdirs();
1559
  
1560
          File newFile = writeStreamToFile(dataDirectory, localId, object);
1561
  
1562
          // TODO: Check that the file size matches SystemMetadata
1563
          // long size = newFile.length();
1564
          // if (size == 0) {
1565
          //     throw new IOException("Uploaded file is 0 bytes!");
1566
          // }
1567
  
1568
          // Register the file in the database (which generates an exception
1569
          // if the localId is not acceptable or other untoward things happen
1570
          try {
1571
            logMetacat.debug("Registering document...");
1572
            DocumentImpl.registerDocument(localId, "BIN", localId,
1573
                    username, groupnames);
1574
            logMetacat.debug("Registration step completed.");
1575
            
1576
          } catch (SQLException e) {
1577
            //newFile.delete();
1578
            logMetacat.debug("SQLE: " + e.getMessage());
1579
            e.printStackTrace(System.out);
1580
            throw new ServiceFailure("1190", "Registration failed: " + 
1581
            		e.getMessage());
1582
            
1583
          } catch (AccessionNumberException e) {
1584
            //newFile.delete();
1585
            logMetacat.debug("ANE: " + e.getMessage());
1586
            e.printStackTrace(System.out);
1587
            throw new ServiceFailure("1190", "Registration failed: " + 
1588
            	e.getMessage());
1589
            
1590
          } catch (Exception e) {
1591
            //newFile.delete();
1592
            logMetacat.debug("Exception: " + e.getMessage());
1593
            e.printStackTrace(System.out);
1594
            throw new ServiceFailure("1190", "Registration failed: " + 
1595
            	e.getMessage());
1596
          }
1597
  
1598
          logMetacat.debug("Logging the creation event.");
1599
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, "create");
1600
  
1601
          // Schedule replication for this data file, the "insert" action is important here!
1602
          logMetacat.debug("Scheduling replication.");
1603
          ForceReplicationHandler frh = new ForceReplicationHandler(localId, "insert", false, null);
1604
      }
1605
      
1606
      return localId;
1607
    
1608
  }
1609

    
1610
  /**
1611
   * Insert a systemMetadata document and return its localId
1612
   */
1613
  public void insertSystemMetadata(SystemMetadata sysmeta) 
1614
      throws ServiceFailure {
1615
      
1616
  	  logMetacat.debug("Starting to insert SystemMetadata...");
1617
      sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
1618
      logMetacat.debug("Inserting new system metadata with modified date " + 
1619
          sysmeta.getDateSysMetadataModified());
1620
      
1621
      //insert the system metadata
1622
      try {
1623
        // note: the calling subclass handles the map hazelcast lock/unlock
1624
      	HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
1625
      	// submit for indexing
1626
        MetacatSolrIndex.getInstance().submit(sysmeta.getIdentifier(), sysmeta, null, true);
1627
      } catch (Exception e) {
1628
          throw new ServiceFailure("1190", e.getMessage());
1629
          
1630
	    }  
1631
  }
1632
  
1633
  /**
1634
   * Retrieve the list of objects present on the MN that match the calling parameters
1635
   * 
1636
   * @param session - the Session object containing the credentials for the Subject
1637
   * @param startTime - Specifies the beginning of the time range from which 
1638
   *                    to return object (>=)
1639
   * @param endTime - Specifies the beginning of the time range from which 
1640
   *                  to return object (>=)
1641
   * @param objectFormat - Restrict results to the specified object format
1642
   * @param replicaStatus - Indicates if replicated objects should be returned in the list
1643
   * @param start - The zero-based index of the first value, relative to the 
1644
   *                first record of the resultset that matches the parameters.
1645
   * @param count - The maximum number of entries that should be returned in 
1646
   *                the response. The Member Node may return less entries 
1647
   *                than specified in this value.
1648
   * 
1649
   * @return objectList - the list of objects matching the criteria
1650
   * 
1651
   * @throws InvalidToken
1652
   * @throws ServiceFailure
1653
   * @throws NotAuthorized
1654
   * @throws InvalidRequest
1655
   * @throws NotImplemented
1656
   */
1657
  public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Identifier identifier, NodeReference nodeId, Integer start,
1658
          Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
1659

    
1660
      ObjectList objectList = null;
1661

    
1662
      try {
1663
          // safeguard against large requests
1664
          if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
1665
              count = MAXIMUM_DB_RECORD_COUNT;
1666
          }
1667
          boolean isSid = false;
1668
          if(identifier != null) {
1669
              isSid = IdentifierManager.getInstance().systemMetadataSIDExists(identifier);
1670
          }
1671
          objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, nodeId, start, count, identifier, isSid);
1672
      } catch (Exception e) {
1673
          throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1674
      }
1675

    
1676
      return objectList;
1677
  }
1678

    
1679

    
1680
  /**
1681
   * Update a systemMetadata document
1682
   * 
1683
   * @param sysMeta - the system metadata object in the system to update
1684
   */
1685
    protected void updateSystemMetadata(SystemMetadata sysMeta)
1686
        throws ServiceFailure {
1687
        logMetacat.debug("D1NodeService.updateSystemMetadata() called.");
1688
        try {
1689
            HazelcastService.getInstance().getSystemMetadataMap().lock(sysMeta.getIdentifier());
1690
            boolean needUpdateModificationDate = true;
1691
            updateSystemMetadataWithoutLock(sysMeta, needUpdateModificationDate);
1692
        } catch (Exception e) {
1693
            throw new ServiceFailure("4862", e.getMessage());
1694
        } finally {
1695
            HazelcastService.getInstance().getSystemMetadataMap().unlock(sysMeta.getIdentifier());
1696

    
1697
        }
1698

    
1699
    }
1700
    
1701
    /**
1702
     * Update system metadata without locking the system metadata in hazelcast server. So the caller should lock it first. 
1703
     * @param sysMeta
1704
     * @param needUpdateModificationDate
1705
     * @throws ServiceFailure
1706
     */
1707
    private void updateSystemMetadataWithoutLock(SystemMetadata sysMeta, boolean needUpdateModificationDate) throws ServiceFailure {
1708
        logMetacat.debug("D1NodeService.updateSystemMetadataWithoutLock() called.");
1709
        if(needUpdateModificationDate) {
1710
            logMetacat.debug("D1NodeService.updateSystemMetadataWithoutLock() - update the modification date.");
1711
            sysMeta.setDateSysMetadataModified(new Date());
1712
        }
1713
        
1714
        // submit for indexing
1715
        try {
1716
            HazelcastService.getInstance().getSystemMetadataMap().put(sysMeta.getIdentifier(), sysMeta);
1717
            MetacatSolrIndex.getInstance().submit(sysMeta.getIdentifier(), sysMeta, null, true);
1718
        } catch (Exception e) {
1719
            throw new ServiceFailure("4862", e.getMessage());
1720
            //logMetacat.warn("D1NodeService.updateSystemMetadataWithoutLock - we can't submit the change of the system metadata to the solr index since "+e.getMessage());
1721
        }
1722
    }
1723
    
1724
    /**
1725
     * Update the system metadata of the specified pid. The caller of this method should lock the system metadata in hazelcast server.
1726
     * @param session - the identity of the client which calls the method
1727
     * @param pid - the identifier of the object which will be updated
1728
     * @param sysmeta - the new system metadata  
1729
     * @return
1730
     * @throws NotImplemented
1731
     * @throws NotAuthorized
1732
     * @throws ServiceFailure
1733
     * @throws InvalidRequest
1734
     * @throws InvalidSystemMetadata
1735
     * @throws InvalidToken
1736
     */
1737
	protected boolean updateSystemMetadata(Session session, Identifier pid,
1738
			SystemMetadata sysmeta, boolean needUpdateModificationDate, SystemMetadata currentSysmeta, boolean fromCN) throws NotImplemented, NotAuthorized,
1739
			ServiceFailure, InvalidRequest, InvalidSystemMetadata, InvalidToken {
1740
		
1741
	  // The lock to be used for this identifier
1742
      Lock lock = null;
1743
     
1744
      // verify that guid == SystemMetadata.getIdentifier()
1745
      logMetacat.debug("Comparing guid|sysmeta_guid: " + pid.getValue() + 
1746
          "|" + sysmeta.getIdentifier().getValue());
1747
      
1748
      if (!pid.getValue().equals(sysmeta.getIdentifier().getValue())) {
1749
          throw new InvalidRequest("4863", 
1750
              "The identifier in method call (" + pid.getValue() + 
1751
              ") does not match identifier in system metadata (" +
1752
              sysmeta.getIdentifier().getValue() + ").");
1753
      }
1754
      //compare serial version.
1755
      
1756
      //check the sid
1757
      //SystemMetadata currentSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1758
      logMetacat.debug("The current dateUploaded is ============"+currentSysmeta.getDateUploaded());
1759
      logMetacat.debug("the dateUploaded in the new system metadata is "+sysmeta.getDateUploaded());
1760
      logMetacat.debug("The current dateUploaded is (by time) ============"+currentSysmeta.getDateUploaded().getTime());
1761
      logMetacat.debug("the dateUploaded in the new system metadata is (by time) "+sysmeta.getDateUploaded().getTime());
1762
      if(currentSysmeta == null ) {
1763
          //do we need throw an exception?
1764
          logMetacat.warn("D1NodeService.updateSystemMetadata: Currently there is no system metadata in this node associated with the pid "+pid.getValue());
1765
      } else {
1766
          
1767
          /*BigInteger newVersion = sysmeta.getSerialVersion();
1768
          if(newVersion == null) {
1769
              throw new InvalidRequest("4869", "The serial version can't be null in the new system metadata");
1770
          }
1771
          BigInteger currentVersion = currentSysmeta.getSerialVersion();
1772
          if(currentVersion != null && newVersion.compareTo(currentVersion) <= 0) {
1773
              throw new InvalidRequest("4869", "The serial version in the new system metadata is "+newVersion.toString()+
1774
                      " which is less than or equals the previous version "+currentVersion.toString()+". This is illegal in the updateSystemMetadata method.");
1775
          }*/
1776
          Identifier currentSid = currentSysmeta.getSeriesId();
1777
          if(currentSid != null) {
1778
              logMetacat.debug("In the branch that the sid is not null in the current system metadata and the current sid is "+currentSid.getValue());
1779
              //new sid must match the current sid
1780
              Identifier newSid = sysmeta.getSeriesId();
1781
              if (!isValidIdentifier(newSid)) {
1782
                  throw new InvalidRequest("4869", "The series id in the system metadata is invalid in the request.");
1783
              } else {
1784
                  if(!newSid.getValue().equals(currentSid.getValue())) {
1785
                      throw new InvalidRequest("4869", "The series id "+newSid.getValue() +" in the system metadata doesn't match the current sid "+currentSid.getValue());
1786
                  }
1787
              }
1788
          } else {
1789
              //current system metadata doesn't have a sid. So we can have those scenarios
1790
              //1. The new sid may be null as well
1791
              //2. If the new sid does exist, it may be an identifier which hasn't bee used.
1792
              //3. If the new sid does exist, it may be an sid which equals the SID it obsoletes
1793
              //4. If the new sid does exist, it may be an sid which equauls the SID it was obsoleted by
1794
              Identifier newSid = sysmeta.getSeriesId();
1795
              if(newSid != null) {
1796
                  //It matches the rules of the checkSidInModifyingSystemMetadata
1797
                  checkSidInModifyingSystemMetadata(sysmeta, "4956", "4868");
1798
              }
1799
          }
1800
          checkModifiedImmutableFields(currentSysmeta, sysmeta);
1801
          checkOneTimeSettableSysmMetaFields(currentSysmeta, sysmeta);
1802
          if(currentSysmeta.getObsoletes() == null && sysmeta.getObsoletes() != null) {
1803
              //we are setting a value to the obsoletes field, so we should make sure if there is not object obsoletes the value
1804
              String obsoletes = existsInObsoletes(sysmeta.getObsoletes());
1805
              if( obsoletes != null) {
1806
                  throw new InvalidSystemMetadata("4956", "There is an object with id "+obsoletes +
1807
                          " already obsoletes the pid "+sysmeta.getObsoletes().getValue() +". You can't set the object "+pid.getValue()+" to obsolete the pid "+sysmeta.getObsoletes().getValue()+" again.");
1808
              }
1809
          }
1810
          checkCircularObsoletesChain(sysmeta);
1811
          if(currentSysmeta.getObsoletedBy() == null && sysmeta.getObsoletedBy() != null) {
1812
              //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. 
1813
              String obsoletedBy = existsInObsoletedBy(sysmeta.getObsoletedBy());
1814
              if( obsoletedBy != null) {
1815
                  throw new InvalidSystemMetadata("4956", "There is an object with id "+obsoletedBy +
1816
                          " 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.");
1817
              }
1818
          }
1819
          checkCircularObsoletedByChain(sysmeta);
1820
      }
1821
      
1822
      // do the actual update
1823
      if(sysmeta.getArchived() != null && sysmeta.getArchived() == true && 
1824
                 ((currentSysmeta.getArchived() != null && currentSysmeta.getArchived() == false ) || currentSysmeta.getArchived() == null)) {
1825
          boolean logArchive = false;//we log it as the update system metadata event. So don't log it again.
1826
          if(fromCN) {
1827
              logMetacat.debug("D1Node.update - this is to archive a cn object "+pid.getValue());
1828
              try {
1829
                  archiveCNObject(logArchive, session, pid, sysmeta, needUpdateModificationDate);
1830
              } catch (NotFound e) {
1831
                  throw new InvalidRequest("4869", "Can't find the pid "+pid.getValue()+" for archive.");
1832
              }
1833
          } else {
1834
              logMetacat.debug("D1Node.update - this is to archive a MN object "+pid.getValue());
1835
              try {
1836
                  archiveObject(logArchive, session, pid, sysmeta, needUpdateModificationDate);
1837
              } catch (NotFound e) {
1838
                  throw new InvalidRequest("4869", "Can't find the pid "+pid.getValue()+" for archive.");
1839
              }
1840
          }
1841
      } else {
1842
          logMetacat.debug("D1Node.update - regularly update the system metadata of the pid "+pid.getValue());
1843
          updateSystemMetadataWithoutLock(sysmeta, needUpdateModificationDate);
1844
      }
1845

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

    
2043
  /*
2044
   * Write a stream to a file
2045
   * 
2046
   * @param dir - the directory to write to
2047
   * @param fileName - the file name to write to
2048
   * @param data - the object bytes as an input stream
2049
   * 
2050
   * @return newFile - the new file created
2051
   * 
2052
   * @throws ServiceFailure
2053
   */
2054
  private File writeStreamToFile(File dir, String fileName, InputStream dataStream) 
2055
    throws ServiceFailure {
2056
    
2057
    File newFile = new File(dir, fileName);
2058
    logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
2059

    
2060
    try {
2061
        if (newFile.createNewFile()) {
2062
          // write data stream to desired file
2063
          OutputStream os = new FileOutputStream(newFile);
2064
          long length = IOUtils.copyLarge(dataStream, os);
2065
          os.flush();
2066
          os.close();
2067
        } else {
2068
          logMetacat.debug("File creation failed, or file already exists.");
2069
          throw new ServiceFailure("1190", "File already exists: " + fileName);
2070
        }
2071
    } catch (FileNotFoundException e) {
2072
      logMetacat.debug("FNF: " + e.getMessage());
2073
      throw new ServiceFailure("1190", "File not found: " + fileName + " " 
2074
                + e.getMessage());
2075
    } catch (IOException e) {
2076
      logMetacat.debug("IOE: " + e.getMessage());
2077
      throw new ServiceFailure("1190", "File was not written: " + fileName 
2078
                + " " + e.getMessage());
2079
    } finally {
2080
        IOUtils.closeQuietly(dataStream);
2081
    }
2082

    
2083
    return newFile;
2084
  }
2085

    
2086
  /*
2087
   * Returns a list of nodes that have been registered with the DataONE infrastructure
2088
   * that match the given session subject
2089
   * @return nodes - List of nodes from the registry with a matching session subject
2090
   * 
2091
   * @throws ServiceFailure
2092
   * @throws NotImplemented
2093
   */
2094
  protected List<Node> listNodesBySubject(Subject subject) 
2095
      throws ServiceFailure, NotImplemented {
2096
      List<Node> nodeList = new ArrayList<Node>();
2097
      
2098
      CNode cn = D1Client.getCN();
2099
      List<Node> nodes = cn.listNodes().getNodeList();
2100
      
2101
      // find the node in the node list
2102
      for ( Node node : nodes ) {
2103
          
2104
          List<Subject> nodeSubjects = node.getSubjectList();
2105
          if (nodeSubjects != null) {    
2106
	          // check if the session subject is in the node subject list
2107
	          for (Subject nodeSubject : nodeSubjects) {
2108
	              if ( nodeSubject.equals(subject) ) { // subject of session == node subject
2109
	                  nodeList.add(node);  
2110
	              }                              
2111
	          }
2112
          }
2113
      }
2114
      
2115
      return nodeList;
2116
      
2117
  }
2118

    
2119
  /**
2120
   * Archives an object, where the object is either a 
2121
   * data object or a science metadata object.
2122
   * Note: it doesn't check the authorization; it doesn't lock the system metadata;it only accept pid.
2123
   * @param session - the Session object containing the credentials for the Subject
2124
   * @param pid - The object identifier to be archived
2125
   * 
2126
   * @return pid - the identifier of the object used for the archiving
2127
   * 
2128
   * @throws InvalidToken
2129
   * @throws ServiceFailure
2130
   * @throws NotAuthorized
2131
   * @throws NotFound
2132
   * @throws NotImplemented
2133
   * @throws InvalidRequest
2134
   */
2135
  protected Identifier archiveObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2136
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2137

    
2138
      String localId = null;
2139
      boolean allowed = false;
2140
      String username = Constants.SUBJECT_PUBLIC;
2141
      if (session == null) {
2142
      	throw new InvalidToken("1330", "No session has been provided");
2143
      } else {
2144
          username = session.getSubject().getValue();
2145
      }
2146
      // do we have a valid pid?
2147
      if (pid == null || pid.getValue().trim().equals("")) {
2148
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
2149
      }
2150
      
2151
      if(sysMeta == null) {
2152
          throw new NotFound("2911", "There is no system metadata associated with "+pid.getValue());
2153
      }
2154
      
2155
      // check for the existing identifier
2156
      try {
2157
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2158
      } catch (McdbDocNotFoundException e) {
2159
          throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
2160
      } catch (SQLException e) {
2161
          throw new ServiceFailure("1350", "The object with the provided identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
2162
      }
2163

    
2164

    
2165
          try {
2166
              // archive the document
2167
              DocumentImpl.delete(localId, null, null, null, false);
2168
              if(log) {
2169
                   try {
2170
                      EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
2171
                   } catch (Exception e) {
2172
                      logMetacat.warn("D1NodeService.archiveObject - can't log the delete event since "+e.getMessage());
2173
                   }
2174
              }
2175
             
2176
              
2177
              // archive it
2178
              sysMeta.setArchived(true);
2179
              if(needModifyDate) {
2180
                  sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2181
                  sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2182
              }
2183
              HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2184
              
2185
              // submit for indexing
2186
              // DocumentImpl call above should do this.
2187
              // see: https://projects.ecoinformatics.org/ecoinfo/issues/6030
2188
              //HazelcastService.getInstance().getIndexQueue().add(sysMeta);
2189
              
2190
          } catch (McdbDocNotFoundException e) {
2191
              try {
2192
                  AccessionNumber acc = new AccessionNumber(localId, "NOACTION");
2193
                  String docid = acc.getDocid();
2194
                  int rev = 1;
2195
                  if (acc.getRev() != null) {
2196
                    rev = (new Integer(acc.getRev()).intValue());
2197
                  }
2198
                  if(IdentifierManager.getInstance().existsInXmlLRevisionTable(docid, rev)) {
2199
                      //somehow the document is in the xml_revision table.
2200
                      // archive it
2201
                      sysMeta.setArchived(true);
2202
                      if(needModifyDate) {
2203
                          sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2204
                          sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2205
                      }
2206
                      HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2207
                  } else {
2208
                      throw new NotFound("1340", "The provided identifier "+ pid.getValue()+" is invalid");
2209
                  }
2210
              } catch (SQLException ee) {
2211
                  ee.printStackTrace();
2212
                  throw new NotFound("1340", "The provided identifier "+ pid.getValue()+" is invalid");
2213
              } catch (AccessionNumberException ee) {
2214
                  ee.printStackTrace();
2215
                  throw new NotFound("1340", "The provided identifier "+ pid.getValue()+" is invalid");
2216
              }
2217
          } catch (SQLException e) {
2218
              throw new ServiceFailure("1350", "There was a problem archiving the object." + "The error message was: " + e.getMessage());
2219

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

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

    
2227

    
2228
      return pid;
2229
  }
2230
  
2231
  /**
2232
   * Archive a object on cn and notify the replica. This method doesn't lock the system metadata map. The caller should lock it.
2233
   * This method doesn't check the authorization; this method only accept a pid.
2234
   * It wouldn't notify the replca that the system metadata has been changed.
2235
   * @param session
2236
   * @param pid
2237
   * @param sysMeta
2238
   * @param notifyReplica
2239
   * @return
2240
   * @throws InvalidToken
2241
   * @throws ServiceFailure
2242
   * @throws NotAuthorized
2243
   * @throws NotFound
2244
   * @throws NotImplemented
2245
   */
2246
  protected void archiveCNObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2247
          throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2248

    
2249
          String localId = null; // The corresponding docid for this pid
2250
          
2251
          // Check for the existing identifier
2252
          try {
2253
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2254
              archiveObject(log, session, pid, sysMeta, needModifyDate);
2255
          
2256
          } catch (McdbDocNotFoundException e) {
2257
              // This object is not registered in the identifier table. Assume it is of formatType DATA,
2258
              // and set the archive flag. (i.e. the *object* doesn't exist on the CN)
2259
              
2260
              try {
2261
                  if ( sysMeta != null ) {
2262
                    sysMeta.setArchived(true);
2263
                    if (needModifyDate) {
2264
                        sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2265
                        sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2266
                    }
2267
                    HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2268
                      
2269
                  } else {
2270
                      throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2271
                          ". Couldn't obtain the system metadata record.");
2272
                      
2273
                  }
2274
                  
2275
              } catch (RuntimeException re) {
2276
                  throw new ServiceFailure("4972", "Couldn't archive " + pid.getValue() + 
2277
                      ". The error message was: " + re.getMessage());
2278
                  
2279
              } 
2280

    
2281
          } catch (SQLException e) {
2282
              throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2283
                      ". The local id of the object with the identifier can't be identified since "+e.getMessage());
2284
          }
2285
          
2286
    }
2287
  
2288
  
2289
  /**
2290
   * A utility method for v1 api to check the specified identifier exists as a pid
2291
   * @param identifier  the specified identifier
2292
   * @param serviceFailureCode  the detail error code for the service failure exception
2293
   * @param noFoundCode  the detail error code for the not found exception
2294
   * @throws ServiceFailure
2295
   * @throws NotFound
2296
   */
2297
  public void checkV1SystemMetaPidExist(Identifier identifier, String serviceFailureCode, String serviceFailureMessage,  
2298
          String noFoundCode, String notFoundMessage) throws ServiceFailure, NotFound {
2299
      boolean exists = false;
2300
      try {
2301
          exists = IdentifierManager.getInstance().systemMetadataPIDExists(identifier);
2302
      } catch (SQLException e) {
2303
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+e.getMessage());
2304
      }
2305
      if(!exists) {
2306
         //the v1 method only handles a pid. so it should throw a not-found exception.
2307
          // check if the pid was deleted.
2308
          try {
2309
              String localId = IdentifierManager.getInstance().getLocalId(identifier.getValue());
2310
              if(EventLog.getInstance().isDeleted(localId)) {
2311
                  notFoundMessage=notFoundMessage+" "+DELETEDMESSAGE;
2312
              } 
2313
            } catch (Exception e) {
2314
              logMetacat.info("Couldn't determine if the not-found identifier "+identifier.getValue()+" was deleted since "+e.getMessage());
2315
            }
2316
            throw new NotFound(noFoundCode, notFoundMessage);
2317
      }
2318
  }
2319
  
2320
  /**
2321
   * Utility method to get the PID for an SID. If the specified identifier is not an SID
2322
   * , null will be returned.
2323
   * @param sid  the specified sid
2324
   * @param serviceFailureCode  the detail error code for the service failure exception
2325
   * @return the pid for the sid. If the specified identifier is not an SID, null will be returned.
2326
   * @throws ServiceFailure
2327
   */
2328
  protected Identifier getPIDForSID(Identifier sid, String serviceFailureCode) throws ServiceFailure {
2329
      Identifier id = null;
2330
      String serviceFailureMessage = "The PID "+" couldn't be identified for the sid " + sid.getValue();
2331
      try {
2332
          //determine if the given pid is a sid or not.
2333
          if(IdentifierManager.getInstance().systemMetadataSIDExists(sid)) {
2334
              try {
2335
                  //set the header pid for the sid if the identifier is a sid.
2336
                  id = IdentifierManager.getInstance().getHeadPID(sid);
2337
              } catch (SQLException sqle) {
2338
                  throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+sqle.getMessage());
2339
              }
2340
              
2341
          }
2342
      } catch (SQLException e) {
2343
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage + " since "+e.getMessage());
2344
      }
2345
      return id;
2346
  }
2347

    
2348
  /*
2349
   * 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:
2350
   * A. If the sysmeta doesn't have an SID, nothing needs to be checked for the SID.
2351
   * B. If the sysmeta does have an SID, it may be an identifier which doesn't exist in the system.
2352
   * C. If the sysmeta does have an SID and it exists as an SID in the system, those scenarios are acceptable:
2353
   *    i. The sysmeta has an obsoletes field, the SID has the same value as the SID of the system metadata of the obsoleting pid.
2354
   *    ii. The sysmeta has an obsoletedBy field, the SID has the same value as the SID of the system metadata of the obsoletedBy pid. 
2355
   */
2356
  protected boolean checkSidInModifyingSystemMetadata(SystemMetadata sysmeta, String invalidSystemMetadataCode, String serviceFailureCode) throws InvalidSystemMetadata, ServiceFailure{
2357
      boolean pass = false;
2358
      if(sysmeta == null) {
2359
          throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The system metadata is null in the request.");
2360
      }
2361
      Identifier sid = sysmeta.getSeriesId();
2362
      if(sid != null) {
2363
          // the series id exists
2364
          if (!isValidIdentifier(sid)) {
2365
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id in the system metadata is invalid in the request.");
2366
          }
2367
          Identifier pid = sysmeta.getIdentifier();
2368
          if (!isValidIdentifier(pid)) {
2369
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The pid in the system metadata is invalid in the request.");
2370
          }
2371
          //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 )
2372
          if(sid.getValue().equals(pid.getValue())) {
2373
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
2374
          }
2375
          try {
2376
              if (IdentifierManager.getInstance().identifierExists(sid.getValue())) {
2377
                  //the sid exists in system
2378
                  if(sysmeta.getObsoletes() != null) {
2379
                      SystemMetadata obsoletesSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletes());
2380
                      if(obsoletesSysmeta != null) {
2381
                          Identifier obsoletesSid = obsoletesSysmeta.getSeriesId();
2382
                          if(obsoletesSid != null && obsoletesSid.getValue() != null && !obsoletesSid.getValue().trim().equals("")) {
2383
                              if(sid.getValue().equals(obsoletesSid.getValue())) {
2384
                                  pass = true;// the i of rule C
2385
                              }
2386
                          }
2387
                      } else {
2388
                           logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue()+
2389
                                                                         " which is the value of the obsoletes. So we can't check if the sid " +sid.getValue()+" is legitimate ");
2390
                      }
2391
                  }
2392
                  if(!pass) {
2393
                      // the sid doesn't match the sid of the obsoleting identifier. So we check the obsoletedBy
2394
                      if(sysmeta.getObsoletedBy() != null) {
2395
                          SystemMetadata obsoletedBySysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletedBy());
2396
                          if(obsoletedBySysmeta != null) {
2397
                              Identifier obsoletedBySid = obsoletedBySysmeta.getSeriesId();
2398
                              if(obsoletedBySid != null && obsoletedBySid.getValue() != null && !obsoletedBySid.getValue().trim().equals("")) {
2399
                                  if(sid.getValue().equals(obsoletedBySid.getValue())) {
2400
                                      pass = true;// the ii of the rule C
2401
                                  }
2402
                              }
2403
                          } else {
2404
                              logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue() 
2405
                                                                            +" which is the value of the obsoletedBy. So we can't check if the sid "+sid.getValue()+" is legitimate.");
2406
                          }
2407
                      }
2408
                  }
2409
                  if(!pass) {
2410
                      throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+
2411
                              " in the system metadata exists in the system. And it doesn't match either previous object's sid or the next object's sid.");
2412
                  }
2413
              } else {
2414
                  pass = true; //Rule B
2415
              }
2416
          } catch (SQLException e) {
2417
              throw new ServiceFailure(serviceFailureCode, "Can't determine if the sid in the system metadata is unique or not since "+e.getMessage());
2418
          }
2419
          
2420
      } else {
2421
          //no sid. Rule A.
2422
          pass = true;
2423
      }
2424
      return pass;
2425
      
2426
  }
2427
  
2428
  //@Override
2429
  public OptionList listViews(Session arg0) throws InvalidToken,
2430
          ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented {
2431
      OptionList views = new OptionList();
2432
      views.setKey("views");
2433
      views.setDescription("List of views for objects on the node");
2434
      Vector<String> skinNames = null;
2435
      try {
2436
          skinNames = SkinUtil.getSkinNames();
2437
      } catch (PropertyNotFoundException e) {
2438
          throw new ServiceFailure("2841", e.getMessage());
2439
      }
2440
      for (String skinName: skinNames) {
2441
          views.addOption(skinName);
2442
      }
2443
      return views;
2444
  }
2445
  
2446
  public OptionList listViews() throws InvalidToken,
2447
  ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented { 
2448
      return listViews(null);
2449
  }
2450

    
2451
  //@Override
2452
  public InputStream view(Session session, String format, Identifier pid)
2453
          throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
2454
          NotImplemented, NotFound {
2455
      InputStream resultInputStream = null;
2456
      
2457
      String serviceFailureCode = "2831";
2458
      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2459
      if(sid != null) {
2460
          pid = sid;
2461
      }
2462
      
2463
      SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2464
      InputStream object = this.get(session, pid);
2465

    
2466
      try {
2467
          // can only transform metadata, really
2468
          ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2469
          if (objectFormat.getFormatType().equals("METADATA")) {
2470
              // transform
2471
              DBTransform transformer = new DBTransform();
2472
              String documentContent = IOUtils.toString(object, "UTF-8");
2473
              String sourceType = objectFormat.getFormatId().getValue();
2474
              String targetType = "-//W3C//HTML//EN";
2475
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
2476
              Writer writer = new OutputStreamWriter(baos , "UTF-8");
2477
              // TODO: include more params?
2478
              Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2479
              String localId = null;
2480
              try {
2481
                  localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2482
              } catch (McdbDocNotFoundException e) {
2483
                  throw new NotFound("1020", e.getMessage());
2484
              }
2485
              params.put("qformat", new String[] {format});               
2486
              params.put("docid", new String[] {localId});
2487
              params.put("pid", new String[] {pid.getValue()});
2488
              transformer.transformXMLDocument(
2489
                      documentContent , 
2490
                      sourceType, 
2491
                      targetType , 
2492
                      format, 
2493
                      writer, 
2494
                      params, 
2495
                      null //sessionid
2496
                      );
2497
              
2498
              // finally, get the HTML back
2499
              resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2500
              ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2501
  
2502
          } else {
2503
              // just return the raw bytes
2504
              resultInputStream = object;
2505
          }
2506
      } catch (IOException e) {
2507
          // report as service failure
2508
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2509
          sf.initCause(e);
2510
          throw sf;
2511
      } catch (PropertyNotFoundException e) {
2512
          // report as service failure
2513
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2514
          sf.initCause(e);
2515
          throw sf;
2516
      } catch (SQLException e) {
2517
          // report as service failure
2518
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2519
          sf.initCause(e);
2520
          throw sf;
2521
      } catch (ClassNotFoundException e) {
2522
          // report as service failure
2523
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2524
          sf.initCause(e);
2525
          throw sf;
2526
      }
2527
      
2528
      return resultInputStream;
2529
  } 
2530
  
2531
  /*
2532
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2533
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2534
   * the guid of the first row.
2535
   */
2536
  protected String existsInObsoletes(Identifier id) throws InvalidRequest, ServiceFailure{
2537
      String guid = existsInFields("obsoletes", id);
2538
      return guid;
2539
  }
2540
  
2541
  /*
2542
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2543
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2544
   * the guid of the first row.
2545
   */
2546
  protected String existsInObsoletedBy(Identifier id) throws InvalidRequest, ServiceFailure{
2547
      String guid = existsInFields("obsoleted_by", id);
2548
      return guid;
2549
  }
2550

    
2551
  /*
2552
   * Determine if the given identifier exists in the given column in the system metadata table. 
2553
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2554
   * the guid of the first row.
2555
   */
2556
  private String existsInFields(String column, Identifier id) throws InvalidRequest, ServiceFailure {
2557
      String guid = null;
2558
      if(id == null ) {
2559
          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");
2560
      }
2561
      String sql = "SELECT guid FROM systemmetadata WHERE "+column+" = ?";
2562
      int serialNumber = -1;
2563
      DBConnection dbConn = null;
2564
      PreparedStatement stmt = null;
2565
      ResultSet result = null;
2566
      try {
2567
          dbConn = 
2568
                  DBConnectionPool.getDBConnection("D1NodeService.existsInFields");
2569
          serialNumber = dbConn.getCheckOutSerialNumber();
2570
          stmt = dbConn.prepareStatement(sql);
2571
          stmt.setString(1, id.getValue());
2572
          result = stmt.executeQuery();
2573
          if(result.next()) {
2574
              guid = result.getString(1);
2575
          }
2576
          stmt.close();
2577
      } catch (SQLException e) {
2578
          e.printStackTrace();
2579
          throw new ServiceFailure("4862","We can't determine if the id "+id.getValue()+" exists in field "+column+" in the systemmetadata table since "+e.getMessage());
2580
      } finally {
2581
          // Return database connection to the pool
2582
          DBConnectionPool.returnDBConnection(dbConn, serialNumber);
2583
          if(stmt != null) {
2584
              try {
2585
                  stmt.close();
2586
              } catch (SQLException e) {
2587
                  logMetacat.warn("We can close the PreparedStatment in D1NodeService.existsInFields since "+e.getMessage());
2588
              }
2589
          }
2590
          
2591
      }
2592
      return guid;
2593
      
2594
  }
2595
}
(2-2/8)