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-05-17 15:06:37 -0700 (Wed, 17 May 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
      } catch (SQLException e) {
250
          throw new ServiceFailure("1350", "The object with the provided " + "identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
251
      }
252
      
253
      try {
254
          // delete the document, as admin
255
          DocumentImpl.delete(localId, null, null, null, true);
256
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
257

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

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

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

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

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

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

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

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

    
378
    logMetacat.debug("Checking if identifier exists: " + pid.getValue());
379
    // Check that the identifier does not already exist
380
    boolean idExists = false;
381
    try {
382
        idExists = IdentifierManager.getInstance().identifierExists(pid.getValue());
383
    } catch (SQLException e) {
384
        throw new ServiceFailure("1190", 
385
                                "The requested identifier " + pid.getValue() +
386
                                " couldn't be determined if it is unique since : "+e.getMessage());
387
    }
388
    if (idExists) {
389
	    	throw new IdentifierNotUnique("1120", 
390
			          "The requested identifier " + pid.getValue() +
391
			          " is already used by another object and" +
392
			          "therefore can not be used for this object. Clients should choose" +
393
			          "a new identifier that is unique and retry the operation or " +
394
			          "use CN.reserveIdentifier() to reserve one.");
395
    	
396
    }
397
    
398
    
399
    // TODO: this probably needs to be refined more
400
    try {
401
      allowed = isAuthorized(session, pid, Permission.WRITE);
402
            
403
    } catch (NotFound e) {
404
      // The identifier doesn't exist, writing should be fine.
405
      allowed = true;
406
    }
407
    
408
    if(!allowed) {
409
        throw new NotAuthorized("1100", "Provited Identity doesn't have the WRITE permission on the pid "+pid.getValue());
410
    }
411
 
412
    	
413
    // we have the go ahead
414
    //if ( allowed ) {
415
     
416
      
417
        logMetacat.debug("Allowed to insert: " + pid.getValue());
418

    
419
        // save the sysmeta
420
        try {
421
            // lock and unlock of the pid happens in the subclass
422
            HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
423
        
424
        } catch (Exception e) {
425
            logMetacat.error("D1Node.create - There was problem to save the system metadata: " + pid.getValue(), e);
426
            throw new ServiceFailure("1190", "There was problem to save the system metadata: " + pid.getValue()+" since "+e.getMessage());
427
        }
428
        boolean isScienceMetadata = false;
429
      // Science metadata (XML) or science data object?
430
      // TODO: there are cases where certain object formats are science metadata
431
      // but are not XML (netCDF ...).  Handle this.
432
      if ( isScienceMetadata(sysmeta) ) {
433
        isScienceMetadata = true;
434
        // CASE METADATA:
435
      	//String objectAsXML = "";
436
        try {
437
	        //objectAsXML = IOUtils.toString(object, "UTF-8");
438
            String formatId = null;
439
            if(sysmeta.getFormatId() != null)  {
440
                formatId = sysmeta.getFormatId().getValue();
441
            }
442
	        localId = insertOrUpdateDocument(object,"UTF-8", pid, session, "insert", formatId, sysmeta.getChecksum());
443
	        //localId = im.getLocalId(pid.getValue());
444

    
445
        } catch (IOException e) {
446
            removeSystemMetaAndIdentifier(pid);
447
        	String msg = "The Node is unable to create the object "+pid.getValue() +
448
          " There was a problem converting the object to XML";
449
        	logMetacat.error(msg, e);
450
          throw new ServiceFailure("1190", msg + ": " + e.getMessage());
451

    
452
        } catch (ServiceFailure e) {
453
            removeSystemMetaAndIdentifier(pid);
454
            logMetacat.error("D1NodeService.create - the node couldn't create the object "+pid.getValue()+" since "+e.getMessage(), e);
455
            throw e;
456
        } catch (Exception e) {
457
            removeSystemMetaAndIdentifier(pid);
458
            logMetacat.error("The node is unable to create the object: "+pid.getValue()+ " since " + e.getMessage(), e);
459
            throw new ServiceFailure("1190", "The node is unable to create the object: " +pid.getValue()+" since "+ e.getMessage());
460
        }
461
                    
462
      } else {
463
	        
464
	      // DEFAULT CASE: DATA (needs to be checked and completed)
465
          try {
466
              localId = insertDataObject(object, pid, session, sysmeta.getChecksum());
467
          } catch (ServiceFailure e) {
468
              removeSystemMetaAndIdentifier(pid);
469
              throw e;
470
          } catch (InvalidSystemMetadata e) {
471
              removeSystemMetaAndIdentifier(pid);
472
              throw e;
473
          } catch (Exception e) {
474
              removeSystemMetaAndIdentifier(pid);
475
              throw new ServiceFailure("1190", "The node is unable to create the object "+pid.getValue()+" since " + e.getMessage());
476
          }
477
	      
478
      }   
479
    
480
    //}
481

    
482
    logMetacat.debug("Done inserting new object: " + pid.getValue());
483
    
484
    // setting the resulting identifier failed. We will check if the object does exist.
485
    try {
486
        if (localId == null || !IdentifierManager.getInstance().objectFileExists(localId, isScienceMetadata) ) {
487
            removeSystemMetaAndIdentifier(pid);
488
          throw new ServiceFailure("1190", "The Node is unable to create the object. "+pid.getValue());
489
        }
490
    } catch (PropertyNotFoundException e) {
491
        removeSystemMetaAndIdentifier(pid);
492
        throw new ServiceFailure("1190", "The Node is unable to create the object. "+pid.getValue() + " since "+e.getMessage());
493
    }
494
   
495
    
496
    
497
  
498
    
499
    try {
500
        // submit for indexing
501
        MetacatSolrIndex.getInstance().submit(sysmeta.getIdentifier(), sysmeta, null, true);
502
    } catch (Exception e) {
503
        logMetacat.warn("Couldn't create solr index for object "+pid.getValue());
504
    }
505

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

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

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

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

    
591
    if ( start == null ) {
592
    	start = 0;	
593
    }
594
    
595
    if ( count == null ) {
596
    	count = 1000;
597
    }
598
    
599
    // safeguard against large requests
600
    if (count > MAXIMUM_DB_RECORD_COUNT) {
601
    	count = MAXIMUM_DB_RECORD_COUNT;
602
    }
603

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

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

    
710
    // if we fail to set the input stream
711
    if ( inputStream == null ) {
712
        String error ="";
713
        if(EventLog.getInstance().isDeleted(localId)) {
714
              error=DELETEDMESSAGE;
715
        }
716
        throw new NotFound("1020", "The object specified by " + 
717
                         pid.getValue() +
718
                         " does not exist at this node. "+error);
719
    }
720
    
721
	// log the read event
722
    String principal = Constants.SUBJECT_PUBLIC;
723
    if (session != null && session.getSubject() != null) {
724
    	principal = session.getSubject().getValue();
725
    }
726
    EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "read");
727
    
728
    return inputStream;
729
  }
