Project

General

Profile

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

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

    
26
import java.io.ByteArrayOutputStream;
27
import java.io.File;
28
import java.io.FileNotFoundException;
29
import java.io.FileOutputStream;
30
import java.io.IOException;
31
import java.io.InputStream;
32
import java.io.OutputStream;
33
import java.io.OutputStreamWriter;
34
import java.io.Writer;
35
import java.math.BigInteger;
36
import java.security.DigestOutputStream;
37
import java.security.MessageDigest;
38
import java.security.NoSuchAlgorithmException;
39
import java.sql.PreparedStatement;
40
import java.sql.ResultSet;
41
import java.sql.SQLException;
42
import java.util.ArrayList;
43
import java.util.Calendar;
44
import java.util.Date;
45
import java.util.Hashtable;
46
import java.util.Iterator;
47
import java.util.List;
48
import java.util.Set;
49
import java.util.Timer;
50
import java.util.Vector;
51
import java.util.concurrent.locks.Lock;
52

    
53
import javax.servlet.http.HttpServletRequest;
54
import javax.xml.bind.DatatypeConverter;
55

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

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

    
117
public abstract class D1NodeService {
118
    
119
  public static final String DELETEDMESSAGE = "The object with the PID has been deleted from the node.";
120
    
121
  private static Logger logMetacat = Logger.getLogger(D1NodeService.class);
122

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

    
150
  /**
151
   * Constructor - used to set the metacatUrl from a subclass extending D1NodeService
152
   * 
153
   * @param metacatUrl - the URL of the metacat service, including the ending /d1
154
   */
155
  public D1NodeService(HttpServletRequest request) {
156
		this.request = request;
157
	}
158

    
159
  /**
160
   * retrieve the out-of-band session
161
   * @return
162
   */
163
  	public Session getSession() {
164
		return session;
165
	}
166
  	
167
  	/**
168
  	 * Set the out-of-band session
169
  	 * @param session
170
  	 */
171
	public void setSession(Session session) {
172
		this.session = session;
173
	}
174

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

    
202
    // get system metadata and construct the describe response
203
      SystemMetadata sysmeta = getSystemMetadata(session, pid);
204
      DescribeResponse describeResponse = 
205
      	new DescribeResponse(sysmeta.getFormatId(), sysmeta.getSize(), 
206
      			sysmeta.getDateSysMetadataModified(),
207
      			sysmeta.getChecksum(), sysmeta.getSerialVersion());
208

    
209
      return describeResponse;
210

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

    
239
      // do we have a valid pid?
240
      if (pid == null || pid.getValue().trim().equals("")) {
241
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
242
      }
243

    
244
      // check for the existing identifier
245
      try {
246
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
247
      } catch (McdbDocNotFoundException e) {
248
          //throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
249
          logMetacat.warn("D1NodeService.delete - the object itself with the provided identifier "+pid.getValue()+" doesn't exist in the system. But we will continute to delete the system metadata of the object.");
250
          Lock lock = null;
251
          try {
252
              lock = HazelcastService.getInstance().getLock(pid.getValue());
253
              lock.lock();
254
              logMetacat.debug("Locked identifier " + pid.getValue());
255
              SystemMetadata sysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
256
              if ( sysMeta != null ) {
257
                HazelcastService.getInstance().getSystemMetadataMap().remove(pid);
258
                HazelcastService.getInstance().getIdentifiers().remove(pid);
259
                sysMeta.setArchived(true);
260
                try {
261
                    MetacatSolrIndex.getInstance().submit(pid, sysMeta, null, false);
262
                } catch (Exception ee ) {
263
                    logMetacat.warn("D1NodeService.delete - the object with the provided identifier "+pid.getValue()+" was deleted. But the MN solr index can't be deleted.");
264
                }
265
                //since data objects were not registered in the identifier table, we use pid as the docid
266
                EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, pid.getValue(), Event.DELETE.xmlValue());
267
                
268
              } else {
269
                  throw new ServiceFailure("1350", "Couldn't delete the object " + pid.getValue() +
270
                      ". Couldn't obtain the system metadata record.");
271
                  
272
              }
273
              
274
          } catch (RuntimeException re) {
275
              throw new ServiceFailure("1350", "Couldn't delete " + pid.getValue() + 
276
                  ". The error message was: " + re.getMessage());
277
              
278
          } finally {
279
              if(lock != null) {
280
                  lock.unlock();
281
                  logMetacat.debug("Unlocked identifier " + pid.getValue());
282
              }
283
          }
284
          return pid;
285
      } catch (SQLException e) {
286
          throw new ServiceFailure("1350", "The object with the provided " + "identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
287
      }
288
      
289
      try {
290
          // delete the document, as admin
291
          DocumentImpl.delete(localId, null, null, null, true);
292
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
293

    
294
          // archive it
295
          // DocumentImpl.delete() now sets this
296
          // see https://redmine.dataone.org/issues/3406
297
//          SystemMetadata sysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
298
//          sysMeta.setArchived(true);
299
//          sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
300
//          HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
301
          
302
      } catch (McdbDocNotFoundException e) {
303
          throw new NotFound("1340", "The provided identifier was invalid.");
304

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

    
308
      } catch (InsufficientKarmaException e) {
309
          if ( logMetacat.isDebugEnabled() ) {
310
              e.printStackTrace();
311
          }
312
          throw new NotAuthorized("1320", "The provided identity does not have " + "permission to DELETE objects on the Member Node.");
313
      
314
      } catch (Exception e) { // for some reason DocumentImpl throws a general Exception
315
          throw new ServiceFailure("1350", "There was a problem deleting the object." + "The error message was: " + e.getMessage());
316
      }
317

    
318
      return pid;
319
  }
320
  
321
  /**
322
   * Low level, "are you alive" operation. A valid ping response is 
323
   * indicated by a HTTP status of 200.
324
   * 
325
   * @return true if the service is alive
326
   * 
327
   * @throws NotImplemented
328
   * @throws ServiceFailure
329
   * @throws InsufficientResources
330
   */
331
  public Date ping() 
332
      throws NotImplemented, ServiceFailure, InsufficientResources {
333

    
334
      // test if we can get a database connection
335
      int serialNumber = -1;
336
      DBConnection dbConn = null;
337
      try {
338
          dbConn = DBConnectionPool.getDBConnection("MNodeService.ping");
339
          serialNumber = dbConn.getCheckOutSerialNumber();
340
      } catch (SQLException e) {
341
      	ServiceFailure sf = new ServiceFailure("", e.getMessage());
342
      	sf.initCause(e);
343
          throw sf;
344
      } finally {
345
          // Return the database connection
346
          DBConnectionPool.returnDBConnection(dbConn, serialNumber);
347
      }
348

    
349
      return Calendar.getInstance().getTime();
350
  }
351
  
352
  /**
353
   * Adds a new object to the Node, where the object is either a data 
354
   * object or a science metadata object. This method is called by clients 
355
   * to create new data objects on Member Nodes or internally for Coordinating
356
   * Nodes
357
   * 
358
   * @param session - the Session object containing the credentials for the Subject
359
   * @param pid - The object identifier to be created
360
   * @param object - the object bytes
361
   * @param sysmeta - the system metadata that describes the object  
362
   * 
363
   * @return pid - the object identifier created
364
   * 
365
   * @throws InvalidToken
366
   * @throws ServiceFailure
367
   * @throws NotAuthorized
368
   * @throws IdentifierNotUnique
369
   * @throws UnsupportedType
370
   * @throws InsufficientResources
371
   * @throws InvalidSystemMetadata
372
   * @throws NotImplemented
373
   * @throws InvalidRequest
374
   */
375
  public Identifier create(Session session, Identifier pid, InputStream object,
376
    SystemMetadata sysmeta) 
377
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
378
    UnsupportedType, InsufficientResources, InvalidSystemMetadata, 
