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: 2016-12-08 14:15:57 -0800 (Thu, 08 Dec 2016) $'
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
        // save the sysmeta
431
        try {
432
            // lock and unlock of the pid happens in the subclass
433
            HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
434
           
435
        
436
        } catch (Exception e) {
437
            logMetacat.error("Problem creating system metadata: " + pid.getValue(), e);
438
            throw new ServiceFailure("1190", e.getMessage());
439
        }
440
      
441
        logMetacat.debug("Allowed to insert: " + pid.getValue());
442

    
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
        
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
            removeSystemMeta(pid);
461
        	String msg = "The Node is unable to create the object. " +
462
          "There was a problem converting the object to XML";
463
        	logMetacat.info(msg);
464
          throw new ServiceFailure("1190", msg + ": " + e.getMessage());
465

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

    
492
    logMetacat.debug("Done inserting new object: " + pid.getValue());
493
    
494
    // setting the resulting identifier failed
495
    if (localId == null ) {
496
        removeSystemMeta(pid);
497
      throw new ServiceFailure("1190", "The Node is unable to create the object. ");
498
    }
499
    
500
    try {
501
        // submit for indexing
502
        MetacatSolrIndex.getInstance().submit(sysmeta.getIdentifier(), sysmeta, null, true);
503
    } catch (Exception e) {
504
        logMetacat.warn("Couldn't create solr index for object "+pid.getValue());
505
    }
506

    
507
    resultPid = pid;
508
    
509
    logMetacat.debug("create() complete for object: " + pid.getValue());
510

    
511
    return resultPid;
512
  }
513
  
514
  /*
515
   * Roll-back method when inserting data object fails.
516
   */
517
  protected void removeSystemMeta(Identifier id){
518
      if(id != null) {
519
          logMetacat.debug("D1NodeService.removeSystemMeta - the system metadata of object "+id.getValue()+" will removed from both hazelcast and db tables");
520
      }
521
      HazelcastService.getInstance().getSystemMetadataMap().remove(id);
522
      if(id != null) {
523
          logMetacat.debug("D1NodeService.removeSystemMeta - the system metadata of object "+id.getValue()+" has been removed from both hazelcast and db tables");
524
      }
525
  }
526
  
527
  /*
528
   * Roll-back method when inserting data object fails.
529
   */
530
  protected void removeSolrIndex(SystemMetadata sysMeta) {
531
      sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
532
      sysMeta.setArchived(true);
533
      sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
534
      try {
535
          MetacatSolrIndex.getInstance().submit(sysMeta.getIdentifier(), sysMeta, null, false);
536
      } catch (Exception e) {
537
          logMetacat.warn("Can't remove the solr index for pid "+sysMeta.getIdentifier().getValue());
538
      }
539
      
540
  }
541

    
542
  /**
543
   * Return the log records associated with a given event between the start and 
544
   * end dates listed given a particular Subject listed in the Session
545
   * 
546
   * @param session - the Session object containing the credentials for the Subject
547
   * @param fromDate - the start date of the desired log records
548
   * @param toDate - the end date of the desired log records
549
   * @param event - restrict log records of a specific event type
550
   * @param start - zero based offset from the first record in the 
551
   *                set of matching log records. Used to assist with 
552
   *                paging the response.
553
   * @param count - maximum number of log records to return in the response. 
554
   *                Used to assist with paging the response.
555
   * 
556
   * @return the desired log records
557
   * 
558
   * @throws InvalidToken
559
   * @throws ServiceFailure
560
   * @throws NotAuthorized
561
   * @throws InvalidRequest
562
   * @throws NotImplemented
563
   */
564
  public Log getLogRecords(Session session, Date fromDate, Date toDate, 
565
      String event, String pidFilter, Integer start, Integer count) throws InvalidToken, ServiceFailure,