730

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

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

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

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

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

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

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

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

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

    
1376
  /** 
1377
   * Determine if a given object should be treated as an XML science metadata
1378
   * object. 
1379
   * 
1380
   * @param sysmeta - the SystemMetadata describing the object
1381
   * @return true if the object should be treated as science metadata
1382
   */
1383
  public static boolean isScienceMetadata(SystemMetadata sysmeta) {
1384
    
1385
    ObjectFormat objectFormat = null;
1386
    boolean isScienceMetadata = false;
1387
    
1388
    try {
1389
      objectFormat = ObjectFormatCache.getInstance().getFormat(sysmeta.getFormatId());
1390
      if ( objectFormat.getFormatType().equals("METADATA") ) {
1391
      	isScienceMetadata = true;
1392
      	
1393
      }
1394
      
1395
       
1396
    /*} catch (ServiceFailure e) {
1397
      logMetacat.debug("There was a problem determining if the object identified by" + 
1398
          sysmeta.getIdentifier().getValue() + 
1399
          " is science metadata: " + e.getMessage());*/
1400
    
1401
    } catch (NotFound e) {
1402
      logMetacat.debug("There was a problem determining if the object identified by" + 
1403
          sysmeta.getIdentifier().getValue() + 
1404
          " is science metadata: " + e.getMessage());
1405
    
1406
    }
1407
    
1408
    return isScienceMetadata;
1409

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

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

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

    
1584
    logMetacat.debug("Case DATA: starting to write to disk.");
1585
	if (locked) {
1586

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

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

    
1690
      ObjectList objectList = null;
1691

    
1692
      try {
1693
          // safeguard against large requests
1694
          if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
1695
              count = MAXIMUM_DB_RECORD_COUNT;
1696
          }
1697
          boolean isSid = false;
1698
          if(identifier != null) {
1699
              isSid = IdentifierManager.getInstance().systemMetadataSIDExists(identifier);
1700
          }
1701
          objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, nodeId, start, count, identifier, isSid);
1702
      } catch (Exception e) {
1703
          throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1704
      }