379
    NotImplemented, InvalidRequest {
380

    
381
    Identifier resultPid = null;
382
    String localId = null;
383
    boolean allowed = false;
384
    
385
    // check for null session
386
    if (session == null) {
387
    	throw new InvalidToken("4894", "Session is required to WRITE to the Node.");
388
    }
389
    Subject subject = session.getSubject();
390

    
391
    Subject publicSubject = new Subject();
392
    publicSubject.setValue(Constants.SUBJECT_PUBLIC);
393
	// be sure the user is authenticated for create()
394
    if (subject == null || subject.getValue() == null || 
395
        subject.equals(publicSubject) ) {
396
      throw new NotAuthorized("1100", "The provided identity does not have " +
397
        "permission to WRITE to the Node.");
398
      
399
    }
400
        
401
    // verify that pid == SystemMetadata.getIdentifier()
402
    logMetacat.debug("Comparing pid|sysmeta_pid: " + 
403
      pid.getValue() + "|" + sysmeta.getIdentifier().getValue());
404
    if (!pid.getValue().equals(sysmeta.getIdentifier().getValue())) {
405
        throw new InvalidSystemMetadata("1180", 
406
            "The supplied system metadata is invalid. " +
407
            "The identifier " + pid.getValue() + " does not match identifier" +
408
            "in the system metadata identified by " +
409
            sysmeta.getIdentifier().getValue() + ".");
410
        
411
    }
412
    
413
    if(sysmeta.getChecksum() == null) {
414
        logMetacat.error("D1NodeService.create - the checksum object from the system metadata shouldn't be null for the object "+pid.getValue());
415
        throw new InvalidSystemMetadata("1180", "The checksum object from the system metadata shouldn't be null.");
416
    } 
417
    
418

    
419
    logMetacat.debug("Checking if identifier exists: " + pid.getValue());
420
    // Check that the identifier does not already exist
421
    boolean idExists = false;
422
    try {
423
        idExists = IdentifierManager.getInstance().identifierExists(pid.getValue());
424
    } catch (SQLException e) {
425
        throw new ServiceFailure("1190", 
426
                                "The requested identifier " + pid.getValue() +
427
                                " couldn't be determined if it is unique since : "+e.getMessage());
428
    }
429
    if (idExists) {
430
	    	throw new IdentifierNotUnique("1120", 
431
			          "The requested identifier " + pid.getValue() +
432
			          " is already used by another object and" +
433
			          "therefore can not be used for this object. Clients should choose" +
434
			          "a new identifier that is unique and retry the operation or " +
435
			          "use CN.reserveIdentifier() to reserve one.");
436
    	
437
    }
438
    
439
    
440
    // TODO: this probably needs to be refined more
441
    try {
442
      allowed = isAuthorized(session, pid, Permission.WRITE);
443
            
444
    } catch (NotFound e) {
445
      // The identifier doesn't exist, writing should be fine.
446
      allowed = true;
447
    }
448
    
449
    if(!allowed) {
450
        throw new NotAuthorized("1100", "Provited Identity doesn't have the WRITE permission on the pid "+pid.getValue());
451
    }
452
 
453
    	
454
    // we have the go ahead
455
    //if ( allowed ) {
456
     
457
      
458
        logMetacat.debug("Allowed to insert: " + pid.getValue());
459

    
460
        // save the sysmeta
461
        try {
462
            // lock and unlock of the pid happens in the subclass
463
            HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
464
        
465
        } catch (Exception e) {
466
            logMetacat.error("D1Node.create - There was problem to save the system metadata: " + pid.getValue(), e);
467
            throw new ServiceFailure("1190", "There was problem to save the system metadata: " + pid.getValue()+" since "+e.getMessage());
468
        }
469
        boolean isScienceMetadata = false;
470
      // Science metadata (XML) or science data object?
471
      // TODO: there are cases where certain object formats are science metadata
472
      // but are not XML (netCDF ...).  Handle this.
473
      if ( isScienceMetadata(sysmeta) ) {
474
        isScienceMetadata = true;
475
        // CASE METADATA:
476
      	//String objectAsXML = "";
477
        try {
478
	        //objectAsXML = IOUtils.toString(object, "UTF-8");
479
            String formatId = null;
480
            if(sysmeta.getFormatId() != null)  {
481
                formatId = sysmeta.getFormatId().getValue();
482
            }
483
	        localId = insertOrUpdateDocument(object,"UTF-8", pid, session, "insert", formatId, sysmeta.getChecksum());
484
	        //localId = im.getLocalId(pid.getValue());
485

    
486
        } catch (IOException e) {
487
            removeSystemMetaAndIdentifier(pid);
488
        	String msg = "The Node is unable to create the object "+pid.getValue() +
489
          " There was a problem converting the object to XML";
490
        	logMetacat.error(msg, e);
491
          throw new ServiceFailure("1190", msg + ": " + e.getMessage());
492

    
493
        } catch (ServiceFailure e) {
494
            removeSystemMetaAndIdentifier(pid);
495
            logMetacat.error("D1NodeService.create - the node couldn't create the object "+pid.getValue()+" since "+e.getMessage(), e);
496
            throw e;
497
        } catch (Exception e) {
498
            removeSystemMetaAndIdentifier(pid);
499
            logMetacat.error("The node is unable to create the object: "+pid.getValue()+ " since " + e.getMessage(), e);
500
            throw new ServiceFailure("1190", "The node is unable to create the object: " +pid.getValue()+" since "+ e.getMessage());
501
        }
502
                    
503
      } else {
504
	        
505
	      // DEFAULT CASE: DATA (needs to be checked and completed)
506
          try {
507
              localId = insertDataObject(object, pid, session, sysmeta.getChecksum());
508
          } catch (ServiceFailure e) {
509
              removeSystemMetaAndIdentifier(pid);
510
              throw e;
511
          } catch (InvalidSystemMetadata e) {
512
              removeSystemMetaAndIdentifier(pid);
513
              throw e;
514
          } catch (Exception e) {
515
              removeSystemMetaAndIdentifier(pid);
516
              throw new ServiceFailure("1190", "The node is unable to create the object "+pid.getValue()+" since " + e.getMessage());
517
          }
518
	      
519
      }   
520
    
521
    //}
522

    
523
    logMetacat.debug("Done inserting new object: " + pid.getValue());
524
    
525
    // setting the resulting identifier failed. We will check if the object does exist.
526
    try {
527
        if (localId == null || !IdentifierManager.getInstance().objectFileExists(localId, isScienceMetadata) ) {
528
            removeSystemMetaAndIdentifier(pid);
529
          throw new ServiceFailure("1190", "The Node is unable to create the object. "+pid.getValue());
530
        }
531
    } catch (PropertyNotFoundException e) {
532
        removeSystemMetaAndIdentifier(pid);
533
        throw new ServiceFailure("1190", "The Node is unable to create the object. "+pid.getValue() + " since "+e.getMessage());
534
    }
535
   
536
    
537
    
538
  
539
    
540
    try {
541
        // submit for indexing
542
        MetacatSolrIndex.getInstance().submit(sysmeta.getIdentifier(), sysmeta, null, true);
543
    } catch (Exception e) {
544
        logMetacat.warn("Couldn't create solr index for object "+pid.getValue());
545
    }
546

    
547
    resultPid = pid;
548
    
549
    logMetacat.info("create() complete for object: " + pid.getValue());
550

    
551
    return resultPid;
552
  }
553
  
554
  /*
555
   * Roll-back method when inserting data object fails.
556
   */
557
  protected void removeSystemMetaAndIdentifier(Identifier id){
558
      if(id != null) {
559
          logMetacat.debug("D1NodeService.removeSystemMeta - the system metadata of object "+id.getValue()+" will removed from both hazelcast and db tables since the object creation failed");
560
          HazelcastService.getInstance().getSystemMetadataMap().remove(id);
561
          logMetacat.info("D1NodeService.removeSystemMeta - the system metadata of object "+id.getValue()+" has been removed from both hazelcast and db tables since the object creation failed");
562
          try {
563
              if(IdentifierManager.getInstance().mappingExists(id.getValue())) {
564
                 String localId = IdentifierManager.getInstance().getLocalId(id.getValue());
565
                 IdentifierManager.getInstance().removeMapping(id.getValue(), localId);
566
                 logMetacat.info("D1NodeService.removeSystemMeta - the identifier "+id.getValue()+" and local id "+localId+" have been removed from the identifier table since the object creation failed");
567
              }
568
          } catch (Exception e) {
569
              logMetacat.warn("D1NodeService.removeSysteMeta - can't decide if the mapping of  the pid "+id.getValue()+" exists on the identifier table.");
570
          }
571
      }
572
  }
573
  
574
  /*
575
   * Roll-back method when inserting data object fails.
576
   */
577
  protected void removeSolrIndex(SystemMetadata sysMeta) {
578
      sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
579
      sysMeta.setArchived(true);
580
      sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
581
      try {
582
          MetacatSolrIndex.getInstance().submit(sysMeta.getIdentifier(), sysMeta, null, false);
583
      } catch (Exception e) {
584
          logMetacat.warn("Can't remove the solr index for pid "+sysMeta.getIdentifier().getValue());
585
      }
586
      
587
  }
588

    
589
  /**
590
   * Return the log records associated with a given event between the start and 
591
   * end dates listed given a particular Subject listed in the Session
592
   * 
593
   * @param session - the Session object containing the credentials for the Subject
594
   * @param fromDate - the start date of the desired log records
595
   * @param toDate - the end date of the desired log records
596
   * @param event - restrict log records of a specific event type
597
   * @param start - zero based offset from the first record in the 
598
   *                set of matching log records. Used to assist with 
599
   *                paging the response.
600
   * @param count - maximum number of log records to return in the response. 
601
   *                Used to assist with paging the response.
602
   * 
603
   * @return the desired log records
604
   * 
605
   * @throws InvalidToken
606
   * @throws ServiceFailure
607
   * @throws NotAuthorized
608
   * @throws InvalidRequest
609
   * @throws NotImplemented
610
   */
611
  public Log getLogRecords(Session session, Date fromDate, Date toDate, 
612
      String event, String pidFilter, Integer start, Integer count) throws InvalidToken, ServiceFailure,