566
      NotAuthorized, InvalidRequest, NotImplemented {
567

    
568
	  // only admin access to this method
569
	  // see https://redmine.dataone.org/issues/2855
570
	  if (!isAdminAuthorized(session)) {
571
		  throw new NotAuthorized("1460", "Only the CN or admin is allowed to harvest logs from this node");
572
	  }
573
    Log log = new Log();
574
    IdentifierManager im = IdentifierManager.getInstance();
575
    EventLog el = EventLog.getInstance();
576
    if ( fromDate == null ) {
577
      logMetacat.debug("setting fromdate from null");
578
      fromDate = new Date(1);
579
    }
580
    if ( toDate == null ) {
581
      logMetacat.debug("setting todate from null");
582
      toDate = new Date();
583
    }
584

    
585
    if ( start == null ) {
586
    	start = 0;	
587
    }
588
    
589
    if ( count == null ) {
590
    	count = 1000;
591
    }
592
    
593
    // safeguard against large requests
594
    if (count > MAXIMUM_DB_RECORD_COUNT) {
595
    	count = MAXIMUM_DB_RECORD_COUNT;
596
    }
597

    
598
    String[] filterDocid = null;
599
    if (pidFilter != null && !pidFilter.trim().equals("")) {
600
        //check if the given identifier is a sid. If it is sid, choose the current pid of the sid.
601
        Identifier pid = new Identifier();
602
        pid.setValue(pidFilter);
603
        String serviceFailureCode = "1490";
604
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
605
        if(sid != null) {
606
            pid = sid;
607
        }
608
        pidFilter = pid.getValue();
609
		try {
610
	      String localId = im.getLocalId(pidFilter);
611
	      filterDocid = new String[] {localId};
612
	    } catch (Exception ex) { 
613
	    	String msg = "Could not find localId for given pidFilter '" + pidFilter + "'";
614
	        logMetacat.warn(msg, ex);
615
	        //throw new InvalidRequest("1480", msg);
616
	        return log; //return 0 record
617
	    }
618
    }
619
    
620
    logMetacat.debug("fromDate: " + fromDate);
621
    logMetacat.debug("toDate: " + toDate);
622

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

    
704
    // if we fail to set the input stream
705
    if ( inputStream == null ) {
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
    }
714
    
715
	// log the read event
716
    String principal = Constants.SUBJECT_PUBLIC;
717
    if (session != null && session.getSubject() != null) {
718
    	principal = session.getSubject().getValue();
719
    }
720
    EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "read");
721
    
722
    return inputStream;
723
  }
724

    
725
  /**
726
   * Return the system metadata for a given object
727
   * 
728
   * @param session - the Session object containing the credentials for the Subject
729
   * @param pid - the object identifier for the given object
730
   * 
731
   * @return inputStream - the input stream of the given system metadata object
732
   * 
733
   * @throws InvalidToken
734
   * @throws ServiceFailure
735
   * @throws NotAuthorized
736
   * @throws NotFound
737
   * @throws InvalidRequest
738
   * @throws NotImplemented
739
   */
740
    public SystemMetadata getSystemMetadata(Session session, Identifier pid)