1705

    
1706
      return objectList;
1707
  }
1708

    
1709

    
1710
  /**
1711
   * Update a systemMetadata document
1712
   * 
1713
   * @param sysMeta - the system metadata object in the system to update
1714
   */
1715
    protected void updateSystemMetadata(SystemMetadata sysMeta)
1716
        throws ServiceFailure {
1717
        logMetacat.debug("D1NodeService.updateSystemMetadata() called.");
1718
        try {
1719
            HazelcastService.getInstance().getSystemMetadataMap().lock(sysMeta.getIdentifier());
1720
            boolean needUpdateModificationDate = true;
1721
            updateSystemMetadataWithoutLock(sysMeta, needUpdateModificationDate);
1722
        } catch (Exception e) {
1723
            throw new ServiceFailure("4862", e.getMessage());
1724
        } finally {
1725
            HazelcastService.getInstance().getSystemMetadataMap().unlock(sysMeta.getIdentifier());
1726

    
1727
        }
1728

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

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

    
2073
  /*
2074
   * Write a stream to a file
2075
   * 
2076
   * @param dir - the directory to write to
2077
   * @param fileName - the file name to write to
2078
   * @param data - the object bytes as an input stream
2079
   * 
2080
   * @return newFile - the new file created
2081
   * 
2082
   * @throws ServiceFailure
2083
   */
2084
  private File writeStreamToFile(File dir, String fileName, InputStream dataStream, Checksum checksum, Identifier pid) 
2085
    throws ServiceFailure, InvalidSystemMetadata {
2086
    
2087
    File newFile = new File(dir, fileName);
2088
    logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath()+" for the data object pid "+pid.getValue());
2089

    
2090
    try {
2091
        if (newFile.createNewFile()) {
2092
          if(checksum == null) {
2093
              logMetacat.error("D1NodeService.writeStreamToFile - the checksum object from the system metadata shouldn't be null for the data object "+pid.getValue());
2094
              throw new InvalidSystemMetadata("1180", "The checksum object from the system metadata shouldn't be null.");
2095
          } 
2096
          String checksumValue = checksum.getValue();
2097
          logMetacat.info("D1NodeService.writeStreamToFile - the checksum value from the system metadata is "+checksumValue+" for the data object "+pid.getValue());
2098
          if(checksumValue == null || checksumValue.trim().equals("")) {
2099
              logMetacat.error("D1NodeService.writeStreamToFile - the checksum value from the system metadata shouldn't be null or blank for the data object "+pid.getValue());
2100
              throw new InvalidSystemMetadata("1180", "The checksum value from the system metadata shouldn't be null or blank.");
2101
          }
2102
          String algorithm = checksum.getAlgorithm();
2103
          logMetacat.info("D1NodeService.writeStreamToFile - the algorithm to calculate the checksum from the system metadata is "+algorithm+" for the data object "+pid.getValue());
2104
          if(algorithm == null || algorithm.trim().equals("")) {
2105
              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());
2106
              throw new InvalidSystemMetadata("1180", "The algorithm to calculate the checksum from the system metadata shouldn't be null or blank.");
2107
          }
2108
          MessageDigest md = MessageDigest.getInstance(algorithm);
2109
          // write data stream to desired file
2110
          DigestOutputStream os = new DigestOutputStream( new FileOutputStream(newFile), md);
2111
          long length = IOUtils.copyLarge(dataStream, os);
2112
          os.flush();
2113
          os.close();
2114
          String localChecksum = DatatypeConverter.printHexBinary(md.digest());
2115
          logMetacat.info("D1NodeService.writeStreamToFile - the check sum calculated from the saved local file is "+localChecksum);
2116
          if(localChecksum == null || localChecksum.trim().equals("") || !localChecksum.equalsIgnoreCase(checksumValue)) {
2117
              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());
2118
              boolean success = newFile.delete();
2119
              logMetacat.info("delete the file "+newFile.getAbsolutePath()+" for the object "+pid.getValue()+" sucessfully?"+success);
2120
              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+".");
2121
          }