613
      NotAuthorized, InvalidRequest, NotImplemented {
614

    
615
	  // only admin access to this method
616
	  // see https://redmine.dataone.org/issues/2855
617
	  if (!isAdminAuthorized(session)) {
618
		  throw new NotAuthorized("1460", "Only the CN or admin is allowed to harvest logs from this node");
619
	  }
620
    Log log = new Log();
621
    IdentifierManager im = IdentifierManager.getInstance();
622
    EventLog el = EventLog.getInstance();
623
    if ( fromDate == null ) {
624
      logMetacat.debug("setting fromdate from null");
625
      fromDate = new Date(1);
626
    }
627
    if ( toDate == null ) {
628
      logMetacat.debug("setting todate from null");
629
      toDate = new Date();
630
    }
631

    
632
    if ( start == null ) {
633
    	start = 0;	
634
    }
635
    
636
    if ( count == null ) {
637
    	count = 1000;
638
    }
639
    
640
    // safeguard against large requests
641
    if (count > MAXIMUM_DB_RECORD_COUNT) {
642
    	count = MAXIMUM_DB_RECORD_COUNT;
643
    }
644

    
645
    String[] filterDocid = null;
646
    if (pidFilter != null && !pidFilter.trim().equals("")) {
647
        //check if the given identifier is a sid. If it is sid, choose the current pid of the sid.
648
        Identifier pid = new Identifier();
649
        pid.setValue(pidFilter);
650
        String serviceFailureCode = "1490";
651
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
652
        if(sid != null) {
653
            pid = sid;
654
        }
655
        pidFilter = pid.getValue();
656
		try {
657
	      String localId = im.getLocalId(pidFilter);
658
	      filterDocid = new String[] {localId};
659
	    } catch (Exception ex) { 
660
	    	String msg = "Could not find localId for given pidFilter '" + pidFilter + "'";
661
	        logMetacat.warn(msg, ex);
662
	        //throw new InvalidRequest("1480", msg);
663
	        return log; //return 0 record
664
	    }
665
    }
666
    
667
    logMetacat.debug("fromDate: " + fromDate);
668
    logMetacat.debug("toDate: " + toDate);
669

    
670
    log = el.getD1Report(null, null, filterDocid, event,
671
        new java.sql.Timestamp(fromDate.getTime()),
672
        new java.sql.Timestamp(toDate.getTime()), false, start, count);
673
    
674
    logMetacat.info("getLogRecords");
675
    return log;
676
  }
677
    
678
  /**
679
   * Return the object identified by the given object identifier
680
   * 
681
   * @param session - the Session object containing the credentials for the Subject
682
   * @param pid - the object identifier for the given object
683
   * 
684
   * TODO: The D1 Authorization API doesn't provide information on which 
685
   * authentication system the Subject belongs to, and so it's not possible to
686
   * discern which Person or Group is a valid KNB LDAP DN.  Fix this.
687
   * 
688
   * @return inputStream - the input stream of the given object
689
   * 
690
   * @throws InvalidToken
691
   * @throws ServiceFailure
692
   * @throws NotAuthorized
693
   * @throws InvalidRequest
694
   * @throws NotImplemented
695
   */
696
  public InputStream get(Session session, Identifier pid) 
697
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
698
    NotImplemented {
699
    
700
    String serviceFailureCode = "1030";
701
    Identifier sid = getPIDForSID(pid, serviceFailureCode);
702
    if(sid != null) {
703
        pid = sid;
704
    }
705
    
706
    InputStream inputStream = null; // bytes to be returned
707
    handler = new MetacatHandler(new Timer());
708
    boolean allowed = false;
709
    String localId; // the metacat docid for the pid
710
    
711
    // get the local docid from Metacat
712
    try {
713
      localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
714
    
715
    } catch (McdbDocNotFoundException e) {
716
      throw new NotFound("1020", "The object specified by " + 
717
                         pid.getValue() +
718
                         " does not exist at this node.");
719
    } catch (SQLException e) {
720
        throw new ServiceFailure("1030", "The object specified by "+ pid.getValue()+
721
                                  " couldn't be identified at this node since "+e.getMessage());
722
    }
723
    
724
    // check for authorization
725
    try {
726
		allowed = isAuthorized(session, pid, Permission.READ);
727
	} catch (InvalidRequest e) {
728
		throw new ServiceFailure("1030", e.getDescription());
729
	}
730
    
731
    // if the person is authorized, perform the read
732
    if (allowed) {
733
      try {
734
        inputStream = handler.read(localId);
735
      } catch (McdbDocNotFoundException de) {
736
          String error ="";
737
          if(EventLog.getInstance().isDeleted(localId)) {
738
                error=DELETEDMESSAGE;
739
          }
740
          throw new NotFound("1020", "The object specified by " + 
741
                           pid.getValue() +
742
                           " does not exist at this node. "+error);
743
      } catch (Exception e) {
744
        throw new ServiceFailure("1030", "The object specified by " + 
745
            pid.getValue() +
746
            " could not be returned due to error: " +
747
            e.getMessage()+". ");
748
      }
749
    }
750

    
751
    // if we fail to set the input stream
752
    if ( inputStream == null ) {
753
        String error ="";
754
        if(EventLog.getInstance().isDeleted(localId)) {
755
              error=DELETEDMESSAGE;
756
        }
757
        throw new NotFound("1020", "The object specified by " + 
758
                         pid.getValue() +
759
                         " does not exist at this node. "+error);
760
    }
761
    
762
	// log the read event
763
    String principal = Constants.SUBJECT_PUBLIC;
764
    if (session != null && session.getSubject() != null) {
765
    	principal = session.getSubject().getValue();
766
    }
767
    EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "read");
768
    
769
    return inputStream;
770
  }
771

    
772
  /**
773
   * Return the system metadata for a given object
774
   * 
775
   * @param session - the Session object containing the credentials for the Subject
776
   * @param pid - the object identifier for the given object
777
   * 
778
   * @return inputStream - the input stream of the given system metadata object
779
   * 
780
   * @throws InvalidToken
781
   * @throws ServiceFailure
782
   * @throws NotAuthorized
783
   * @throws NotFound
784
   * @throws InvalidRequest
785
   * @throws NotImplemented
786
   */
787
    public SystemMetadata getSystemMetadata(Session session, Identifier pid)