741
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
742
        NotImplemented {
743

    
744
        String serviceFailureCode = "1090";
745
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
746
        if(sid != null) {
747
            pid = sid;
748
        }
749
        boolean isAuthorized = false;
750
        SystemMetadata systemMetadata = null;
751
        List<Replica> replicaList = null;
752
        NodeReference replicaNodeRef = null;
753
        List<Node> nodeListBySubject = null;
754
        Subject subject = null;
755
        
756
        if (session != null ) {
757
            subject = session.getSubject();
758
        }
759
        
760
        // check normal authorization
761
        BaseException originalAuthorizationException = null;
762
        if (!isAuthorized) {
763
            try {
764
                isAuthorized = isAuthorized(session, pid, Permission.READ);
765

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

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

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

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

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

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

    
1329
  /** 
1330
   * Determine if a given object should be treated as an XML science metadata
1331
   * object. 
1332
   * 
1333
   * @param sysmeta - the SystemMetadata describing the object
1334
   * @return true if the object should be treated as science metadata
1335
   */
1336
  public static boolean isScienceMetadata(SystemMetadata sysmeta) {
1337
    
1338
    ObjectFormat objectFormat = null;
1339
    boolean isScienceMetadata = false;
1340
    
1341
    try {
1342
      objectFormat = ObjectFormatCache.getInstance().getFormat(sysmeta.getFormatId());
1343
      if ( objectFormat.getFormatType().equals("METADATA") ) {
1344
      	isScienceMetadata = true;
1345
      	
1346
      }
1347
      
1348
       
1349
    /*} catch (ServiceFailure e) {
1350
      logMetacat.debug("There was a problem determining if the object identified by" + 
1351
          sysmeta.getIdentifier().getValue() + 
1352
          " is science metadata: " + e.getMessage());*/
1353
    
1354
    } catch (NotFound e) {
1355
      logMetacat.debug("There was a problem determining if the object identified by" + 
1356
          sysmeta.getIdentifier().getValue() + 
1357
          " is science metadata: " + e.getMessage());
1358
    
1359
    }
1360
    
1361
    return isScienceMetadata;
1362

    
1363
  }
1364
  
1365
  /**
1366
   * Check fro whitespace in the given pid.
1367
   * null pids are also invalid by default
1368
   * @param pid
1369
   * @return
1370
   */
1371
  public static boolean isValidIdentifier(Identifier pid) {
1372
	  if (pid != null && pid.getValue() != null && pid.getValue().length() > 0) {
1373
		  return !pid.getValue().matches(".*\\s+.*");
1374
	  } 
1375
	  return false;
1376
  }
1377
  
1378
  
1379
  /**
1380
   * Insert or update an XML document into Metacat
1381
   * 
1382
   * @param xml - the XML document to insert or update
1383
   * @param pid - the identifier to be used for the resulting object
1384
   * 
1385
   * @return localId - the resulting docid of the document created or updated
1386
   * 
1387
   */
1388
  public String insertOrUpdateDocument(InputStream xmlStream, String encoding,  Identifier pid, 
1389
    Session session, String insertOrUpdate, String formatId) 
1390
    throws ServiceFailure, IOException {
1391
    
1392
  	logMetacat.debug("Starting to insert xml document...");
1393
    IdentifierManager im = IdentifierManager.getInstance();
1394

    
1395
    // generate pid/localId pair for sysmeta
1396
    String localId = null;
1397
    byte[] xmlBytes  = IOUtils.toByteArray(xmlStream);
1398
    IOUtils.closeQuietly(xmlStream);
1399
    String xmlStr = new String(xmlBytes, encoding);
1400
    if(insertOrUpdate.equals("insert")) {
1401
      localId = im.generateLocalId(pid.getValue(), 1);
1402
      
1403
    } else {
1404
      //localid should already exist in the identifier table, so just find it
1405
      try {
1406
        logMetacat.debug("Updating pid " + pid.getValue());
1407
        logMetacat.debug("looking in identifier table for pid " + pid.getValue());
1408
        
1409
        localId = im.getLocalId(pid.getValue());
1410
        
1411
        logMetacat.debug("localId: " + localId);
1412
        //increment the revision
1413
        String docid = localId.substring(0, localId.lastIndexOf("."));
1414
        String revS = localId.substring(localId.lastIndexOf(".") + 1, localId.length());
1415
        int rev = new Integer(revS).intValue();
1416
        rev++;
1417
        docid = docid + "." + rev;
1418
        localId = docid;
1419
        logMetacat.debug("incremented localId: " + localId);
1420
      
1421
      } catch(McdbDocNotFoundException e) {
1422
        throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument(): " +
1423
            "pid " + pid.getValue() + 
1424
            " should have been in the identifier table, but it wasn't: " + 
1425
            e.getMessage());
1426
      
1427
      } catch (SQLException e) {
1428
          throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument() -"+
1429
                     " couldn't identify if the pid "+pid.getValue()+" is in the identifier table since "+e.getMessage());
1430
      }
1431
      
1432
    }
1433

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

    
1537
    logMetacat.debug("Case DATA: starting to write to disk.");
1538
	if (locked) {
1539

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

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

    
1643
      ObjectList objectList = null;
1644

    
1645
      try {
1646
          // safeguard against large requests
1647
          if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
1648
              count = MAXIMUM_DB_RECORD_COUNT;
1649
          }
1650
          boolean isSid = false;
1651
          if(identifier != null) {
1652
              isSid = IdentifierManager.getInstance().systemMetadataSIDExists(identifier);
1653
          }
1654
          objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, nodeId, start, count, identifier, isSid);
1655
      } catch (Exception e) {
1656
          throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1657
      }
1658

    
1659
      return objectList;
1660
  }