2122
          
2123
        } else {
2124
          logMetacat.debug("File creation failed, or file already exists.");
2125
          throw new ServiceFailure("1190", "File already exists: " + fileName);
2126
        }
2127
    } catch (FileNotFoundException e) {
2128
      logMetacat.error("FNF: " + e.getMessage()+" for the data object "+pid.getValue(), e);
2129
      throw new ServiceFailure("1190", "File not found: " + fileName + " " 
2130
                + e.getMessage());
2131
    } catch (IOException e) {
2132
      logMetacat.error("IOE: " + e.getMessage()+" for the data object "+pid.getValue(), e);
2133
      throw new ServiceFailure("1190", "File was not written: " + fileName 
2134
                + " " + e.getMessage());
2135
    } catch (NoSuchAlgorithmException e) {
2136
        logMetacat.error("D1NodeService.writeStreamToFile - no such checksum algorithm exception " + e.getMessage()+" for the data object "+pid.getValue(), e);
2137
        throw new ServiceFailure("1190", "No such checksum algorithm: "  
2138
                + " " + e.getMessage());
2139
    } finally {
2140
        IOUtils.closeQuietly(dataStream);
2141
    }
2142

    
2143
    return newFile;
2144
  }
2145

    
2146
  /*
2147
   * Returns a list of nodes that have been registered with the DataONE infrastructure
2148
   * that match the given session subject
2149
   * @return nodes - List of nodes from the registry with a matching session subject
2150
   * 
2151
   * @throws ServiceFailure
2152
   * @throws NotImplemented
2153
   */
2154
  protected List<Node> listNodesBySubject(Subject subject) 
2155
      throws ServiceFailure, NotImplemented {
2156
      List<Node> nodeList = new ArrayList<Node>();
2157
      
2158
      CNode cn = D1Client.getCN();
2159
      List<Node> nodes = cn.listNodes().getNodeList();
2160
      
2161
      // find the node in the node list
2162
      for ( Node node : nodes ) {
2163
          
2164
          List<Subject> nodeSubjects = node.getSubjectList();
2165
          if (nodeSubjects != null) {    
2166
	          // check if the session subject is in the node subject list
2167
	          for (Subject nodeSubject : nodeSubjects) {
2168
	              if ( nodeSubject.equals(subject) ) { // subject of session == node subject
2169
	                  nodeList.add(node);  
2170
	              }                              
2171
	          }
2172
          }
2173
      }
2174
      
2175
      return nodeList;
2176
      
2177
  }
2178

    
2179
  /**
2180
   * Archives an object, where the object is either a 
2181
   * data object or a science metadata object.
2182
   * Note: it doesn't check the authorization; it doesn't lock the system metadata;it only accept pid.
2183
   * @param session - the Session object containing the credentials for the Subject
2184
   * @param pid - The object identifier to be archived
2185
   * 
2186
   * @return pid - the identifier of the object used for the archiving
2187
   * 
2188
   * @throws InvalidToken
2189
   * @throws ServiceFailure
2190
   * @throws NotAuthorized
2191
   * @throws NotFound
2192
   * @throws NotImplemented
2193
   * @throws InvalidRequest
2194
   */
2195
  protected Identifier archiveObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2196
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2197

    
2198
      String localId = null;
2199
      boolean allowed = false;
2200
      String username = Constants.SUBJECT_PUBLIC;
2201
      if (session == null) {
2202
      	throw new InvalidToken("1330", "No session has been provided");
2203
      } else {
2204
          username = session.getSubject().getValue();
2205
      }
2206
      // do we have a valid pid?
2207
      if (pid == null || pid.getValue().trim().equals("")) {
2208
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
2209
      }
2210
      
2211
      if(sysMeta == null) {
2212
          throw new NotFound("2911", "There is no system metadata associated with "+pid.getValue());
2213
      }
2214
      
2215
      // check for the existing identifier