788
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
789
        NotImplemented {
790

    
791
        String serviceFailureCode = "1090";
792
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
793
        if(sid != null) {
794
            pid = sid;
795
        }
796
        boolean isAuthorized = false;
797
        SystemMetadata systemMetadata = null;
798
        List<Replica> replicaList = null;
799
        NodeReference replicaNodeRef = null;
800
        List<Node> nodeListBySubject = null;
801
        Subject subject = null;
802
        
803
        if (session != null ) {
804
            subject = session.getSubject();
805
        }
806
        
807
        // check normal authorization
808
        BaseException originalAuthorizationException = null;
809
        if (!isAuthorized) {
810
            try {
811
                isAuthorized = isAuthorized(session, pid, Permission.READ);
812

    
813
            } catch (InvalidRequest e) {
814
                throw new ServiceFailure("1090", e.getDescription());
815
            } catch (NotAuthorized nae) {
816
            	// catch this for later
817
            	originalAuthorizationException = nae;
818
			}
819
        }
820
        
821
        // get the system metadata first because we need the replica list for auth
822
        systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
823
        
824
        // check the replica information to expand access to MNs that might need it
825
        if (!isAuthorized) {
826
        	
827
	        try {
828
	        	
829
	            // if MNs are listed as replicas, allow access
830
	            if ( systemMetadata != null ) {
831
	                replicaList = systemMetadata.getReplicaList();
832
	                // only check if there are in fact replicas listed
833
	                if ( replicaList != null ) {
834
	                    
835
	                    if ( subject != null ) {
836
	                        // get the list of nodes with a matching node subject
837
	                        try {
838
	                            nodeListBySubject = listNodesBySubject(session.getSubject());
839
	
840
	                        } catch (BaseException e) {
841
	                            // Unexpected error contacting the CN via D1Client
842
	                            String msg = "Caught an unexpected error while trying "
843
	                                    + "to potentially authorize system metadata access "
844
	                                    + "based on the session subject. The error was "
845
	                                    + e.getMessage();
846
	                            logMetacat.error(msg);
847
	                            if (logMetacat.isDebugEnabled()) {
848
	                                e.printStackTrace();
849
	
850
	                            }
851
	                            // isAuthorized is still false 
852
	                        }
853
	
854
	                    }
855
	                    if (nodeListBySubject != null) {
856
	                        // compare node ids to replica node ids
857
	                        outer: for (Replica replica : replicaList) {
858
	                            replicaNodeRef = replica.getReplicaMemberNode();
859
	
860
	                            for (Node node : nodeListBySubject) {
861
	                                if (node.getIdentifier().equals(replicaNodeRef)) {
862
	                                    // node id via session subject matches a replica node
863
	                                    isAuthorized = true;
864
	                                    break outer;
865
	                                }
866
	                            }
867
	                        }
868
	                    }
869
	                }
870
	            }
871
	            
872
	            // if we still aren't authorized, then we are done
873
	            if (!isAuthorized) {
874
	                throw new NotAuthorized("1400", Permission.READ
875
	                        + " not allowed on " + pid.getValue());
876
	            }
877

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

    
1006
      boolean allowed = false;
1007
      
1008
      // must have a session in order to check admin 
1009
      if (session == null) {
1010
         logMetacat.debug("In isAdminAuthorized(), session is null ");
1011
         return false;
1012
      }
1013
      
1014
      logMetacat.debug("In isAdminAuthorized(), checking CN or MN authorization for " +
1015
           session.getSubject().getValue());
1016
      
1017
      // check if this is the node calling itself (MN)
1018
      try {
1019
          allowed = isNodeAdmin(session);
1020
      } catch (Exception e) {
1021
          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.");
1022
      }
1023
      
1024
      
1025
      // check the CN list
1026
      if (!allowed) {
1027
	      allowed = isCNAdmin(session);
1028
      }
1029
      
1030
      return allowed;
1031
  }
1032
  
1033
  /*
1034
   * Determine if the specified session is a CN or not. Return true if it is; otherwise false.
1035
   */
1036
  protected boolean isCNAdmin (Session session) {
1037
      boolean allowed = false;
1038
      List<Node> nodes = null;
1039
      logMetacat.debug("D1NodeService.isCNAdmin - the beginning");
1040
      try {
1041
          // are we allowed to do this? only CNs are allowed
1042
          CNode cn = D1Client.getCN();
1043
          logMetacat.debug("D1NodeService.isCNAdmin - after getting the cn.");
1044
          nodes = cn.listNodes().getNodeList();
1045
          logMetacat.debug("D1NodeService.isCNAdmin - after getting the node list.");
1046
      }
1047
      catch (Throwable e) {
1048
          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.");
1049
          return false;  
1050
      }
1051
          
1052
      if ( nodes == null ) {
1053
          return false;
1054
          //throw new ServiceFailure("4852", "Couldn't get node list.");
1055
      }
1056
      
1057
      // find the node in the node list
1058
      for ( Node node : nodes ) {
1059
          
1060
          NodeReference nodeReference = node.getIdentifier();
1061
          logMetacat.debug("In isCNAdmin(), a Node reference from the CN node list is: " + nodeReference.getValue());
1062
          
1063
          Subject subject = session.getSubject();
1064
          
1065
          if (node.getType() == NodeType.CN) {
1066
              List<Subject> nodeSubjects = node.getSubjectList();
1067
              
1068
              // check if the session subject is in the node subject list
1069
              for (Subject nodeSubject : nodeSubjects) {
1070
                  logMetacat.debug("In isCNAdmin(), comparing subjects: " +
1071
                      nodeSubject.getValue() + " and the user " + subject.getValue());
1072
                  if ( nodeSubject.equals(subject) ) {
1073
                      allowed = true; // subject of session == target node subject
1074
                      break;
1075
                      
1076
                  }
1077
              }              
1078
          }
1079
      }
1080
      logMetacat.debug("D1NodeService.isCNAdmin. Is it a cn admin? "+allowed);
1081
      return allowed;
1082
  }
1083
  
1084
  /**
1085
   * Test if the user identified by the provided token has administrative authorization 
1086
   * on this node because they are calling themselves
1087
   * 
1088
   * @param session - the Session object containing the credentials for the Subject
1089
   * 
1090
   * @return true if the user is this node
1091
   * @throws ServiceFailure 
1092
   * @throws NotImplemented 
1093
   */
1094
  public boolean isNodeAdmin(Session session) throws NotImplemented, ServiceFailure {
1095

    
1096
      boolean allowed = false;
1097
      
1098
      // must have a session in order to check admin 
1099
      if (session == null) {
1100
         logMetacat.debug("In isNodeAdmin(), session is null ");
1101
         return false;
1102
      }
1103
      
1104
      logMetacat.debug("In isNodeAdmin(), MN authorization for the user " +
1105
           session.getSubject().getValue());
1106
      
1107
      Node node = MNodeService.getInstance(request).getCapabilities();
1108
      NodeReference nodeReference = node.getIdentifier();
1109
      logMetacat.debug("In isNodeAdmin(), Node reference is: " + nodeReference.getValue());
1110
      
1111
      Subject subject = session.getSubject();
1112
      
1113
      if (node.getType() == NodeType.MN) {
1114
          List<Subject> nodeSubjects = node.getSubjectList();
1115
          
1116
          // check if the session subject is in the node subject list
1117
          for (Subject nodeSubject : nodeSubjects) {
1118
              logMetacat.debug("In isNodeAdmin(), comparing subjects: " +
1119
                  nodeSubject.getValue() + " and the user" + subject.getValue());
1120
              if ( nodeSubject.equals(subject) ) {
1121
                  allowed = true; // subject of session == this node's subect
1122
                  break;
1123
              }
1124
          }              
1125
      }
1126
      logMetacat.debug("In is NodeAdmin method. Is this a node admin? "+allowed);
1127
      return allowed;
1128
  }
1129
  
1130
  /**
1131
   * Test if the user identified by the provided token has authorization 
1132
   * for the operation on the specified object.
1133
   * Allowed subjects include:
1134
   * 1. CNs
1135
   * 2. Authoritative node
1136
   * 3. Owner of the object
1137
   * 4. Users with the specified permission in the access rules.
1138
   * 
1139
   * @param session - the Session object containing the credentials for the Subject
1140
   * @param pid - The identifer of the resource for which access is being checked
1141
   * @param operation - The type of operation which is being requested for the given pid
1142
   *
1143
   * @return true if the operation is allowed
1144
   * 
1145
   * @throws ServiceFailure
1146
   * @throws InvalidToken
1147
   * @throws NotFound
1148
   * @throws NotAuthorized
1149
   * @throws NotImplemented
1150
   * @throws InvalidRequest
1151
   */
1152
  public boolean isAuthorized(Session session, Identifier pid, Permission permission)
1153
    throws ServiceFailure, InvalidToken, NotFound, NotAuthorized,
1154
    NotImplemented, InvalidRequest {
1155

    
1156
    boolean allowed = false;
1157
    
1158
    if (permission == null) {
1159
    	throw new InvalidRequest("1761", "Permission was not provided or is invalid");
1160
    }
1161
    
1162
    // always allow CN access
1163
    if ( isAdminAuthorized(session) ) {
1164
        allowed = true;
1165
        return allowed;
1166
        
1167
    }
1168
    
1169
    String serviceFailureCode = "1760";
1170
    Identifier sid = getPIDForSID(pid, serviceFailureCode);
1171
    if(sid != null) {
1172
        pid = sid;
1173
    }
1174
    
1175
    // the authoritative member node of the pid always has the access as well.
1176
    if (isAuthoritativeMNodeAdmin(session, pid)) {
1177
        allowed = true;
1178
        return allowed;
1179
    }
1180
    
1181
    //is it the owner of the object or the access rules allow the user?
1182
    allowed = userHasPermission(session,  pid, permission );
1183
    
1184
    // throw or return?
1185
    if (!allowed) {
1186
     // track the identities we have checked against
1187
      StringBuffer includedSubjects = new StringBuffer();
1188
      Set<Subject> subjects = AuthUtils.authorizedClientSubjects(session);
1189
      for (Subject s: subjects) {
1190
             includedSubjects.append(s.getValue() + "; ");
1191
        }    
1192
      throw new NotAuthorized("1820", permission + " not allowed on " + pid.getValue() + " for subject[s]: " + includedSubjects.toString() );
1193
    }
1194
    
1195
    return allowed;
1196
    
1197
  }
1198
  
1199
  
1200
  /*
1201
   * Determine if a user has the permission to perform the specified permission.
1202
   * 1. Owner can have any permission.
1203
   * 2. Access table allow the user has the permission
1204
   */
1205
  public static boolean userHasPermission(Session userSession, Identifier pid, Permission permission ) throws NotFound, ServiceFailure, NotImplemented, InvalidRequest, InvalidToken, NotAuthorized {
1206
      boolean allowed = false;
1207
      // permissions are hierarchical
1208
      List<Permission> expandedPermissions = null;
1209
      // get the subject[s] from the session
1210
      //defer to the shared util for recursively compiling the subjects   
1211
      Set<Subject> subjects = AuthUtils.authorizedClientSubjects(userSession);
1212
          
1213
      // get the system metadata
1214
      String pidStr = pid.getValue();
1215
      SystemMetadata systemMetadata = null;
1216
      try {
1217
          systemMetadata = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1218

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

    
1320
          while(subjects != null) {
1321
              logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the returned result is not null");
1322
              List<Group> groups = subjects.getGroupList();
1323
              is = isInGroups(userSession, rightHolder, groups);
1324
              if(is) {
1325
                  //since we find it, return it.
1326
                  return is;
1327
              } else {
1328
                  //decide if we need to try the page query for another trying.
1329
                  int sizeOfGroups = 0;
1330
                  if(groups != null) {
1331
                     sizeOfGroups  = groups.size();
1332
                  }
1333
                  List<Person> persons = subjects.getPersonList();
1334
                  int sizeOfPersons = 0;
1335
                  if(persons != null) {
1336
                      sizeOfPersons = persons.size();
1337
                  }
1338
                  int totalSize = sizeOfGroups+sizeOfPersons;
1339
                  //logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the size of return result is "+totalSize);
1340
                 //we can't find the target on the first query, maybe query again.
1341
                  if(totalSize == count) {
1342
                      start = start+count;
1343
                      logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the size of return result equals the count "+totalSize+" .And we didn't find the target in the this query. So we have to use the page query with the start number "+start);
1344
                      subjects = cn.listSubjects(session, query, status, start, count);
1345
                  } else if (totalSize < count){
1346
                      logMetacat.debug("D1NodeService.expandRightHolder - we are already at the end of the returned restult since the size of returned results "+totalSize+
1347
                          " is less than the count "+count+". So we have to break the loop and finish the try.");
1348
                      break;
1349
                  } else if (totalSize >count) {
1350
                      logMetacat.warn("D1NodeService.expandRightHolder - Something is wrong on the implementation of the method listSubject since the size of returned results "+totalSize+
1351
                              " is greater than the count "+count+". So we have to break the loop and finish the try.");
1352
                      break;
1353
                  }
1354
              }
1355
              
1356
          } 
1357
          //logMetacat.debug("D1NodeService.expandRightHolder - search the subject "+query+" in the cn and the returned result is null");
1358
          if(!is) {
1359
              logMetacat.debug("D1NodeService.expandRightHolder - We can NOT find any member in the group "+query+" (if it is a group) matches the user "+userSession.getValue());
1360
          }
1361
      } else {
1362
          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");
1363
      }
1364
     
1365
      return is;
1366
  }
1367
  
1368
  /*
1369
   * If the given useSession is a member of a group which is in the given list of groups and has the name of righHolder.
1370
   */
1371
  private static boolean isInGroups(Subject userSession, Subject rightHolder, List<Group> groups) {
1372
      boolean is = false;
1373
      if(groups != null) {
1374
          logMetacat.debug("D1NodeService.isInGroups -  the given groups' (the returned result including groups) size is "+groups.size());
1375
          for(Group group : groups) {
1376
              //logMetacat.debug("D1NodeService.expandRightHolder - group has the subject "+group.getSubject().getValue());
1377
              if(group != null && group.getSubject() != null && group.getSubject().equals(rightHolder)) {
1378
                  logMetacat.debug("D1NodeService.isInGroups - there is a group in the list having the subjecct "+group.getSubject().getValue()+" which matches the right holder's subject "+rightHolder.getValue());
1379
                  List<Subject> members = group.getHasMemberList();
1380
                  if(members != null ){
1381
                      logMetacat.debug("D1NodeService.isInGroups - the group "+group.getSubject().getValue()+" in the cn has members");
1382
                      for(Subject member : members) {
1383
                          logMetacat.debug("D1NodeService.isInGroups - compare the member "+member.getValue()+" with the user "+userSession.getValue());
1384
                          if(member.getValue() != null && !member.getValue().trim().equals("") && userSession.getValue() != null && member.getValue().equals(userSession.getValue())) {
1385
                              logMetacat.debug("D1NodeService.isInGroups - Find it! The member "+member.getValue()+" in the group "+group.getSubject().getValue()+" matches the user "+userSession.getValue());
1386
                              is = true;
1387
                              return is;
1388
                          }
1389
                      }
1390
                  }
1391
                  break;//we found the group but can't find the member matches the user. so break it.
1392
              }
1393
          }
1394
      } else {
1395
          logMetacat.debug("D1NodeService.isInGroups -  the given group is null (the returned result does NOT have a group");
1396
      }
1397
      return is;
1398
  }
1399
  /*
1400
   * parse a logEntry and get the relevant field from it
1401
   * 
1402
   * @param fieldname
1403
   * @param entry
1404
   * @return
1405
   */
1406
  private String getLogEntryField(String fieldname, String entry) {
1407
    String begin = "<" + fieldname + ">";
1408
    String end = "</" + fieldname + ">";
1409
    // logMetacat.debug("looking for " + begin + " and " + end +
1410
    // " in entry " + entry);
1411
    String s = entry.substring(entry.indexOf(begin) + begin.length(), entry
1412
        .indexOf(end));
1413
    logMetacat.debug("entry " + fieldname + " : " + s);
1414
    return s;
1415
  }
1416

    
1417
  /** 
1418
   * Determine if a given object should be treated as an XML science metadata
1419
   * object. 
1420
   * 
1421
   * @param sysmeta - the SystemMetadata describing the object
1422
   * @return true if the object should be treated as science metadata
1423
   */
1424
  public static boolean isScienceMetadata(SystemMetadata sysmeta) {
1425
    
1426
    ObjectFormat objectFormat = null;
1427
    boolean isScienceMetadata = false;
1428
    
1429
    try {
1430
      objectFormat = ObjectFormatCache.getInstance().getFormat(sysmeta.getFormatId());
1431
      if ( objectFormat.getFormatType().equals("METADATA") ) {
1432
      	isScienceMetadata = true;
1433
      	
1434
      }
1435
      
1436
       
1437
    /*} catch (ServiceFailure e) {
1438
      logMetacat.debug("There was a problem determining if the object identified by" + 
1439
          sysmeta.getIdentifier().getValue() + 
1440
          " is science metadata: " + e.getMessage());*/
1441
    
1442
    } catch (NotFound e) {
1443
      logMetacat.debug("There was a problem determining if the object identified by" + 
1444
          sysmeta.getIdentifier().getValue() + 
1445
          " is science metadata: " + e.getMessage());
1446
    
1447
    }
1448
    
1449
    return isScienceMetadata;
1450

    
1451
  }
1452
  
1453
  /**
1454
   * Check fro whitespace in the given pid.
1455
   * null pids are also invalid by default
1456
   * @param pid
1457
   * @return
1458
   */
1459
  public static boolean isValidIdentifier(Identifier pid) {
1460
	  if (pid != null && pid.getValue() != null && pid.getValue().length() > 0) {
1461
		  return !pid.getValue().matches(".*\\s+.*");
1462
	  } 
1463
	  return false;
1464
  }
1465
  
1466
  
1467
  /**
1468
   * Insert or update an XML document into Metacat
1469
   * 
1470
   * @param xml - the XML document to insert or update
1471
   * @param pid - the identifier to be used for the resulting object
1472
   * 
1473
   * @return localId - the resulting docid of the document created or updated
1474
   * 
1475
   */
1476
  public String insertOrUpdateDocument(InputStream xmlStream, String encoding,  Identifier pid, 
1477
    Session session, String insertOrUpdate, String formatId, Checksum checksum) 
1478
    throws ServiceFailure, IOException, PropertyNotFoundException{
1479
    
1480
  	logMetacat.debug("Starting to insert xml document...");
1481
    IdentifierManager im = IdentifierManager.getInstance();
1482

    
1483
    // generate pid/localId pair for sysmeta
1484
    String localId = null;
1485
    byte[] xmlBytes  = IOUtils.toByteArray(xmlStream);
1486
    IOUtils.closeQuietly(xmlStream);
1487
    String xmlStr = new String(xmlBytes, encoding);
1488
    if(insertOrUpdate.equals("insert")) {
1489
      localId = im.generateLocalId(pid.getValue(), 1);
1490
      
1491
    } else {
1492
      //localid should already exist in the identifier table, so just find it
1493
      try {
1494
        logMetacat.debug("Updating pid " + pid.getValue());
1495
        logMetacat.debug("looking in identifier table for pid " + pid.getValue());
1496
        
1497
        localId = im.getLocalId(pid.getValue());
1498
        
1499
        logMetacat.debug("localId: " + localId);
1500
        //increment the revision
1501
        String docid = localId.substring(0, localId.lastIndexOf("."));
1502
        String revS = localId.substring(localId.lastIndexOf(".") + 1, localId.length());
1503
        int rev = new Integer(revS).intValue();
1504
        rev++;
1505
        docid = docid + "." + rev;
1506
        localId = docid;
1507
        logMetacat.debug("incremented localId: " + localId);
1508
      
1509
      } catch(McdbDocNotFoundException e) {
1510
        throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument(): " +
1511
            "pid " + pid.getValue() + 
1512
            " should have been in the identifier table, but it wasn't: " + 
1513
            e.getMessage());
1514
      
1515
      } catch (SQLException e) {
1516
          throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument() -"+
1517
                     " couldn't identify if the pid "+pid.getValue()+" is in the identifier table since "+e.getMessage());
1518
      }
1519
      
1520
    }
1521

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

    
1625
    logMetacat.debug("Case DATA: starting to write to disk.");
1626
	if (locked) {
1627

    
1628
          File dataDirectory = new File(datafilepath);
1629
          dataDirectory.mkdirs();
1630
  
1631
          File newFile = writeStreamToFile(dataDirectory, localId, object, checksum, pid);
1632
  
1633
          // TODO: Check that the file size matches SystemMetadata
1634
          // long size = newFile.length();
1635
          // if (size == 0) {
1636
          //     throw new IOException("Uploaded file is 0 bytes!");
1637
          // }
1638
  
1639
          // Register the file in the database (which generates an exception
1640
          // if the localId is not acceptable or other untoward things happen
1641
          try {
1642
            logMetacat.debug("Registering document...");
1643
            DocumentImpl.registerDocument(localId, "BIN", localId,
1644
                    username, groupnames);
1645
            logMetacat.debug("Registration step completed.");
1646
            
1647
          } catch (SQLException e) {
1648
            //newFile.delete();
1649
            logMetacat.debug("SQLE: " + e.getMessage());
1650
            e.printStackTrace(System.out);
1651
            throw new ServiceFailure("1190", "Registration failed: " + 
1652
            		e.getMessage());
1653
            
1654
          } catch (AccessionNumberException e) {
1655
            //newFile.delete();
1656
            logMetacat.debug("ANE: " + e.getMessage());
1657
            e.printStackTrace(System.out);
1658
            throw new ServiceFailure("1190", "Registration failed: " + 
1659
            	e.getMessage());
1660
            
1661
          } catch (Exception e) {
1662
            //newFile.delete();
1663
            logMetacat.debug("Exception: " + e.getMessage());
1664
            e.printStackTrace(System.out);
1665
            throw new ServiceFailure("1190", "Registration failed: " + 
1666
            	e.getMessage());
1667
          }
1668
  
1669
          logMetacat.debug("Logging the creation event.");
1670
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, "create");
1671
  
1672
          // Schedule replication for this data file, the "insert" action is important here!
1673
          logMetacat.debug("Scheduling replication.");
1674
          ForceReplicationHandler frh = new ForceReplicationHandler(localId, "insert", false, null);
1675
      }