1661

    
1662

    
1663
  /**
1664
   * Update a systemMetadata document
1665
   * 
1666
   * @param sysMeta - the system metadata object in the system to update
1667
   */
1668
    protected void updateSystemMetadata(SystemMetadata sysMeta)
1669
        throws ServiceFailure {
1670
        logMetacat.debug("D1NodeService.updateSystemMetadata() called.");
1671
        try {
1672
            HazelcastService.getInstance().getSystemMetadataMap().lock(sysMeta.getIdentifier());
1673
            boolean needUpdateModificationDate = true;
1674
            updateSystemMetadataWithoutLock(sysMeta, needUpdateModificationDate);
1675
        } catch (Exception e) {
1676
            throw new ServiceFailure("4862", e.getMessage());
1677
        } finally {
1678
            HazelcastService.getInstance().getSystemMetadataMap().unlock(sysMeta.getIdentifier());
1679

    
1680
        }
1681

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

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

    
2026
  /*
2027
   * Write a stream to a file
2028
   * 
2029
   * @param dir - the directory to write to
2030
   * @param fileName - the file name to write to
2031
   * @param data - the object bytes as an input stream
2032
   * 
2033
   * @return newFile - the new file created
2034
   * 
2035
   * @throws ServiceFailure
2036
   */
2037
  private File writeStreamToFile(File dir, String fileName, InputStream dataStream) 
2038
    throws ServiceFailure {
2039
    
2040
    File newFile = new File(dir, fileName);
2041
    logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
2042

    
2043
    try {
2044
        if (newFile.createNewFile()) {
2045
          // write data stream to desired file
2046
          OutputStream os = new FileOutputStream(newFile);
2047
          long length = IOUtils.copyLarge(dataStream, os);
2048
          os.flush();
2049
          os.close();
2050
        } else {
2051
          logMetacat.debug("File creation failed, or file already exists.");
2052
          throw new ServiceFailure("1190", "File already exists: " + fileName);
2053
        }
2054
    } catch (FileNotFoundException e) {
2055
      logMetacat.debug("FNF: " + e.getMessage());
2056
      throw new ServiceFailure("1190", "File not found: " + fileName + " " 
2057
                + e.getMessage());
2058
    } catch (IOException e) {
2059
      logMetacat.debug("IOE: " + e.getMessage());
2060
      throw new ServiceFailure("1190", "File was not written: " + fileName 
2061
                + " " + e.getMessage());
2062
    } finally {
2063
        IOUtils.closeQuietly(dataStream);
2064
    }
2065

    
2066
    return newFile;
2067
  }
2068

    
2069
  /*
2070
   * Returns a list of nodes that have been registered with the DataONE infrastructure
2071
   * that match the given session subject
2072
   * @return nodes - List of nodes from the registry with a matching session subject
2073
   * 
2074
   * @throws ServiceFailure
2075
   * @throws NotImplemented
2076
   */
2077
  protected List<Node> listNodesBySubject(Subject subject) 
2078
      throws ServiceFailure, NotImplemented {
2079
      List<Node> nodeList = new ArrayList<Node>();
2080
      
2081
      CNode cn = D1Client.getCN();
2082
      List<Node> nodes = cn.listNodes().getNodeList();
2083
      
2084
      // find the node in the node list
2085
      for ( Node node : nodes ) {
2086
          
2087
          List<Subject> nodeSubjects = node.getSubjectList();
2088
          if (nodeSubjects != null) {    
2089
	          // check if the session subject is in the node subject list
2090
	          for (Subject nodeSubject : nodeSubjects) {
2091
	              if ( nodeSubject.equals(subject) ) { // subject of session == node subject
2092
	                  nodeList.add(node);  
2093
	              }                              
2094
	          }
2095
          }
2096
      }
2097
      
2098
      return nodeList;
2099
      
2100
  }
2101

    
2102
  /**
2103
   * Archives an object, where the object is either a 
2104
   * data object or a science metadata object.
2105
   * Note: it doesn't check the authorization; it doesn't lock the system metadata;it only accept pid.
2106
   * @param session - the Session object containing the credentials for the Subject
2107
   * @param pid - The object identifier to be archived
2108
   * 
2109
   * @return pid - the identifier of the object used for the archiving
2110
   * 
2111
   * @throws InvalidToken
2112
   * @throws ServiceFailure
2113
   * @throws NotAuthorized
2114
   * @throws NotFound
2115
   * @throws NotImplemented
2116
   * @throws InvalidRequest
2117
   */
2118
  protected Identifier archiveObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2119
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2120

    
2121
      String localId = null;
2122
      boolean allowed = false;
2123
      String username = Constants.SUBJECT_PUBLIC;
2124
      if (session == null) {
2125
      	throw new InvalidToken("1330", "No session has been provided");
2126
      } else {
2127
          username = session.getSubject().getValue();
2128
      }
2129
      // do we have a valid pid?
2130
      if (pid == null || pid.getValue().trim().equals("")) {
2131
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
2132
      }
2133
      
2134
      if(sysMeta == null) {
2135
          throw new NotFound("2911", "There is no system metadata associated with "+pid.getValue());
2136
      }
2137
      
2138
      // check for the existing identifier
2139
      try {
2140
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2141
      } catch (McdbDocNotFoundException e) {
2142
          throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
2143
      } catch (SQLException e) {
2144
          throw new ServiceFailure("1350", "The object with the provided identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
2145
      }
2146

    
2147

    
2148
          try {
2149
              // archive the document
2150
              DocumentImpl.delete(localId, null, null, null, false);
2151
              if(log) {
2152
                   try {
2153
                      EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
2154
                   } catch (Exception e) {
2155
                      logMetacat.warn("D1NodeService.archiveObject - can't log the delete event since "+e.getMessage());
2156
                   }
2157
              }
2158
             
2159
              
2160
              // archive it
2161
              sysMeta.setArchived(true);
2162
              if(needModifyDate) {
2163
                  sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2164
                  sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2165
              }
2166
              HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2167
              
2168
              // submit for indexing
2169
              // DocumentImpl call above should do this.
2170
              // see: https://projects.ecoinformatics.org/ecoinfo/issues/6030
2171
              //HazelcastService.getInstance().getIndexQueue().add(sysMeta);
2172
              
2173
          } catch (McdbDocNotFoundException e) {
2174
              throw new NotFound("1340", "The provided identifier was invalid.");
2175

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

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

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

    
2186

    
2187
      return pid;
2188
  }
2189
  
2190
  /**
2191
   * Archive a object on cn and notify the replica. This method doesn't lock the system metadata map. The caller should lock it.
2192
   * This method doesn't check the authorization; this method only accept a pid.
2193
   * It wouldn't notify the replca that the system metadata has been changed.
2194
   * @param session
2195
   * @param pid
2196
   * @param sysMeta
2197
   * @param notifyReplica
2198
   * @return
2199
   * @throws InvalidToken
2200
   * @throws ServiceFailure
2201
   * @throws NotAuthorized
2202
   * @throws NotFound
2203
   * @throws NotImplemented
2204
   */
2205
  protected void archiveCNObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2206
          throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2207

    
2208
          String localId = null; // The corresponding docid for this pid
2209
          
2210
          // Check for the existing identifier
2211
          try {
2212
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2213
              archiveObject(log, session, pid, sysMeta, needModifyDate);
2214
          
2215
          } catch (McdbDocNotFoundException e) {
2216
              // This object is not registered in the identifier table. Assume it is of formatType DATA,
2217
              // and set the archive flag. (i.e. the *object* doesn't exist on the CN)
2218
              
2219
              try {
2220
                  if ( sysMeta != null ) {
2221
                    sysMeta.setArchived(true);
2222
                    if (needModifyDate) {
2223
                        sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2224
                        sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2225
                    }
2226
                    HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2227
                      
2228
                  } else {
2229
                      throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2230
                          ". Couldn't obtain the system metadata record.");
2231
                      
2232
                  }
2233
                  
2234
              } catch (RuntimeException re) {
2235
                  throw new ServiceFailure("4972", "Couldn't archive " + pid.getValue() + 
2236
                      ". The error message was: " + re.getMessage());
2237
                  
2238
              } 
2239

    
2240
          } catch (SQLException e) {
2241
              throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2242
                      ". The local id of the object with the identifier can't be identified since "+e.getMessage());
2243
          }