2216
      try {
2217
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2218
      } catch (McdbDocNotFoundException e) {
2219
          throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
2220
      } catch (SQLException e) {
2221
          throw new ServiceFailure("1350", "The object with the provided identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
2222
      }
2223

    
2224

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

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

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

    
2287

    
2288
      return pid;
2289
  }
2290
  
2291
  /**
2292
   * Archive a object on cn and notify the replica. This method doesn't lock the system metadata map. The caller should lock it.
2293
   * This method doesn't check the authorization; this method only accept a pid.
2294
   * It wouldn't notify the replca that the system metadata has been changed.
2295
   * @param session
2296
   * @param pid
2297
   * @param sysMeta
2298
   * @param notifyReplica
2299
   * @return
2300
   * @throws InvalidToken
2301
   * @throws ServiceFailure
2302
   * @throws NotAuthorized
2303
   * @throws NotFound
2304
   * @throws NotImplemented
2305
   */
2306
  protected void archiveCNObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2307
          throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2308

    
2309
          String localId = null; // The corresponding docid for this pid
2310
          
2311
          // Check for the existing identifier
2312
          try {
2313
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2314
              archiveObject(log, session, pid, sysMeta, needModifyDate);
2315
          
2316
          } catch (McdbDocNotFoundException e) {
2317
              // This object is not registered in the identifier table. Assume it is of formatType DATA,
2318
              // and set the archive flag. (i.e. the *object* doesn't exist on the CN)
2319
              
2320
              try {
2321
                  if ( sysMeta != null ) {
2322
                    sysMeta.setArchived(true);
2323
                    if (needModifyDate) {
2324
                        sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2325
                        sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2326
                    }
2327
                    HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2328
                      
2329
                  } else {
2330
                      throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2331
                          ". Couldn't obtain the system metadata record.");
2332
                      
2333
                  }
2334
                  
2335
              } catch (RuntimeException re) {
2336
                  throw new ServiceFailure("4972", "Couldn't archive " + pid.getValue() + 
2337
                      ". The error message was: " + re.getMessage());
2338
                  
2339
              } 
2340

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

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

    
2511
  //@Override
2512
  public InputStream view(Session session, String format, Identifier pid)
2513
          throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
2514
          NotImplemented, NotFound {
2515
      InputStream resultInputStream = null;
2516
      
2517
      String serviceFailureCode = "2831";
2518
      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2519
      if(sid != null) {
2520
          pid = sid;
2521
      }
2522
      
2523
      SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2524
      InputStream object = this.get(session, pid);
2525

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

    
2611
  /*
2612
   * Determine if the given identifier exists in the given column in the system metadata table. 
2613
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2614
   * the guid of the first row.
2615
   */
2616
  private String existsInFields(String column, Identifier id) throws InvalidRequest, ServiceFailure {
2617
      String guid = null;
2618
      if(id == null ) {
2619
          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");
2620
      }
2621
      String sql = "SELECT guid FROM systemmetadata WHERE "+column+" = ?";
2622
      int serialNumber = -1;
2623
      DBConnection dbConn = null;
2624
      PreparedStatement stmt = null;
2625
      ResultSet result = null;
2626
      try {
2627
          dbConn = 
2628
                  DBConnectionPool.getDBConnection("D1NodeService.existsInFields");
2629
          serialNumber = dbConn.getCheckOutSerialNumber();
2630
          stmt = dbConn.prepareStatement(sql);
2631
          stmt.setString(1, id.getValue());
2632
          result = stmt.executeQuery();
2633
          if(result.next()) {
2634
              guid = result.getString(1);
2635
          }
2636
          stmt.close();
2637
      } catch (SQLException e) {
2638
          e.printStackTrace();
2639
          throw new ServiceFailure("4862","We can't determine if the id "+id.getValue()+" exists in field "+column+" in the systemmetadata table since "+e.getMessage());
2640
      } finally {
2641
          // Return database connection to the pool
2642
          DBConnectionPool.returnDBConnection(dbConn, serialNumber);
2643
          if(stmt != null) {
2644
              try {
2645
                  stmt.close();
2646
              } catch (SQLException e) {
2647
                  logMetacat.warn("We can close the PreparedStatment in D1NodeService.existsInFields since "+e.getMessage());
2648
              }
2649
          }
2650
          
2651
      }
2652
      return guid;
2653
      
2654
  }
2655
}
(2-2/8)