1676
      
1677
      return localId;
1678
    
1679
  }
1680

    
1681
  /**
1682
   * Insert a systemMetadata document and return its localId
1683
   */
1684
  public void insertSystemMetadata(SystemMetadata sysmeta) 
1685
      throws ServiceFailure {
1686
      
1687
  	  logMetacat.debug("Starting to insert SystemMetadata...");
1688
      sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
1689
      logMetacat.debug("Inserting new system metadata with modified date " + 
1690
          sysmeta.getDateSysMetadataModified());
1691
      
1692
      //insert the system metadata
1693
      try {
1694
        // note: the calling subclass handles the map hazelcast lock/unlock
1695
      	HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
1696
      	// submit for indexing
1697
        MetacatSolrIndex.getInstance().submit(sysmeta.getIdentifier(), sysmeta, null, true);
1698
      } catch (Exception e) {
1699
          throw new ServiceFailure("1190", e.getMessage());
1700
          
1701
	    }  
1702
  }
1703
  
1704
  /**
1705
   * Retrieve the list of objects present on the MN that match the calling parameters
1706
   * 
1707
   * @param session - the Session object containing the credentials for the Subject
1708
   * @param startTime - Specifies the beginning of the time range from which 
1709
   *                    to return object (>=)
1710
   * @param endTime - Specifies the beginning of the time range from which 
1711
   *                  to return object (>=)
1712
   * @param objectFormat - Restrict results to the specified object format
1713
   * @param replicaStatus - Indicates if replicated objects should be returned in the list
1714
   * @param start - The zero-based index of the first value, relative to the 
1715
   *                first record of the resultset that matches the parameters.
1716
   * @param count - The maximum number of entries that should be returned in 
1717
   *                the response. The Member Node may return less entries 
1718
   *                than specified in this value.
1719
   * 
1720
   * @return objectList - the list of objects matching the criteria
1721
   * 
1722
   * @throws InvalidToken
1723
   * @throws ServiceFailure
1724
   * @throws NotAuthorized
1725
   * @throws InvalidRequest
1726
   * @throws NotImplemented
1727
   */