2244
          
2245
    }
2246
  
2247
  
2248
  /**
2249
   * A utility method for v1 api to check the specified identifier exists as a pid
2250
   * @param identifier  the specified identifier
2251
   * @param serviceFailureCode  the detail error code for the service failure exception
2252
   * @param noFoundCode  the detail error code for the not found exception
2253
   * @throws ServiceFailure
2254
   * @throws NotFound
2255
   */
2256
  public void checkV1SystemMetaPidExist(Identifier identifier, String serviceFailureCode, String serviceFailureMessage,  
2257
          String noFoundCode, String notFoundMessage) throws ServiceFailure, NotFound {
2258
      boolean exists = false;
2259
      try {
2260
          exists = IdentifierManager.getInstance().systemMetadataPIDExists(identifier);
2261
      } catch (SQLException e) {
2262
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+e.getMessage());
2263
      }
2264
      if(!exists) {
2265
         //the v1 method only handles a pid. so it should throw a not-found exception.
2266
          // check if the pid was deleted.
2267
          try {
2268
              String localId = IdentifierManager.getInstance().getLocalId(identifier.getValue());
2269
              if(EventLog.getInstance().isDeleted(localId)) {
2270
                  notFoundMessage=notFoundMessage+" "+DELETEDMESSAGE;
2271
              } 
2272
            } catch (Exception e) {
2273
              logMetacat.info("Couldn't determine if the not-found identifier "+identifier.getValue()+" was deleted since "+e.getMessage());
2274
            }
2275
            throw new NotFound(noFoundCode, notFoundMessage);
2276
      }