1728
  public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Identifier identifier, NodeReference nodeId, Integer start,
1729
          Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
1730

    
1731
      ObjectList objectList = null;
1732

    
1733
      try {
1734
          // safeguard against large requests
1735
          if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
1736
              count = MAXIMUM_DB_RECORD_COUNT;
1737
          }
1738
          boolean isSid = false;
1739
          if(identifier != null) {
1740
              isSid = IdentifierManager.getInstance().systemMetadataSIDExists(identifier);
1741
          }
1742
          objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, nodeId, start, count, identifier, isSid);
1743
      } catch (Exception e) {
1744
          throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1745
      }
1746

    
1747
      return objectList;
1748
  }
1749

    
1750

    
1751
  /**
1752
   * Update a systemMetadata document
1753
   * 
1754
   * @param sysMeta - the system metadata object in the system to update
1755
   */
1756
    protected void updateSystemMetadata(SystemMetadata sysMeta)
1757
        throws ServiceFailure {
1758
        logMetacat.debug("D1NodeService.updateSystemMetadata() called.");
1759
        try {
1760
            HazelcastService.getInstance().getSystemMetadataMap().lock(sysMeta.getIdentifier());
1761
            boolean needUpdateModificationDate = true;
1762
            updateSystemMetadataWithoutLock(sysMeta, needUpdateModificationDate);
1763
        } catch (Exception e) {
1764
            throw new ServiceFailure("4862", e.getMessage());
1765
        } finally {
1766
            HazelcastService.getInstance().getSystemMetadataMap().unlock(sysMeta.getIdentifier());
1767

    
1768
        }
1769

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

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

    
2114
  /*
2115
   * Write a stream to a file
2116
   * 
2117
   * @param dir - the directory to write to
2118
   * @param fileName - the file name to write to
2119
   * @param data - the object bytes as an input stream
2120
   * 
2121
   * @return newFile - the new file created
2122
   * 
2123
   * @throws ServiceFailure
2124
   */
2125
  private File writeStreamToFile(File dir, String fileName, InputStream dataStream, Checksum checksum, Identifier pid) 
2126
    throws ServiceFailure, InvalidSystemMetadata {
2127
    
2128
    File newFile = new File(dir, fileName);
2129
    logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath()+" for the data object pid "+pid.getValue());
2130

    
2131
    try {
2132
        if (newFile.createNewFile()) {
2133
            String checksumValue = checksum.getValue();
2134
            logMetacat.info("D1NodeService.writeStreamToFile - the checksum value from the system metadata is "+checksumValue+" for the data object "+pid.getValue());
2135
            if(checksumValue == null || checksumValue.trim().equals("")) {
2136
                logMetacat.error("D1NodeService.writeStreamToFile - the checksum value from the system metadata shouldn't be null or blank for the data object "+pid.getValue());
2137
                throw new InvalidSystemMetadata("1180", "The checksum value from the system metadata shouldn't be null or blank.");
2138
            }
2139
            String algorithm = checksum.getAlgorithm();
2140
            logMetacat.info("D1NodeService.writeStreamToFile - the algorithm to calculate the checksum from the system metadata is "+algorithm+" for the data object "+pid.getValue());
2141
            if(algorithm == null || algorithm.trim().equals("")) {
2142
                logMetacat.error("D1NodeService.writeStreamToFile - the algorithm to calculate the checksum from the system metadata shouldn't be null or blank for the data object "+pid.getValue());
2143
                throw new InvalidSystemMetadata("1180", "The algorithm to calculate the checksum from the system metadata shouldn't be null or blank.");
2144
            }
2145
          MessageDigest md = MessageDigest.getInstance(algorithm);
2146
          // write data stream to desired file
2147
          DigestOutputStream os = new DigestOutputStream( new FileOutputStream(newFile), md);
2148
          long length = IOUtils.copyLarge(dataStream, os);
2149
          os.flush();
2150
          os.close();
2151
          String localChecksum = DatatypeConverter.printHexBinary(md.digest());
2152
          logMetacat.info("D1NodeService.writeStreamToFile - the check sum calculated from the saved local file is "+localChecksum);
2153
          if(localChecksum == null || localChecksum.trim().equals("") || !localChecksum.equalsIgnoreCase(checksumValue)) {
2154
              logMetacat.error("D1NodeService.writeStreamToFile - the check sum calculated from the saved local file is "+localChecksum+ ". But it doesn't match the value from the system metadata "+checksumValue+" for the object "+pid.getValue());
2155
              boolean success = newFile.delete();
2156
              logMetacat.info("delete the file "+newFile.getAbsolutePath()+" for the object "+pid.getValue()+" sucessfully?"+success);
2157
              throw new InvalidSystemMetadata("1180", "The checksum calculated from the saved local file is "+localChecksum+ ". But it doesn't match the value from the system metadata "+checksumValue+".");
2158
          }
2159
          
2160
        } else {
2161
          logMetacat.debug("File creation failed, or file already exists.");
2162
          throw new ServiceFailure("1190", "File already exists: " + fileName);
2163
        }
2164
    } catch (FileNotFoundException e) {
2165
      logMetacat.error("FNF: " + e.getMessage()+" for the data object "+pid.getValue(), e);
2166
      throw new ServiceFailure("1190", "File not found: " + fileName + " " 
2167
                + e.getMessage());
2168
    } catch (IOException e) {
2169
      logMetacat.error("IOE: " + e.getMessage()+" for the data object "+pid.getValue(), e);
2170
      throw new ServiceFailure("1190", "File was not written: " + fileName 
2171
                + " " + e.getMessage());
2172
    } catch (NoSuchAlgorithmException e) {
2173
        logMetacat.error("D1NodeService.writeStreamToFile - no such checksum algorithm exception " + e.getMessage()+" for the data object "+pid.getValue(), e);
2174
        throw new ServiceFailure("1190", "No such checksum algorithm: "  
2175
                + " " + e.getMessage());
2176
    } finally {
2177
        IOUtils.closeQuietly(dataStream);
2178
    }
2179

    
2180
    return newFile;
2181
  }
2182

    
2183
  /*
2184
   * Returns a list of nodes that have been registered with the DataONE infrastructure
2185
   * that match the given session subject
2186
   * @return nodes - List of nodes from the registry with a matching session subject
2187
   * 
2188
   * @throws ServiceFailure
2189
   * @throws NotImplemented
2190
   */
2191
  protected List<Node> listNodesBySubject(Subject subject) 
2192
      throws ServiceFailure, NotImplemented {
2193
      List<Node> nodeList = new ArrayList<Node>();
2194
      
2195
      CNode cn = D1Client.getCN();
2196
      List<Node> nodes = cn.listNodes().getNodeList();
2197
      
2198
      // find the node in the node list
2199
      for ( Node node : nodes ) {
2200
          
2201
          List<Subject> nodeSubjects = node.getSubjectList();
2202
          if (nodeSubjects != null) {    
2203
	          // check if the session subject is in the node subject list
2204
	          for (Subject nodeSubject : nodeSubjects) {
2205
	              if ( nodeSubject.equals(subject) ) { // subject of session == node subject
2206
	                  nodeList.add(node);  
2207
	              }                              
2208
	          }
2209
          }
2210
      }
2211
      
2212
      return nodeList;
2213
      
2214
  }