2277
  }
2278
  
2279
  /**
2280
   * Utility method to get the PID for an SID. If the specified identifier is not an SID
2281
   * , null will be returned.
2282
   * @param sid  the specified sid
2283
   * @param serviceFailureCode  the detail error code for the service failure exception
2284
   * @return the pid for the sid. If the specified identifier is not an SID, null will be returned.
2285
   * @throws ServiceFailure
2286
   */
2287
  protected Identifier getPIDForSID(Identifier sid, String serviceFailureCode) throws ServiceFailure {
2288
      Identifier id = null;
2289
      String serviceFailureMessage = "The PID "+" couldn't be identified for the sid " + sid.getValue();
2290
      try {
2291
          //determine if the given pid is a sid or not.
2292
          if(IdentifierManager.getInstance().systemMetadataSIDExists(sid)) {
2293
              try {
2294
                  //set the header pid for the sid if the identifier is a sid.
2295
                  id = IdentifierManager.getInstance().getHeadPID(sid);
2296
              } catch (SQLException sqle) {
2297
                  throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+sqle.getMessage());
2298
              }
2299
              
2300
          }
2301
      } catch (SQLException e) {
2302
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage + " since "+e.getMessage());
2303
      }
2304
      return id;
2305
  }
2306

    
2307
  /*
2308
   * 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:
2309
   * A. If the sysmeta doesn't have an SID, nothing needs to be checked for the SID.
2310
   * B. If the sysmeta does have an SID, it may be an identifier which doesn't exist in the system.
2311
   * C. If the sysmeta does have an SID and it exists as an SID in the system, those scenarios are acceptable:
2312
   *    i. The sysmeta has an obsoletes field, the SID has the same value as the SID of the system metadata of the obsoleting pid.
2313
   *    ii. The sysmeta has an obsoletedBy field, the SID has the same value as the SID of the system metadata of the obsoletedBy pid. 
2314
   */
2315
  protected boolean checkSidInModifyingSystemMetadata(SystemMetadata sysmeta, String invalidSystemMetadataCode, String serviceFailureCode) throws InvalidSystemMetadata, ServiceFailure{
2316
      boolean pass = false;
2317
      if(sysmeta == null) {
2318
          throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The system metadata is null in the request.");
2319
      }
2320
      Identifier sid = sysmeta.getSeriesId();
2321
      if(sid != null) {
2322
          // the series id exists
2323
          if (!isValidIdentifier(sid)) {
2324
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id in the system metadata is invalid in the request.");
2325
          }
2326
          Identifier pid = sysmeta.getIdentifier();
2327
          if (!isValidIdentifier(pid)) {
2328
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The pid in the system metadata is invalid in the request.");
2329
          }
2330
          //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 )
2331
          if(sid.getValue().equals(pid.getValue())) {
2332
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
2333
          }
2334
          try {
2335
              if (IdentifierManager.getInstance().identifierExists(sid.getValue())) {
2336
                  //the sid exists in system
2337
                  if(sysmeta.getObsoletes() != null) {
2338
                      SystemMetadata obsoletesSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletes());
2339
                      if(obsoletesSysmeta != null) {
2340
                          Identifier obsoletesSid = obsoletesSysmeta.getSeriesId();
2341
                          if(obsoletesSid != null && obsoletesSid.getValue() != null && !obsoletesSid.getValue().trim().equals("")) {
2342
                              if(sid.getValue().equals(obsoletesSid.getValue())) {
2343
                                  pass = true;// the i of rule C
2344
                              }
2345
                          }
2346
                      } else {
2347
                           logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue()+
2348
                                                                         " which is the value of the obsoletes. So we can't check if the sid " +sid.getValue()+" is legitimate ");
2349
                      }
2350
                  }
2351
                  if(!pass) {
2352
                      // the sid doesn't match the sid of the obsoleting identifier. So we check the obsoletedBy
2353
                      if(sysmeta.getObsoletedBy() != null) {
2354
                          SystemMetadata obsoletedBySysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletedBy());
2355
                          if(obsoletedBySysmeta != null) {
2356
                              Identifier obsoletedBySid = obsoletedBySysmeta.getSeriesId();
2357
                              if(obsoletedBySid != null && obsoletedBySid.getValue() != null && !obsoletedBySid.getValue().trim().equals("")) {
2358
                                  if(sid.getValue().equals(obsoletedBySid.getValue())) {
2359
                                      pass = true;// the ii of the rule C
2360
                                  }
2361
                              }
2362
                          } else {
2363
                              logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue() 
2364
                                                                            +" which is the value of the obsoletedBy. So we can't check if the sid "+sid.getValue()+" is legitimate.");
2365
                          }
2366
                      }
2367
                  }
2368
                  if(!pass) {
2369
                      throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+
2370
                              " in the system metadata exists in the system. And it doesn't match either previous object's sid or the next object's sid.");
2371
                  }
2372
              } else {
2373
                  pass = true; //Rule B
2374
              }
2375
          } catch (SQLException e) {
2376
              throw new ServiceFailure(serviceFailureCode, "Can't determine if the sid in the system metadata is unique or not since "+e.getMessage());
2377
          }
2378
          
2379
      } else {
2380
          //no sid. Rule A.
2381
          pass = true;
2382
      }
2383
      return pass;
2384
      
2385
  }
2386
  
2387
  //@Override
2388
  public OptionList listViews(Session arg0) throws InvalidToken,
2389
          ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented {
2390
      OptionList views = new OptionList();
2391
      views.setKey("views");
2392
      views.setDescription("List of views for objects on the node");
2393
      Vector<String> skinNames = null;
2394
      try {
2395
          skinNames = SkinUtil.getSkinNames();
2396
      } catch (PropertyNotFoundException e) {
2397
          throw new ServiceFailure("2841", e.getMessage());
2398
      }
2399
      for (String skinName: skinNames) {
2400
          views.addOption(skinName);
2401
      }
2402
      return views;
2403
  }
2404
  
2405
  public OptionList listViews() throws InvalidToken,
2406
  ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented { 
2407
      return listViews(null);
2408
  }
2409

    
2410
  //@Override
2411
  public InputStream view(Session session, String format, Identifier pid)
2412
          throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