2215

    
2216
  /**
2217
   * Archives an object, where the object is either a 
2218
   * data object or a science metadata object.
2219
   * Note: it doesn't check the authorization; it doesn't lock the system metadata;it only accept pid.
2220
   * @param session - the Session object containing the credentials for the Subject
2221
   * @param pid - The object identifier to be archived
2222
   * 
2223
   * @return pid - the identifier of the object used for the archiving
2224
   * 
2225
   * @throws InvalidToken
2226
   * @throws ServiceFailure
2227
   * @throws NotAuthorized
2228
   * @throws NotFound
2229
   * @throws NotImplemented
2230
   * @throws InvalidRequest
2231
   */
2232
  protected Identifier archiveObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2233
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2234

    
2235
      String localId = null;
2236
      boolean allowed = false;
2237
      String username = Constants.SUBJECT_PUBLIC;
2238
      if (session == null) {
2239
      	throw new InvalidToken("1330", "No session has been provided");
2240
      } else {
2241
          username = session.getSubject().getValue();
2242
      }
2243
      // do we have a valid pid?
2244
      if (pid == null || pid.getValue().trim().equals("")) {
2245
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
2246
      }
2247
      
2248
      if(sysMeta == null) {
2249
          throw new NotFound("2911", "There is no system metadata associated with "+pid.getValue());
2250
      }
2251
      
2252
      // check for the existing identifier
2253
      try {
2254
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2255
      } catch (McdbDocNotFoundException e) {
2256
          throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
2257
      } catch (SQLException e) {
2258
          throw new ServiceFailure("1350", "The object with the provided identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
2259
      }
2260

    
2261

    
2262
          try {
2263
              // archive the document
2264
              DocumentImpl.delete(localId, null, null, null, false);
2265
              if(log) {
2266
                   try {
2267
                      EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
2268
                   } catch (Exception e) {
2269
                      logMetacat.warn("D1NodeService.archiveObject - can't log the delete event since "+e.getMessage());
2270
                   }
2271
              }
2272
             
2273
              
2274
              // archive it
2275
              sysMeta.setArchived(true);
2276
              if(needModifyDate) {
2277
                  sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2278
                  sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2279
              }
2280
              HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2281
              
2282
              // submit for indexing
2283
              // DocumentImpl call above should do this.
2284
              // see: https://projects.ecoinformatics.org/ecoinfo/issues/6030
2285
              //HazelcastService.getInstance().getIndexQueue().add(sysMeta);
2286
              
2287
          } catch (McdbDocNotFoundException e) {
2288
              try {
2289
                  AccessionNumber acc = new AccessionNumber(localId, "NOACTION");
2290
                  String docid = acc.getDocid();
2291
                  int rev = 1;
2292
                  if (acc.getRev() != null) {
2293
                    rev = (new Integer(acc.getRev()).intValue());
2294
                  }
2295
                  if(IdentifierManager.getInstance().existsInXmlLRevisionTable(docid, rev)) {
2296
                      //somehow the document is in the xml_revision table.
2297
                      // archive it
2298
                      sysMeta.setArchived(true);
2299
                      if(needModifyDate) {
2300
                          sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2301
                          sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2302
                      }
2303
                      HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2304
                  } else {
2305
                      throw new NotFound("1340", "The provided identifier "+ pid.getValue()+" is invalid");
2306
                  }
2307
              } catch (SQLException ee) {
2308
                  ee.printStackTrace();
2309
                  throw new NotFound("1340", "The provided identifier "+ pid.getValue()+" is invalid");
2310
              } catch (AccessionNumberException ee) {
2311
                  ee.printStackTrace();
2312
                  throw new NotFound("1340", "The provided identifier "+ pid.getValue()+" is invalid");
2313
              }
2314
          } catch (SQLException e) {
2315
              throw new ServiceFailure("1350", "There was a problem archiving the object." + "The error message was: " + e.getMessage());
2316

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

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

    
2324

    
2325
      return pid;
2326
  }
2327
  
2328
  /**
2329
   * Archive a object on cn and notify the replica. This method doesn't lock the system metadata map. The caller should lock it.
2330
   * This method doesn't check the authorization; this method only accept a pid.
2331
   * It wouldn't notify the replca that the system metadata has been changed.
2332
   * @param session
2333
   * @param pid
2334
   * @param sysMeta
2335
   * @param notifyReplica
2336
   * @return
2337
   * @throws InvalidToken
2338
   * @throws ServiceFailure
2339
   * @throws NotAuthorized
2340
   * @throws NotFound
2341
   * @throws NotImplemented
2342
   */
2343
  protected void archiveCNObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2344
          throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2345

    
2346
          String localId = null; // The corresponding docid for this pid
2347
          
2348
          // Check for the existing identifier
2349
          try {
2350
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2351
              archiveObject(log, session, pid, sysMeta, needModifyDate);
2352
          
2353
          } catch (McdbDocNotFoundException e) {
2354
              // This object is not registered in the identifier table. Assume it is of formatType DATA,
2355
              // and set the archive flag. (i.e. the *object* doesn't exist on the CN)
2356
              
2357
              try {
2358
                  if ( sysMeta != null ) {
2359
                    sysMeta.setArchived(true);
2360
                    if (needModifyDate) {
2361
                        sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2362
                        sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2363
                    }
2364
                    HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2365
                      
2366
                  } else {
2367
                      throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2368
                          ". Couldn't obtain the system metadata record.");
2369
                      
2370
                  }
2371
                  
2372
              } catch (RuntimeException re) {
2373
                  throw new ServiceFailure("4972", "Couldn't archive " + pid.getValue() + 
2374
                      ". The error message was: " + re.getMessage());
2375
                  
2376
              } 
2377

    
2378
          } catch (SQLException e) {
2379
              throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2380
                      ". The local id of the object with the identifier can't be identified since "+e.getMessage());
2381
          }
2382
          
2383
    }
2384
  
2385
  
2386
  /**
2387
   * A utility method for v1 api to check the specified identifier exists as a pid
2388
   * @param identifier  the specified identifier
2389
   * @param serviceFailureCode  the detail error code for the service failure exception
2390
   * @param noFoundCode  the detail error code for the not found exception
2391
   * @throws ServiceFailure
2392
   * @throws NotFound
2393
   */
2394
  public void checkV1SystemMetaPidExist(Identifier identifier, String serviceFailureCode, String serviceFailureMessage,  
2395
          String noFoundCode, String notFoundMessage) throws ServiceFailure, NotFound {
2396
      boolean exists = false;
2397
      try {
2398
          exists = IdentifierManager.getInstance().systemMetadataPIDExists(identifier);
2399
      } catch (SQLException e) {
2400
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+e.getMessage());
2401
      }
2402
      if(!exists) {
2403
         //the v1 method only handles a pid. so it should throw a not-found exception.
2404
          // check if the pid was deleted.
2405
          try {
2406
              String localId = IdentifierManager.getInstance().getLocalId(identifier.getValue());
2407
              if(EventLog.getInstance().isDeleted(localId)) {
2408
                  notFoundMessage=notFoundMessage+" "+DELETEDMESSAGE;
2409
              } 
2410
            } catch (Exception e) {
2411
              logMetacat.info("Couldn't determine if the not-found identifier "+identifier.getValue()+" was deleted since "+e.getMessage());
2412
            }
2413
            throw new NotFound(noFoundCode, notFoundMessage);
2414
      }
2415
  }
2416
  
2417
  /**
2418
   * Utility method to get the PID for an SID. If the specified identifier is not an SID
2419
   * , null will be returned.
2420
   * @param sid  the specified sid
2421
   * @param serviceFailureCode  the detail error code for the service failure exception
2422
   * @return the pid for the sid. If the specified identifier is not an SID, null will be returned.
2423
   * @throws ServiceFailure
2424
   */
2425
  protected Identifier getPIDForSID(Identifier sid, String serviceFailureCode) throws ServiceFailure {
2426
      Identifier id = null;
2427
      String serviceFailureMessage = "The PID "+" couldn't be identified for the sid " + sid.getValue();
2428
      try {
2429
          //determine if the given pid is a sid or not.
2430
          if(IdentifierManager.getInstance().systemMetadataSIDExists(sid)) {
2431
              try {
2432
                  //set the header pid for the sid if the identifier is a sid.
2433
                  id = IdentifierManager.getInstance().getHeadPID(sid);
2434
              } catch (SQLException sqle) {
2435
                  throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+sqle.getMessage());
2436
              }
2437
              
2438
          }
2439
      } catch (SQLException e) {
2440
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage + " since "+e.getMessage());
2441
      }
2442
      return id;
2443
  }
2444

    
2445
  /*
2446
   * 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:
2447
   * A. If the sysmeta doesn't have an SID, nothing needs to be checked for the SID.
2448
   * B. If the sysmeta does have an SID, it may be an identifier which doesn't exist in the system.
2449
   * C. If the sysmeta does have an SID and it exists as an SID in the system, those scenarios are acceptable:
2450
   *    i. The sysmeta has an obsoletes field, the SID has the same value as the SID of the system metadata of the obsoleting pid.
2451
   *    ii. The sysmeta has an obsoletedBy field, the SID has the same value as the SID of the system metadata of the obsoletedBy pid. 
2452
   */
2453
  protected boolean checkSidInModifyingSystemMetadata(SystemMetadata sysmeta, String invalidSystemMetadataCode, String serviceFailureCode) throws InvalidSystemMetadata, ServiceFailure{
2454
      boolean pass = false;
2455
      if(sysmeta == null) {
2456
          throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The system metadata is null in the request.");
2457
      }
2458
      Identifier sid = sysmeta.getSeriesId();
2459
      if(sid != null) {
2460
          // the series id exists
2461
          if (!isValidIdentifier(sid)) {
2462
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id in the system metadata is invalid in the request.");
2463
          }
2464
          Identifier pid = sysmeta.getIdentifier();
2465
          if (!isValidIdentifier(pid)) {
2466
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The pid in the system metadata is invalid in the request.");
2467
          }
2468
          //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 )
2469
          if(sid.getValue().equals(pid.getValue())) {
2470
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
2471
          }
2472
          try {
2473
              if (IdentifierManager.getInstance().identifierExists(sid.getValue())) {
2474
                  //the sid exists in system
2475
                  if(sysmeta.getObsoletes() != null) {
2476
                      SystemMetadata obsoletesSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletes());
2477
                      if(obsoletesSysmeta != null) {
2478
                          Identifier obsoletesSid = obsoletesSysmeta.getSeriesId();
2479
                          if(obsoletesSid != null && obsoletesSid.getValue() != null && !obsoletesSid.getValue().trim().equals("")) {
2480
                              if(sid.getValue().equals(obsoletesSid.getValue())) {
2481
                                  pass = true;// the i of rule C
2482
                              }
2483
                          }
2484
                      } else {
2485
                           logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue()+
2486
                                                                         " which is the value of the obsoletes. So we can't check if the sid " +sid.getValue()+" is legitimate ");
2487
                      }
2488
                  }
2489
                  if(!pass) {
2490
                      // the sid doesn't match the sid of the obsoleting identifier. So we check the obsoletedBy
2491
                      if(sysmeta.getObsoletedBy() != null) {
2492
                          SystemMetadata obsoletedBySysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletedBy());
2493
                          if(obsoletedBySysmeta != null) {
2494
                              Identifier obsoletedBySid = obsoletedBySysmeta.getSeriesId();
2495
                              if(obsoletedBySid != null && obsoletedBySid.getValue() != null && !obsoletedBySid.getValue().trim().equals("")) {
2496
                                  if(sid.getValue().equals(obsoletedBySid.getValue())) {
2497
                                      pass = true;// the ii of the rule C
2498
                                  }
2499
                              }
2500
                          } else {
2501
                              logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue() 
2502
                                                                            +" which is the value of the obsoletedBy. So we can't check if the sid "+sid.getValue()+" is legitimate.");
2503
                          }
2504
                      }
2505
                  }
2506
                  if(!pass) {
2507
                      throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+
2508
                              " in the system metadata exists in the system. And it doesn't match either previous object's sid or the next object's sid.");
2509
                  }
2510
              } else {
2511
                  pass = true; //Rule B
2512
              }
2513
          } catch (SQLException e) {
2514
              throw new ServiceFailure(serviceFailureCode, "Can't determine if the sid in the system metadata is unique or not since "+e.getMessage());
2515
          }
2516
          
2517
      } else {
2518
          //no sid. Rule A.
2519
          pass = true;
2520
      }
2521
      return pass;
2522
      
2523
  }
2524
  
2525
  //@Override
2526
  public OptionList listViews(Session arg0) throws InvalidToken,
2527
          ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented {
2528
      OptionList views = new OptionList();
2529
      views.setKey("views");
2530
      views.setDescription("List of views for objects on the node");
2531
      Vector<String> skinNames = null;
2532
      try {
2533
          skinNames = SkinUtil.getSkinNames();
2534
      } catch (PropertyNotFoundException e) {
2535
          throw new ServiceFailure("2841", e.getMessage());
2536
      }
2537
      for (String skinName: skinNames) {
2538
          views.addOption(skinName);
2539
      }
2540
      return views;
2541
  }
2542
  
2543
  public OptionList listViews() throws InvalidToken,
2544
  ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented { 
2545
      return listViews(null);
2546
  }
2547

    
2548
  //@Override
2549
  public InputStream view(Session session, String format, Identifier pid)
2550
          throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
2551
          NotImplemented, NotFound {
2552
      InputStream resultInputStream = null;
2553
      
2554
      String serviceFailureCode = "2831";
2555
      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2556
      if(sid != null) {
2557
          pid = sid;
2558
      }
2559
      
2560
      SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2561
      InputStream object = this.get(session, pid);
2562

    
2563
      try {
2564
          // can only transform metadata, really
2565
          ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2566
          if (objectFormat.getFormatType().equals("METADATA")) {
2567
              // transform
2568
              DBTransform transformer = new DBTransform();
2569
              String documentContent = IOUtils.toString(object, "UTF-8");
2570
              String sourceType = objectFormat.getFormatId().getValue();
2571
              String targetType = "-//W3C//HTML//EN";
2572
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
2573
              Writer writer = new OutputStreamWriter(baos , "UTF-8");
2574
              // TODO: include more params?
2575
              Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2576
              String localId = null;
2577
              try {
2578
                  localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2579
              } catch (McdbDocNotFoundException e) {
2580
                  throw new NotFound("1020", e.getMessage());
2581
              }
2582
              params.put("qformat", new String[] {format});               
2583
              params.put("docid", new String[] {localId});
2584
              params.put("pid", new String[] {pid.getValue()});
2585
              transformer.transformXMLDocument(
2586
                      documentContent , 
2587
                      sourceType, 
2588
                      targetType , 
2589
                      format, 
2590
                      writer, 
2591
                      params, 
2592
                      null //sessionid
2593
                      );
2594
              
2595
              // finally, get the HTML back
2596
              resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2597
              ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2598
  
2599
          } else {
2600
              // just return the raw bytes
2601
              resultInputStream = object;
2602
          }
2603
      } catch (IOException e) {
2604
          // report as service failure
2605
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2606
          sf.initCause(e);
2607
          throw sf;
2608
      } catch (PropertyNotFoundException e) {
2609
          // report as service failure
2610
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2611
          sf.initCause(e);
2612
          throw sf;
2613
      } catch (SQLException e) {
2614
          // report as service failure
2615
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2616
          sf.initCause(e);
2617
          throw sf;
2618
      } catch (ClassNotFoundException e) {
2619
          // report as service failure
2620
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2621
          sf.initCause(e);
2622
          throw sf;
2623
      }
2624
      
2625
      return resultInputStream;
2626
  } 
2627
  
2628
  /*
2629
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2630
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2631
   * the guid of the first row.
2632
   */
2633
  protected String existsInObsoletes(Identifier id) throws InvalidRequest, ServiceFailure{
2634
      String guid = existsInFields("obsoletes", id);
2635
      return guid;
2636
  }
2637
  
2638
  /*
2639
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2640
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2641
   * the guid of the first row.
2642
   */
2643
  protected String existsInObsoletedBy(Identifier id) throws InvalidRequest, ServiceFailure{
2644
      String guid = existsInFields("obsoleted_by", id);
2645
      return guid;
2646
  }
2647

    
2648
  /*
2649
   * Determine if the given identifier exists in the given column in the system metadata table. 
2650
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2651
   * the guid of the first row.
2652
   */
2653
  private String existsInFields(String column, Identifier id) throws InvalidRequest, ServiceFailure {
2654
      String guid = null;
2655
      if(id == null ) {
2656
          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");
2657
      }
2658
      String sql = "SELECT guid FROM systemmetadata WHERE "+column+" = ?";
2659
      int serialNumber = -1;
2660
      DBConnection dbConn = null;
2661
      PreparedStatement stmt = null;
2662
      ResultSet result = null;
2663
      try {
2664
          dbConn = 
2665
                  DBConnectionPool.getDBConnection("D1NodeService.existsInFields");
2666
          serialNumber = dbConn.getCheckOutSerialNumber();
2667
          stmt = dbConn.prepareStatement(sql);
2668
          stmt.setString(1, id.getValue());
2669
          result = stmt.executeQuery();
2670
          if(result.next()) {
2671
              guid = result.getString(1);
2672
          }
2673
          stmt.close();
2674
      } catch (SQLException e) {
2675
          e.printStackTrace();
2676
          throw new ServiceFailure("4862","We can't determine if the id "+id.getValue()+" exists in field "+column+" in the systemmetadata table since "+e.getMessage());
2677
      } finally {
2678
          // Return database connection to the pool
2679
          DBConnectionPool.returnDBConnection(dbConn, serialNumber);
2680
          if(stmt != null) {
2681
              try {
2682
                  stmt.close();
2683
              } catch (SQLException e) {
2684
                  logMetacat.warn("We can close the PreparedStatment in D1NodeService.existsInFields since "+e.getMessage());
2685
              }
2686
          }
2687
          
2688
      }
2689
      return guid;
2690
      
2691
  }
2692
}
(2-2/8)