2413
          NotImplemented, NotFound {
2414
      InputStream resultInputStream = null;
2415
      
2416
      String serviceFailureCode = "2831";
2417
      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2418
      if(sid != null) {
2419
          pid = sid;
2420
      }
2421
      
2422
      SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2423
      InputStream object = this.get(session, pid);
2424

    
2425
      try {
2426
          // can only transform metadata, really
2427
          ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2428
          if (objectFormat.getFormatType().equals("METADATA")) {
2429
              // transform
2430
              DBTransform transformer = new DBTransform();
2431
              String documentContent = IOUtils.toString(object, "UTF-8");
2432
              String sourceType = objectFormat.getFormatId().getValue();
2433
              String targetType = "-//W3C//HTML//EN";
2434
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
2435
              Writer writer = new OutputStreamWriter(baos , "UTF-8");
2436
              // TODO: include more params?
2437
              Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2438
              String localId = null;
2439
              try {
2440
                  localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2441
              } catch (McdbDocNotFoundException e) {
2442
                  throw new NotFound("1020", e.getMessage());
2443
              }
2444
              params.put("qformat", new String[] {format});               
2445
              params.put("docid", new String[] {localId});
2446
              params.put("pid", new String[] {pid.getValue()});
2447
              transformer.transformXMLDocument(
2448
                      documentContent , 
2449
                      sourceType, 
2450
                      targetType , 
2451
                      format, 
2452
                      writer, 
2453
                      params, 
2454
                      null //sessionid
2455
                      );
2456
              
2457
              // finally, get the HTML back
2458
              resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2459
              ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2460
  
2461
          } else {
2462
              // just return the raw bytes
2463
              resultInputStream = object;
2464
          }
2465
      } catch (IOException e) {
2466
          // report as service failure
2467
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2468
          sf.initCause(e);
2469
          throw sf;
2470
      } catch (PropertyNotFoundException e) {
2471
          // report as service failure
2472
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2473
          sf.initCause(e);
2474
          throw sf;
2475
      } catch (SQLException e) {
2476
          // report as service failure
2477
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2478
          sf.initCause(e);
2479
          throw sf;
2480
      } catch (ClassNotFoundException e) {
2481
          // report as service failure
2482
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2483
          sf.initCause(e);
2484
          throw sf;
2485
      }
2486
      
2487
      return resultInputStream;
2488
  } 
2489
  
2490
  /*
2491
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2492
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2493
   * the guid of the first row.
2494
   */
2495
  protected String existsInObsoletes(Identifier id) throws InvalidRequest, ServiceFailure{
2496
      String guid = existsInFields("obsoletes", id);
2497
      return guid;
2498
  }
2499
  
2500
  /*
2501
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2502
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2503
   * the guid of the first row.
2504
   */
2505
  protected String existsInObsoletedBy(Identifier id) throws InvalidRequest, ServiceFailure{
2506
      String guid = existsInFields("obsoleted_by", id);
2507
      return guid;
2508
  }
2509

    
2510
  /*
2511
   * Determine if the given identifier exists in the given column in the system metadata table. 
2512
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2513
   * the guid of the first row.
2514
   */
2515
  private String existsInFields(String column, Identifier id) throws InvalidRequest, ServiceFailure {
2516
      String guid = null;
2517
      if(id == null ) {
2518
          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");
2519
      }
2520
      String sql = "SELECT guid FROM systemmetadata WHERE "+column+" = ?";
2521
      int serialNumber = -1;
2522
      DBConnection dbConn = null;
2523
      PreparedStatement stmt = null;
2524
      ResultSet result = null;
2525
      try {
2526
          dbConn = 
2527
                  DBConnectionPool.getDBConnection("D1NodeService.existsInFields");
2528
          serialNumber = dbConn.getCheckOutSerialNumber();
2529
          stmt = dbConn.prepareStatement(sql);
2530
          stmt.setString(1, id.getValue());
2531
          result = stmt.executeQuery();
2532
          if(result.next()) {
2533
              guid = result.getString(1);
2534
          }
2535
          stmt.close();
2536
      } catch (SQLException e) {
2537
          e.printStackTrace();
2538
          throw new ServiceFailure("4862","We can't determine if the id "+id.getValue()+" exists in field "+column+" in the systemmetadata table since "+e.getMessage());
2539
      } finally {
2540
          // Return database connection to the pool
2541
          DBConnectionPool.returnDBConnection(dbConn, serialNumber);
2542
          if(stmt != null) {
2543
              try {
2544
                  stmt.close();
2545
              } catch (SQLException e) {
2546
                  logMetacat.warn("We can close the PreparedStatment in D1NodeService.existsInFields since "+e.getMessage());
2547
              }
2548
          }
2549
          
2550
      }
2551
      return guid;
2552
      
2553
  }
2554
}
(2-2/8)