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: 2015-10-20 16:11:04 -0700 (Tue, 20 Oct 2015) $'
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.ByteArrayInputStream;
27
import java.io.ByteArrayOutputStream;
28
import java.io.File;
29
import java.io.FileNotFoundException;
30
import java.io.FileOutputStream;
31
import java.io.IOException;
32
import java.io.InputStream;
33
import java.io.OutputStream;
34
import java.io.OutputStreamWriter;
35
import java.io.StringReader;
36
import java.io.UnsupportedEncodingException;
37
import java.io.Writer;
38
import java.math.BigInteger;
39
import java.nio.charset.Charset;
40
import java.sql.PreparedStatement;
41
import java.sql.ResultSet;
42
import java.sql.SQLException;
43
import java.util.ArrayList;
44
import java.util.Calendar;
45
import java.util.Date;
46
import java.util.Hashtable;
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.xml.transform.Result;
54
import javax.xml.transform.Source;
55
import javax.xml.transform.TransformerException;
56
import javax.xml.transform.TransformerFactory;
57
import javax.xml.transform.dom.DOMSource;
58
import javax.xml.transform.stream.StreamResult;
59
import javax.servlet.http.HttpServletRequest;
60

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

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

    
121
public abstract class D1NodeService {
122
    
123
  public static final String DELETEDMESSAGE = "The object with the PID has been deleted from the node.";
124
  
125
  private static String XPATH_EML_ID = "/eml:eml/@packageId";
126
  
127
  private static Logger logMetacat = Logger.getLogger(D1NodeService.class);
128

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

    
156
  /**
157
   * Constructor - used to set the metacatUrl from a subclass extending D1NodeService
158
   * 
159
   * @param metacatUrl - the URL of the metacat service, including the ending /d1
160
   */
161
  public D1NodeService(HttpServletRequest request) {
162
		this.request = request;
163
	}
164

    
165
  /**
166
   * retrieve the out-of-band session
167
   * @return
168
   */
169
  	public Session getSession() {
170
		return session;
171
	}
172
  	
173
  	/**
174
  	 * Set the out-of-band session
175
  	 * @param session
176
  	 */
177
	public void setSession(Session session) {
178
		this.session = session;
179
	}
180

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

    
208
    // get system metadata and construct the describe response
209
      SystemMetadata sysmeta = getSystemMetadata(session, pid);
210
      DescribeResponse describeResponse = 
211
      	new DescribeResponse(sysmeta.getFormatId(), sysmeta.getSize(), 
212
      			sysmeta.getDateSysMetadataModified(),
213
      			sysmeta.getChecksum(), sysmeta.getSerialVersion());
214

    
215
      return describeResponse;
216

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

    
245
      // do we have a valid pid?
246
      if (pid == null || pid.getValue().trim().equals("")) {
247
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
248
      }
249

    
250
      // check for the existing identifier
251
      try {
252
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
253
      } catch (McdbDocNotFoundException e) {
254
          throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
255
      } catch (SQLException e) {
256
          throw new ServiceFailure("1350", "The object with the provided " + "identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
257
      }
258
      
259
      try {
260
          // delete the document, as admin
261
          DocumentImpl.delete(localId, null, null, null, true);
262
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
263

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

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

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

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

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

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

    
351
    Identifier resultPid = null;
352
    String localId = null;
353
    boolean allowed = false;
354
    
355
    // check for null session
356
    if (session == null) {
357
    	throw new InvalidToken("4894", "Session is required to WRITE to the Node.");
358
    }
359
    Subject subject = session.getSubject();
360

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

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

    
455
      // Science metadata (XML) or science data object?
456
      // TODO: there are cases where certain object formats are science metadata
457
      // but are not XML (netCDF ...).  Handle this.
458
      if ( isScienceMetadata(sysmeta) ) {
459
        
460
        // CASE METADATA:
461
      	//String objectAsXML = "";
462
        try {
463
	        //objectAsXML = IOUtils.toString(object, "UTF-8");
464
	        localId = insertOrUpdateDocument(object,"UTF-8", pid, session, "insert");
465
	        //localId = im.getLocalId(pid.getValue());
466

    
467
        } catch (IOException e) {
468
            removeSystemMeta(pid);
469
        	String msg = "The Node is unable to create the object. " +
470
          "There was a problem converting the object to XML";
471
        	logMetacat.info(msg);
472
          throw new ServiceFailure("1190", msg + ": " + e.getMessage());
473

    
474
        } catch (ServiceFailure e) {
475
            removeSystemMeta(pid);
476
            throw e;
477
        } catch (Exception e) {
478
            removeSystemMeta(pid);
479
            throw new ServiceFailure("1190", "The node is unable to create the object: " + e.getMessage());
480
        }
481
                    
482
      } else {
483
	        
484
	      // DEFAULT CASE: DATA (needs to be checked and completed)
485
          try {
486
              localId = insertDataObject(object, pid, session);
487
          } catch (ServiceFailure e) {
488
              removeSystemMeta(pid);
489
              throw e;
490
          } catch (Exception e) {
491
              removeSystemMeta(pid);
492
              throw new ServiceFailure("1190", "The node is unable to create the object: " + e.getMessage());
493
          }
494
	      
495
      }   
496
    
497
    //}
498

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

    
514
    resultPid = pid;
515
    
516
    logMetacat.debug("create() complete for object: " + pid.getValue());
517

    
518
    return resultPid;
519
  }
520
  
521
  /*
522
   * Roll-back method when inserting data object fails.
523
   */
524
  protected void removeSystemMeta(Identifier id){
525
      HazelcastService.getInstance().getSystemMetadataMap().remove(id);
526
  }
527
  
528
  /*
529
   * Roll-back method when inserting data object fails.
530
   */
531
  protected void removeSolrIndex(SystemMetadata sysMeta) {
532
      sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
533
      sysMeta.setArchived(true);
534
      sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
535
      try {
536
          MetacatSolrIndex.getInstance().submit(sysMeta.getIdentifier(), sysMeta, null, false);
537
      } catch (Exception e) {
538
          logMetacat.warn("Can't remove the solr index for pid "+sysMeta.getIdentifier().getValue());
539
      }
540
      
541
  }
542

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1173
      } catch (Exception e) {
1174
          // convert Hazelcast RuntimeException to NotFound
1175
          logMetacat.error("An error occurred while getting system metadata for identifier " +
1176
              pid.getValue() + ". The error message was: " + e.getMessage());
1177
          throw new NotFound("1800", "No record found for " + pidStr);
1178
          
1179
      } 
1180
      
1181
      // throw not found if it was not found
1182
      if (systemMetadata == null) {
1183
          String localId = null;
1184
          String error = "No system metadata could be found for given PID: " + pidStr;
1185
          try {
1186
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1187
            
1188
           } catch (Exception e) {
1189
              logMetacat.warn("Couldn't find the local id for the pid "+pidStr);
1190
          }
1191
          
1192
          if(localId != null && EventLog.getInstance().isDeleted(localId)) {
1193
              error = error + ". "+DELETEDMESSAGE;
1194
          } else if (localId == null && EventLog.getInstance().isDeleted(pid.getValue())) {
1195
              error = error + ". "+DELETEDMESSAGE;
1196
          }
1197
          throw new NotFound("1800", error);
1198
      }
1199
          
1200
      // do we own it?
1201
      for (Subject s: subjects) {
1202
        logMetacat.debug("Comparing \t" + 
1203
                         systemMetadata.getRightsHolder().getValue() +
1204
                         " \tagainst \t" + s.getValue());
1205
          //includedSubjects.append(s.getValue() + "; ");
1206
          allowed = systemMetadata.getRightsHolder().equals(s);
1207
          if (allowed) {
1208
              return allowed;
1209
          }
1210
      }    
1211
      
1212
      // otherwise check the access rules
1213
      try {
1214
          List<AccessRule> allows = systemMetadata.getAccessPolicy().getAllowList();
1215
          search: // label break
1216
          for (AccessRule accessRule: allows) {
1217
            for (Subject s: subjects) {
1218
              logMetacat.debug("Checking allow access rule for subject: " + s.getValue());
1219
              if (accessRule.getSubjectList().contains(s)) {
1220
                  logMetacat.debug("Access rule contains subject: " + s.getValue());
1221
                  for (Permission p: accessRule.getPermissionList()) {
1222
                      logMetacat.debug("Checking permission: " + p.xmlValue());
1223
                      expandedPermissions = expandPermissions(p);
1224
                      allowed = expandedPermissions.contains(permission);
1225
                      if (allowed) {
1226
                          logMetacat.info("Permission granted: " + p.xmlValue() + " to " + s.getValue());
1227
                          break search; //label break
1228
                      }
1229
                  }
1230
                  
1231
              }
1232
            }
1233
          }
1234
      } catch (Exception e) {
1235
          // catch all for errors - safe side should be to deny the access
1236
          logMetacat.error("Problem checking authorization - defaulting to deny", e);
1237
          allowed = false;
1238
        
1239
      }
1240
      return allowed;
1241
  }
1242
  /*
1243
   * parse a logEntry and get the relevant field from it
1244
   * 
1245
   * @param fieldname
1246
   * @param entry
1247
   * @return
1248
   */
1249
  private String getLogEntryField(String fieldname, String entry) {
1250
    String begin = "<" + fieldname + ">";
1251
    String end = "</" + fieldname + ">";
1252
    // logMetacat.debug("looking for " + begin + " and " + end +
1253
    // " in entry " + entry);
1254
    String s = entry.substring(entry.indexOf(begin) + begin.length(), entry
1255
        .indexOf(end));
1256
    logMetacat.debug("entry " + fieldname + " : " + s);
1257
    return s;
1258
  }
1259

    
1260
  /** 
1261
   * Determine if a given object should be treated as an XML science metadata
1262
   * object. 
1263
   * 
1264
   * @param sysmeta - the SystemMetadata describing the object
1265
   * @return true if the object should be treated as science metadata
1266
   */
1267
  public static boolean isScienceMetadata(SystemMetadata sysmeta) {
1268
    
1269
    ObjectFormat objectFormat = null;
1270
    boolean isScienceMetadata = false;
1271
    
1272
    try {
1273
      objectFormat = ObjectFormatCache.getInstance().getFormat(sysmeta.getFormatId());
1274
      if ( objectFormat.getFormatType().equals("METADATA") ) {
1275
      	isScienceMetadata = true;
1276
      	
1277
      }
1278
      
1279
       
1280
    } catch (ServiceFailure e) {
1281
      logMetacat.debug("There was a problem determining if the object identified by" + 
1282
          sysmeta.getIdentifier().getValue() + 
1283
          " is science metadata: " + e.getMessage());
1284
    
1285
    } catch (NotFound e) {
1286
      logMetacat.debug("There was a problem determining if the object identified by" + 
1287
          sysmeta.getIdentifier().getValue() + 
1288
          " is science metadata: " + e.getMessage());
1289
    
1290
    }
1291
    
1292
    return isScienceMetadata;
1293

    
1294
  }
1295
  
1296
  /**
1297
   * Check fro whitespace in the given pid.
1298
   * null pids are also invalid by default
1299
   * @param pid
1300
   * @return
1301
   */
1302
  public static boolean isValidIdentifier(Identifier pid) {
1303
	  if (pid != null && pid.getValue() != null && pid.getValue().length() > 0) {
1304
		  return !pid.getValue().matches(".*\\s+.*");
1305
	  } 
1306
	  return false;
1307
  }
1308
  
1309
  
1310
  /**
1311
   * Insert or update an XML document into Metacat
1312
   * 
1313
   * @param xml - the XML document to insert or update
1314
   * @param pid - the identifier to be used for the resulting object
1315
   * 
1316
   * @return localId - the resulting docid of the document created or updated
1317
   * 
1318
   */
1319
  public String insertOrUpdateDocument(InputStream xml, String encoding,  Identifier pid, 
1320
    Session session, String insertOrUpdate) 
1321
    throws ServiceFailure, IOException {
1322
    
1323
  	logMetacat.debug("Starting to insert xml document...");
1324
    IdentifierManager im = IdentifierManager.getInstance();
1325

    
1326
    // generate pid/localId pair for sysmeta
1327
    String localId = null;
1328
    byte[] xmlBytes  = IOUtils.toByteArray(xml);
1329
    String xmlStr = new String(xmlBytes, encoding);
1330
    if(insertOrUpdate.equals("insert")) {
1331
      localId = im.generateLocalId(pid.getValue(), 1);
1332
      
1333
    } else {
1334
      //localid should already exist in the identifier table, so just find it
1335
      try {
1336
        logMetacat.debug("Updating pid " + pid.getValue());
1337
        logMetacat.debug("looking in identifier table for pid " + pid.getValue());
1338
        
1339
        localId = im.getLocalId(pid.getValue());
1340
        
1341
        logMetacat.debug("localId: " + localId);
1342
        //increment the revision
1343
        String docid = localId.substring(0, localId.lastIndexOf("."));
1344
        String revS = localId.substring(localId.lastIndexOf(".") + 1, localId.length());
1345
        int rev = new Integer(revS).intValue();
1346
        rev++;
1347
        docid = docid + "." + rev;
1348
        localId = docid;
1349
        logMetacat.debug("incremented localId: " + localId);
1350
      
1351
      } catch(McdbDocNotFoundException e) {
1352
        throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument(): " +
1353
            "pid " + pid.getValue() + 
1354
            " should have been in the identifier table, but it wasn't: " + 
1355
            e.getMessage());
1356
      
1357
      } catch (SQLException e) {
1358
          throw new ServiceFailure("1030", "D1NodeService.insertOrUpdateDocument() -"+
1359
                     " couldn't identify if the pid "+pid.getValue()+" is in the identifier table since "+e.getMessage());
1360
      }
1361
      
1362
    }
1363

    
1364
    params = new Hashtable<String, String[]>();
1365
    String[] action = new String[1];
1366
    action[0] = insertOrUpdate;
1367
    params.put("action", action);
1368
    String[] docid = new String[1];
1369
    docid[0] = localId;
1370
    params.put("docid", docid);
1371
    String[] doctext = new String[1];
1372
    doctext[0] = xmlStr;
1373
    params.put("doctext", doctext);
1374
    
1375
    String username = Constants.SUBJECT_PUBLIC;
1376
    String[] groupnames = null;
1377
    if (session != null ) {
1378
    	username = session.getSubject().getValue();
1379
    	if (session.getSubjectInfo() != null) {
1380
    		List<Group> groupList = session.getSubjectInfo().getGroupList();
1381
    		if (groupList != null) {
1382
    			groupnames = new String[groupList.size()];
1383
    			for (int i = 0; i < groupList.size(); i++ ) {
1384
    				groupnames[i] = groupList.get(i).getGroupName();
1385
    			}
1386
    		}
1387
    	}
1388
    }
1389
    
1390
    // do the insert or update action
1391
    handler = new MetacatHandler(new Timer());
1392
    String result = handler.handleInsertOrUpdateAction(request.getRemoteAddr(), request.getHeader("User-Agent"), null, 
1393
                        null, params, username, groupnames, false, false, xmlBytes);
1394
    
1395
    if(result.indexOf("<error>") != -1) {
1396
    	String detailCode = "";
1397
    	if ( insertOrUpdate.equals("insert") ) {
1398
    		// make sure to remove the mapping so that subsequent attempts do not fail with IdentifierNotUnique
1399
    		im.removeMapping(pid.getValue(), localId);
1400
    		detailCode = "1190";
1401
    		
1402
    	} else if ( insertOrUpdate.equals("update") ) {
1403
    		detailCode = "1310";
1404
    		
1405
    	}
1406
        throw new ServiceFailure(detailCode, 
1407
          "Error inserting or updating document: " + result);
1408
    }
1409
    logMetacat.debug("Finsished inserting xml document with id " + localId);
1410
    
1411
    return localId;
1412
  }
1413
  
1414
  /**
1415
   * Insert a data document
1416
   * 
1417
   * @param object
1418
   * @param pid
1419
   * @param sessionData
1420
   * @throws ServiceFailure
1421
   * @returns localId of the data object inserted
1422
   */
1423
  public String insertDataObject(InputStream object, Identifier pid, 
1424
          Session session) throws ServiceFailure {
1425
      
1426
    String username = Constants.SUBJECT_PUBLIC;
1427
    String[] groupnames = null;
1428
    if (session != null ) {
1429
    	username = session.getSubject().getValue();
1430
    	if (session.getSubjectInfo() != null) {
1431
    		List<Group> groupList = session.getSubjectInfo().getGroupList();
1432
    		if (groupList != null) {
1433
    			groupnames = new String[groupList.size()];
1434
    			for (int i = 0; i < groupList.size(); i++ ) {
1435
    				groupnames[i] = groupList.get(i).getGroupName();
1436
    			}
1437
    		}
1438
    	}
1439
    }
1440
  
1441
    // generate pid/localId pair for object
1442
    logMetacat.debug("Generating a pid/localId mapping");
1443
    IdentifierManager im = IdentifierManager.getInstance();
1444
    String localId = im.generateLocalId(pid.getValue(), 1);
1445
  
1446
    // Save the data file to disk using "localId" as the name
1447
    String datafilepath = null;
1448
	try {
1449
		datafilepath = PropertyService.getProperty("application.datafilepath");
1450
	} catch (PropertyNotFoundException e) {
1451
		ServiceFailure sf = new ServiceFailure("1190", "Lookup data file path" + e.getMessage());
1452
		sf.initCause(e);
1453
		throw sf;
1454
	}
1455
    boolean locked = false;
1456
	try {
1457
		locked = DocumentImpl.getDataFileLockGrant(localId);
1458
	} catch (Exception e) {
1459
		ServiceFailure sf = new ServiceFailure("1190", "Could not lock file for writing:" + e.getMessage());
1460
		sf.initCause(e);
1461
		throw sf;
1462
	}
1463

    
1464
    logMetacat.debug("Case DATA: starting to write to disk.");
1465
	if (locked) {
1466

    
1467
          File dataDirectory = new File(datafilepath);
1468
          dataDirectory.mkdirs();
1469
  
1470
          File newFile = writeStreamToFile(dataDirectory, localId, object);
1471
  
1472
          // TODO: Check that the file size matches SystemMetadata
1473
          // long size = newFile.length();
1474
          // if (size == 0) {
1475
          //     throw new IOException("Uploaded file is 0 bytes!");
1476
          // }
1477
  
1478
          // Register the file in the database (which generates an exception
1479
          // if the localId is not acceptable or other untoward things happen
1480
          try {
1481
            logMetacat.debug("Registering document...");
1482
            DocumentImpl.registerDocument(localId, "BIN", localId,
1483
                    username, groupnames);
1484
            logMetacat.debug("Registration step completed.");
1485
            
1486
          } catch (SQLException e) {
1487
            //newFile.delete();
1488
            logMetacat.debug("SQLE: " + e.getMessage());
1489
            e.printStackTrace(System.out);
1490
            throw new ServiceFailure("1190", "Registration failed: " + 
1491
            		e.getMessage());
1492
            
1493
          } catch (AccessionNumberException e) {
1494
            //newFile.delete();
1495
            logMetacat.debug("ANE: " + e.getMessage());
1496
            e.printStackTrace(System.out);
1497
            throw new ServiceFailure("1190", "Registration failed: " + 
1498
            	e.getMessage());
1499
            
1500
          } catch (Exception e) {
1501
            //newFile.delete();
1502
            logMetacat.debug("Exception: " + e.getMessage());
1503
            e.printStackTrace(System.out);
1504
            throw new ServiceFailure("1190", "Registration failed: " + 
1505
            	e.getMessage());
1506
          }
1507
  
1508
          logMetacat.debug("Logging the creation event.");
1509
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, "create");
1510
  
1511
          // Schedule replication for this data file, the "insert" action is important here!
1512
          logMetacat.debug("Scheduling replication.");
1513
          ForceReplicationHandler frh = new ForceReplicationHandler(localId, "insert", false, null);
1514
      }
1515
      
1516
      return localId;
1517
    
1518
  }
1519

    
1520
  /**
1521
   * Insert a systemMetadata document and return its localId
1522
   */
1523
  public void insertSystemMetadata(SystemMetadata sysmeta) 
1524
      throws ServiceFailure {
1525
      
1526
  	  logMetacat.debug("Starting to insert SystemMetadata...");
1527
      sysmeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
1528
      logMetacat.debug("Inserting new system metadata with modified date " + 
1529
          sysmeta.getDateSysMetadataModified());
1530
      
1531
      //insert the system metadata
1532
      try {
1533
        // note: the calling subclass handles the map hazelcast lock/unlock
1534
      	HazelcastService.getInstance().getSystemMetadataMap().put(sysmeta.getIdentifier(), sysmeta);
1535
      	// submit for indexing
1536
        MetacatSolrIndex.getInstance().submit(sysmeta.getIdentifier(), sysmeta, null, true);
1537
      } catch (Exception e) {
1538
          throw new ServiceFailure("1190", e.getMessage());
1539
          
1540
	    }  
1541
  }
1542
  
1543
  /**
1544
   * Retrieve the list of objects present on the MN that match the calling parameters
1545
   * 
1546
   * @param session - the Session object containing the credentials for the Subject
1547
   * @param startTime - Specifies the beginning of the time range from which 
1548
   *                    to return object (>=)
1549
   * @param endTime - Specifies the beginning of the time range from which 
1550
   *                  to return object (>=)
1551
   * @param objectFormat - Restrict results to the specified object format
1552
   * @param replicaStatus - Indicates if replicated objects should be returned in the list
1553
   * @param start - The zero-based index of the first value, relative to the 
1554
   *                first record of the resultset that matches the parameters.
1555
   * @param count - The maximum number of entries that should be returned in 
1556
   *                the response. The Member Node may return less entries 
1557
   *                than specified in this value.
1558
   * 
1559
   * @return objectList - the list of objects matching the criteria
1560
   * 
1561
   * @throws InvalidToken
1562
   * @throws ServiceFailure
1563
   * @throws NotAuthorized
1564
   * @throws InvalidRequest
1565
   * @throws NotImplemented
1566
   */
1567
  public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Identifier identifier, NodeReference nodeId, Integer start,
1568
          Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
1569

    
1570
      ObjectList objectList = null;
1571

    
1572
      try {
1573
          // safeguard against large requests
1574
          if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
1575
              count = MAXIMUM_DB_RECORD_COUNT;
1576
          }
1577
          boolean isSid = false;
1578
          if(identifier != null) {
1579
              isSid = IdentifierManager.getInstance().systemMetadataSIDExists(identifier);
1580
          }
1581
          objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, nodeId, start, count, identifier, isSid);
1582
      } catch (Exception e) {
1583
          throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1584
      }
1585

    
1586
      return objectList;
1587
  }
1588
  
1589
  /**
1590
   * Update a science metadata document with its new Identifier  
1591
   * 
1592
   * @param session - the Session object containing the credentials for the Subject
1593
   * @param object - the InputStream for the XML object to be edited
1594
   * @param pid - the Identifier of the XML object to be updated
1595
   * @param newPid = the new Identifier to give to the modified XML doc
1596
   * 
1597
   * @return newObject - The InputStream for the modified XML object
1598
   * 
1599
   * @throws ServiceFailure
1600
   * @throws IOException
1601
   * @throws UnsupportedEncodingException
1602
   * @throws InvalidToken
1603
   * @throws NotAuthorized
1604
   * @throws NotFound
1605
   * @throws NotImplemented
1606
   */
1607
  public InputStream editScienceMetadata(Session session, InputStream object, Identifier pid, Identifier newPid)
1608
  	throws ServiceFailure, IOException, UnsupportedEncodingException, InvalidToken, NotAuthorized, NotFound, NotImplemented {
1609
    
1610
	logMetacat.debug("D1NodeService.editScienceMetadata() called.");
1611
	
1612
	 InputStream newObject = null;
1613
	
1614
    try{   	
1615
    	//Get the root node of the XML document
1616
    	byte[] xmlBytes  = IOUtils.toByteArray(object);
1617
        String xmlStr = new String(xmlBytes, "UTF-8");
1618
        
1619
    	Document doc = XMLUtilities.getXMLReaderAsDOMDocument(new StringReader(xmlStr));
1620
	    org.w3c.dom.Node docNode = doc.getDocumentElement();
1621

    
1622
	    //Get the system metadata for this object
1623
	    SystemMetadata sysMeta = null;
1624
	    try{
1625
	    	sysMeta = getSystemMetadata(session, pid);
1626
	    } catch(NotAuthorized e){
1627
	    	throw new ServiceFailure("1030", "D1NodeService.editScienceMetadata(): " + 
1628
	    			"This session is not authorized to access the system metadata for " +
1629
	    			pid.getValue() + " : " + e.getMessage());
1630
	    } catch(NotFound e){
1631
	    	throw new ServiceFailure("1030", "D1NodeService.editScienceMetadata(): " + 
1632
	    			"Could not find the system metadata for " +
1633
	    			pid.getValue() + " : " + e.getMessage());
1634
	    }
1635
	    
1636
	    //Get the formatId
1637
        ObjectFormatIdentifier objFormatId = sysMeta.getFormatId();
1638
        String formatId = objFormatId.getValue();
1639
        
1640
    	//For all EML formats
1641
        if(formatId.indexOf("eml") == 0){
1642
        	//Update or add the id attribute
1643
    	    XMLUtilities.addAttributeNodeToDOMTree(docNode, XPATH_EML_ID, newPid.getValue());
1644
        }
1645

    
1646
        //The modified object InputStream
1647
	    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
1648
	    Source xmlSource = new DOMSource(docNode);
1649
	    Result outputTarget = new StreamResult(outputStream);
1650
	    TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
1651
	    newObject = new ByteArrayInputStream(outputStream.toByteArray());
1652
	    
1653
    } catch(TransformerException e) {
1654
    	throw new ServiceFailure("1030", "D1NodeService.editScienceMetadata(): " +
1655
                "Could not update the ID in the XML document for " +
1656
                "pid " + pid.getValue() +" : " + e.getMessage());
1657
    }
1658
    
1659
    return newObject;
1660
  }
1661

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

    
1679
        }
1680

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

    
1827
      try {
1828
    	  String localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1829
    	  EventLog.getInstance().log(request.getRemoteAddr(), 
1830
    	          request.getHeader("User-Agent"), session.getSubject().getValue(), 
1831
    	          localId, "updateSystemMetadata");
1832
      } catch (McdbDocNotFoundException e) {
1833
    	  // do nothing, no localId to log with
1834
    	  logMetacat.warn("Could not log 'updateSystemMetadata' event because no localId was found for pid: " + pid.getValue());
1835
      } catch (SQLException e) {
1836
          logMetacat.warn("Could not log 'updateSystemMetadata' event because the localId couldn't be identified for the pid: " + pid.getValue());
1837
      }
1838
      return true;
1839
	}
1840
	
1841
	
1842
	/*
1843
	 * Check if the newMeta modifies an immutable field. 
1844
	 */
1845
	private void checkModifiedImmutableFields(SystemMetadata orgMeta, SystemMetadata newMeta) throws InvalidRequest{
1846
	    logMetacat.debug("in the start of the checkModifiedImmutableFields method");
1847
	    if(orgMeta != null && newMeta != null) {
1848
	        logMetacat.debug("in the checkModifiedImmutableFields method when the org and new system metadata is not null");
1849
	        if(newMeta.getIdentifier() == null) {
1850
	            throw new InvalidRequest("4869", "The new version of the system metadata is invalid since the identifier is null");
1851
	        }
1852
	        if(!orgMeta.getIdentifier().equals(newMeta.getIdentifier())) {
1853
	            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 "+
1854
	                  "different to the orginal one "+orgMeta.getIdentifier().getValue());
1855
	        }
1856
	        if(newMeta.getSize() == null) {
1857
	            throw new InvalidRequest("4869", "The new version of the system metadata is invalid since the size is null");
1858
	        }
1859
	        if(!orgMeta.getSize().equals(newMeta.getSize())) {
1860
	            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 "+
1861
	                      "different to the orginal one "+orgMeta.getSize().longValue());
1862
	        }
1863
	        if(newMeta.getChecksum()!= null && orgMeta.getChecksum() != null && !orgMeta.getChecksum().getValue().equals(newMeta.getChecksum().getValue())) {
1864
	            logMetacat.error("The request is trying to modify an immutable field in the SystemMeta: the new system meta's checksum "+newMeta.getChecksum().getValue()+" is "+
1865
                        "different to the orginal one "+orgMeta.getChecksum().getValue());
1866
	            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 "+
1867
                        "different to the orginal one "+orgMeta.getChecksum().getValue());
1868
	        }
1869
	        if(orgMeta.getSubmitter() != null) {
1870
	            logMetacat.debug("in the checkModifiedImmutableFields method and orgMeta.getSubmitter is not null and the orginal submiter is "+orgMeta.getSubmitter().getValue());
1871
	        }
1872
	        
1873
	        if(newMeta.getSubmitter() != null) {
1874
                logMetacat.debug("in the checkModifiedImmutableFields method and newMeta.getSubmitter is not null and the submiter in the new system metadata is "+newMeta.getSubmitter().getValue());
1875
            }
1876
	        if(orgMeta.getSubmitter() != null && newMeta.getSubmitter() != null && !orgMeta.getSubmitter().equals(newMeta.getSubmitter())) {
1877
	            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 "+
1878
                        "different to the orginal one "+orgMeta.getSubmitter().getValue());
1879
	        }
1880
	        
1881
	        if(orgMeta.getDateUploaded() != null && newMeta.getDateUploaded() != null && orgMeta.getDateUploaded().getTime() != newMeta.getDateUploaded().getTime()) {
1882
	            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 "+
1883
                        "different to the orginal one "+orgMeta.getDateUploaded());
1884
	        }
1885
	        
1886
	        if(orgMeta.getOriginMemberNode() != null && newMeta.getOriginMemberNode() != null && !orgMeta.getOriginMemberNode().equals(newMeta.getOriginMemberNode())) {
1887
	            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 "+
1888
                        "different to the orginal one "+orgMeta.getOriginMemberNode().getValue());
1889
	        }
1890
	        
1891
	        if (orgMeta.getOriginMemberNode() != null && newMeta.getOriginMemberNode() == null ) {
1892
	            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 "+
1893
                        "different to the orginal one "+orgMeta.getOriginMemberNode().getValue());
1894
	        }
1895
	        
1896
	        if(orgMeta.getSeriesId() != null && newMeta.getSeriesId() != null && !orgMeta.getSeriesId().equals(newMeta.getSeriesId())) {
1897
                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 "+
1898
                        "different to the orginal one "+orgMeta.getSeriesId().getValue());
1899
            }
1900
	        
1901
	    }
1902
	}
1903
	
1904
	/*
1905
	 * Some fields in the system metadata, such as obsoletes or obsoletedBy can be set only once. 
1906
	 * After set, they are not allowed to be changed.
1907
	 */
1908
	private void checkOneTimeSettableSysmMetaFields(SystemMetadata orgMeta, SystemMetadata newMeta) throws InvalidRequest {
1909
	    if(orgMeta.getObsoletedBy() != null ) {
1910
	        if(newMeta.getObsoletedBy() == null || !orgMeta.getObsoletedBy().equals(newMeta.getObsoletedBy())) {
1911
	            throw new InvalidRequest("4869", "The request is trying to reset the obsoletedBy field in the system metadata of the object "
1912
	                    + orgMeta.getIdentifier().getValue() +". This is illegal since the obsoletedBy filed is set, you can't change it again.");
1913
	        }
1914
        }
1915
	    if(orgMeta.getObsoletes() != null) {
1916
	        if(newMeta.getObsoletes() == null || !orgMeta.getObsoletes().equals(newMeta.getObsoletes())) {
1917
	            throw new InvalidRequest("4869", "The request is trying to reset the obsoletes field in the system metadata of the object"+
1918
	               orgMeta.getIdentifier().getValue()+". This is illegal since the obsoletes filed is set, you can't change it again.");
1919
	        }
1920
	    }
1921
	}
1922
  
1923
  /**
1924
   * Given a Permission, returns a list of all permissions that it encompasses
1925
   * Permissions are hierarchical so that WRITE also allows READ.
1926
   * @param permission
1927
   * @return list of included Permissions for the given permission
1928
   */
1929
  protected List<Permission> expandPermissions(Permission permission) {
1930
	  	List<Permission> expandedPermissions = new ArrayList<Permission>();
1931
	    if (permission.equals(Permission.READ)) {
1932
	    	expandedPermissions.add(Permission.READ);
1933
	    }
1934
	    if (permission.equals(Permission.WRITE)) {
1935
	    	expandedPermissions.add(Permission.READ);
1936
	    	expandedPermissions.add(Permission.WRITE);
1937
	    }
1938
	    if (permission.equals(Permission.CHANGE_PERMISSION)) {
1939
	    	expandedPermissions.add(Permission.READ);
1940
	    	expandedPermissions.add(Permission.WRITE);
1941
	    	expandedPermissions.add(Permission.CHANGE_PERMISSION);
1942
	    }
1943
	    return expandedPermissions;
1944
  }
1945

    
1946
  /*
1947
   * Write a stream to a file
1948
   * 
1949
   * @param dir - the directory to write to
1950
   * @param fileName - the file name to write to
1951
   * @param data - the object bytes as an input stream
1952
   * 
1953
   * @return newFile - the new file created
1954
   * 
1955
   * @throws ServiceFailure
1956
   */
1957
  private File writeStreamToFile(File dir, String fileName, InputStream data) 
1958
    throws ServiceFailure {
1959
    
1960
    File newFile = new File(dir, fileName);
1961
    logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
1962

    
1963
    try {
1964
        if (newFile.createNewFile()) {
1965
          // write data stream to desired file
1966
          OutputStream os = new FileOutputStream(newFile);
1967
          long length = IOUtils.copyLarge(data, os);
1968
          os.flush();
1969
          os.close();
1970
        } else {
1971
          logMetacat.debug("File creation failed, or file already exists.");
1972
          throw new ServiceFailure("1190", "File already exists: " + fileName);
1973
        }
1974
    } catch (FileNotFoundException e) {
1975
      logMetacat.debug("FNF: " + e.getMessage());
1976
      throw new ServiceFailure("1190", "File not found: " + fileName + " " 
1977
                + e.getMessage());
1978
    } catch (IOException e) {
1979
      logMetacat.debug("IOE: " + e.getMessage());
1980
      throw new ServiceFailure("1190", "File was not written: " + fileName 
1981
                + " " + e.getMessage());
1982
    }
1983

    
1984
    return newFile;
1985
  }
1986

    
1987
  /*
1988
   * Returns a list of nodes that have been registered with the DataONE infrastructure
1989
   * that match the given session subject
1990
   * @return nodes - List of nodes from the registry with a matching session subject
1991
   * 
1992
   * @throws ServiceFailure
1993
   * @throws NotImplemented
1994
   */
1995
  protected List<Node> listNodesBySubject(Subject subject) 
1996
      throws ServiceFailure, NotImplemented {
1997
      List<Node> nodeList = new ArrayList<Node>();
1998
      
1999
      CNode cn = D1Client.getCN();
2000
      List<Node> nodes = cn.listNodes().getNodeList();
2001
      
2002
      // find the node in the node list
2003
      for ( Node node : nodes ) {
2004
          
2005
          List<Subject> nodeSubjects = node.getSubjectList();
2006
          if (nodeSubjects != null) {    
2007
	          // check if the session subject is in the node subject list
2008
	          for (Subject nodeSubject : nodeSubjects) {
2009
	              if ( nodeSubject.equals(subject) ) { // subject of session == node subject
2010
	                  nodeList.add(node);  
2011
	              }                              
2012
	          }
2013
          }
2014
      }
2015
      
2016
      return nodeList;
2017
      
2018
  }
2019

    
2020
  /**
2021
   * Archives an object, where the object is either a 
2022
   * data object or a science metadata object.
2023
   * Note: it doesn't check the authorization; it doesn't lock the system metadata;it only accept pid.
2024
   * @param session - the Session object containing the credentials for the Subject
2025
   * @param pid - The object identifier to be archived
2026
   * 
2027
   * @return pid - the identifier of the object used for the archiving
2028
   * 
2029
   * @throws InvalidToken
2030
   * @throws ServiceFailure
2031
   * @throws NotAuthorized
2032
   * @throws NotFound
2033
   * @throws NotImplemented
2034
   * @throws InvalidRequest
2035
   */
2036
  protected Identifier archiveObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2037
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2038

    
2039
      String localId = null;
2040
      boolean allowed = false;
2041
      String username = Constants.SUBJECT_PUBLIC;
2042
      if (session == null) {
2043
      	throw new InvalidToken("1330", "No session has been provided");
2044
      } else {
2045
          username = session.getSubject().getValue();
2046
      }
2047
      // do we have a valid pid?
2048
      if (pid == null || pid.getValue().trim().equals("")) {
2049
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
2050
      }
2051
      
2052
      if(sysMeta == null) {
2053
          throw new NotFound("2911", "There is no system metadata associated with "+pid.getValue());
2054
      }
2055
      
2056
      // check for the existing identifier
2057
      try {
2058
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2059
      } catch (McdbDocNotFoundException e) {
2060
          throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
2061
      } catch (SQLException e) {
2062
          throw new ServiceFailure("1350", "The object with the provided identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
2063
      }
2064

    
2065

    
2066
          try {
2067
              // archive the document
2068
              DocumentImpl.delete(localId, null, null, null, false);
2069
              if(log) {
2070
                   try {
2071
                      EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
2072
                   } catch (Exception e) {
2073
                      logMetacat.warn("D1NodeService.archiveObject - can't log the delete event since "+e.getMessage());
2074
                   }
2075
              }
2076
             
2077
              
2078
              // archive it
2079
              sysMeta.setArchived(true);
2080
              if(needModifyDate) {
2081
                  sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2082
                  sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2083
              }
2084
              HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2085
              
2086
              // submit for indexing
2087
              // DocumentImpl call above should do this.
2088
              // see: https://projects.ecoinformatics.org/ecoinfo/issues/6030
2089
              //HazelcastService.getInstance().getIndexQueue().add(sysMeta);
2090
              
2091
          } catch (McdbDocNotFoundException e) {
2092
              throw new NotFound("1340", "The provided identifier was invalid.");
2093

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

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

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

    
2104

    
2105
      return pid;
2106
  }
2107
  
2108
  /**
2109
   * Archive a object on cn and notify the replica. This method doesn't lock the system metadata map. The caller should lock it.
2110
   * This method doesn't check the authorization; this method only accept a pid.
2111
   * It wouldn't notify the replca that the system metadata has been changed.
2112
   * @param session
2113
   * @param pid
2114
   * @param sysMeta
2115
   * @param notifyReplica
2116
   * @return
2117
   * @throws InvalidToken
2118
   * @throws ServiceFailure
2119
   * @throws NotAuthorized
2120
   * @throws NotFound
2121
   * @throws NotImplemented
2122
   */
2123
  protected void archiveCNObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2124
          throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2125

    
2126
          String localId = null; // The corresponding docid for this pid
2127
          
2128
          // Check for the existing identifier
2129
          try {
2130
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2131
              archiveObject(log, session, pid, sysMeta, needModifyDate);
2132
          
2133
          } catch (McdbDocNotFoundException e) {
2134
              // This object is not registered in the identifier table. Assume it is of formatType DATA,
2135
              // and set the archive flag. (i.e. the *object* doesn't exist on the CN)
2136
              
2137
              try {
2138
                  if ( sysMeta != null ) {
2139
                    sysMeta.setArchived(true);
2140
                    if (needModifyDate) {
2141
                        sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2142
                        sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2143
                    }
2144
                    HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2145
                      
2146
                  } else {
2147
                      throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2148
                          ". Couldn't obtain the system metadata record.");
2149
                      
2150
                  }
2151
                  
2152
              } catch (RuntimeException re) {
2153
                  throw new ServiceFailure("4972", "Couldn't archive " + pid.getValue() + 
2154
                      ". The error message was: " + re.getMessage());
2155
                  
2156
              } 
2157

    
2158
          } catch (SQLException e) {
2159
              throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2160
                      ". The local id of the object with the identifier can't be identified since "+e.getMessage());
2161
          }
2162
          
2163
    }
2164
  
2165
  
2166
  /**
2167
   * A utility method for v1 api to check the specified identifier exists as a pid
2168
   * @param identifier  the specified identifier
2169
   * @param serviceFailureCode  the detail error code for the service failure exception
2170
   * @param noFoundCode  the detail error code for the not found exception
2171
   * @throws ServiceFailure
2172
   * @throws NotFound
2173
   */
2174
  public void checkV1SystemMetaPidExist(Identifier identifier, String serviceFailureCode, String serviceFailureMessage,  
2175
          String noFoundCode, String notFoundMessage) throws ServiceFailure, NotFound {
2176
      boolean exists = false;
2177
      try {
2178
          exists = IdentifierManager.getInstance().systemMetadataPIDExists(identifier);
2179
      } catch (SQLException e) {
2180
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+e.getMessage());
2181
      }
2182
      if(!exists) {
2183
         //the v1 method only handles a pid. so it should throw a not-found exception.
2184
          // check if the pid was deleted.
2185
          try {
2186
              String localId = IdentifierManager.getInstance().getLocalId(identifier.getValue());
2187
              if(EventLog.getInstance().isDeleted(localId)) {
2188
                  notFoundMessage=notFoundMessage+" "+DELETEDMESSAGE;
2189
              } 
2190
            } catch (Exception e) {
2191
              logMetacat.info("Couldn't determine if the not-found identifier "+identifier.getValue()+" was deleted since "+e.getMessage());
2192
            }
2193
            throw new NotFound(noFoundCode, notFoundMessage);
2194
      }
2195
  }
2196
  
2197
  /**
2198
   * Utility method to get the PID for an SID. If the specified identifier is not an SID
2199
   * , null will be returned.
2200
   * @param sid  the specified sid
2201
   * @param serviceFailureCode  the detail error code for the service failure exception
2202
   * @return the pid for the sid. If the specified identifier is not an SID, null will be returned.
2203
   * @throws ServiceFailure
2204
   */
2205
  protected Identifier getPIDForSID(Identifier sid, String serviceFailureCode) throws ServiceFailure {
2206
      Identifier id = null;
2207
      String serviceFailureMessage = "The PID "+" couldn't be identified for the sid " + sid.getValue();
2208
      try {
2209
          //determine if the given pid is a sid or not.
2210
          if(IdentifierManager.getInstance().systemMetadataSIDExists(sid)) {
2211
              try {
2212
                  //set the header pid for the sid if the identifier is a sid.
2213
                  id = IdentifierManager.getInstance().getHeadPID(sid);
2214
              } catch (SQLException sqle) {
2215
                  throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+sqle.getMessage());
2216
              }
2217
              
2218
          }
2219
      } catch (SQLException e) {
2220
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage + " since "+e.getMessage());
2221
      }
2222
      return id;
2223
  }
2224

    
2225
  /*
2226
   * 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:
2227
   * A. If the sysmeta doesn't have an SID, nothing needs to be checked for the SID.
2228
   * B. If the sysmeta does have an SID, it may be an identifier which doesn't exist in the system.
2229
   * C. If the sysmeta does have an SID and it exists as an SID in the system, those scenarios are acceptable:
2230
   *    i. The sysmeta has an obsoletes field, the SID has the same value as the SID of the system metadata of the obsoleting pid.
2231
   *    ii. The sysmeta has an obsoletedBy field, the SID has the same value as the SID of the system metadata of the obsoletedBy pid. 
2232
   */
2233
  protected boolean checkSidInModifyingSystemMetadata(SystemMetadata sysmeta, String invalidSystemMetadataCode, String serviceFailureCode) throws InvalidSystemMetadata, ServiceFailure{
2234
      boolean pass = false;
2235
      if(sysmeta == null) {
2236
          throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The system metadata is null in the request.");
2237
      }
2238
      Identifier sid = sysmeta.getSeriesId();
2239
      if(sid != null) {
2240
          // the series id exists
2241
          if (!isValidIdentifier(sid)) {
2242
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id in the system metadata is invalid in the request.");
2243
          }
2244
          Identifier pid = sysmeta.getIdentifier();
2245
          if (!isValidIdentifier(pid)) {
2246
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The pid in the system metadata is invalid in the request.");
2247
          }
2248
          //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 )
2249
          if(sid.getValue().equals(pid.getValue())) {
2250
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
2251
          }
2252
          try {
2253
              if (IdentifierManager.getInstance().identifierExists(sid.getValue())) {
2254
                  //the sid exists in system
2255
                  if(sysmeta.getObsoletes() != null) {
2256
                      SystemMetadata obsoletesSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletes());
2257
                      if(obsoletesSysmeta != null) {
2258
                          Identifier obsoletesSid = obsoletesSysmeta.getSeriesId();
2259
                          if(obsoletesSid != null && obsoletesSid.getValue() != null && !obsoletesSid.getValue().trim().equals("")) {
2260
                              if(sid.getValue().equals(obsoletesSid.getValue())) {
2261
                                  pass = true;// the i of rule C
2262
                              }
2263
                          }
2264
                      } else {
2265
                           logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue()+
2266
                                                                         " which is the value of the obsoletes. So we can't check if the sid " +sid.getValue()+" is legitimate ");
2267
                      }
2268
                  }
2269
                  if(!pass) {
2270
                      // the sid doesn't match the sid of the obsoleting identifier. So we check the obsoletedBy
2271
                      if(sysmeta.getObsoletedBy() != null) {
2272
                          SystemMetadata obsoletedBySysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletedBy());
2273
                          if(obsoletedBySysmeta != null) {
2274
                              Identifier obsoletedBySid = obsoletedBySysmeta.getSeriesId();
2275
                              if(obsoletedBySid != null && obsoletedBySid.getValue() != null && !obsoletedBySid.getValue().trim().equals("")) {
2276
                                  if(sid.getValue().equals(obsoletedBySid.getValue())) {
2277
                                      pass = true;// the ii of the rule C
2278
                                  }
2279
                              }
2280
                          } else {
2281
                              logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue() 
2282
                                                                            +" which is the value of the obsoletedBy. So we can't check if the sid "+sid.getValue()+" is legitimate.");
2283
                          }
2284
                      }
2285
                  }
2286
                  if(!pass) {
2287
                      throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+
2288
                              " in the system metadata exists in the system. And it doesn't match either previous object's sid or the next object's sid.");
2289
                  }
2290
              } else {
2291
                  pass = true; //Rule B
2292
              }
2293
          } catch (SQLException e) {
2294
              throw new ServiceFailure(serviceFailureCode, "Can't determine if the sid in the system metadata is unique or not since "+e.getMessage());
2295
          }
2296
          
2297
      } else {
2298
          //no sid. Rule A.
2299
          pass = true;
2300
      }
2301
      return pass;
2302
      
2303
  }
2304
  
2305
  //@Override
2306
  public OptionList listViews(Session arg0) throws InvalidToken,
2307
          ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented {
2308
      OptionList views = new OptionList();
2309
      views.setKey("views");
2310
      views.setDescription("List of views for objects on the node");
2311
      Vector<String> skinNames = null;
2312
      try {
2313
          skinNames = SkinUtil.getSkinNames();
2314
      } catch (PropertyNotFoundException e) {
2315
          throw new ServiceFailure("2841", e.getMessage());
2316
      }
2317
      for (String skinName: skinNames) {
2318
          views.addOption(skinName);
2319
      }
2320
      return views;
2321
  }
2322
  
2323
  public OptionList listViews() throws InvalidToken,
2324
  ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented { 
2325
      return listViews(null);
2326
  }
2327

    
2328
  //@Override
2329
  public InputStream view(Session session, String format, Identifier pid)
2330
          throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
2331
          NotImplemented, NotFound {
2332
      InputStream resultInputStream = null;
2333
      
2334
      String serviceFailureCode = "2831";
2335
      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2336
      if(sid != null) {
2337
          pid = sid;
2338
      }
2339
      
2340
      SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2341
      InputStream object = this.get(session, pid);
2342

    
2343
      try {
2344
          // can only transform metadata, really
2345
          ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2346
          if (objectFormat.getFormatType().equals("METADATA")) {
2347
              // transform
2348
              DBTransform transformer = new DBTransform();
2349
              String documentContent = IOUtils.toString(object, "UTF-8");
2350
              String sourceType = objectFormat.getFormatId().getValue();
2351
              String targetType = "-//W3C//HTML//EN";
2352
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
2353
              Writer writer = new OutputStreamWriter(baos , "UTF-8");
2354
              // TODO: include more params?
2355
              Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2356
              String localId = null;
2357
              try {
2358
                  localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2359
              } catch (McdbDocNotFoundException e) {
2360
                  throw new NotFound("1020", e.getMessage());
2361
              }
2362
              params.put("qformat", new String[] {format});               
2363
              params.put("docid", new String[] {localId});
2364
              params.put("pid", new String[] {pid.getValue()});
2365
              transformer.transformXMLDocument(
2366
                      documentContent , 
2367
                      sourceType, 
2368
                      targetType , 
2369
                      format, 
2370
                      writer, 
2371
                      params, 
2372
                      null //sessionid
2373
                      );
2374
              
2375
              // finally, get the HTML back
2376
              resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2377
              ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2378
  
2379
          } else {
2380
              // just return the raw bytes
2381
              resultInputStream = object;
2382
          }
2383
      } catch (IOException e) {
2384
          // report as service failure
2385
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2386
          sf.initCause(e);
2387
          throw sf;
2388
      } catch (PropertyNotFoundException e) {
2389
          // report as service failure
2390
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2391
          sf.initCause(e);
2392
          throw sf;
2393
      } catch (SQLException e) {
2394
          // report as service failure
2395
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2396
          sf.initCause(e);
2397
          throw sf;
2398
      } catch (ClassNotFoundException e) {
2399
          // report as service failure
2400
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2401
          sf.initCause(e);
2402
          throw sf;
2403
      }
2404
      
2405
      return resultInputStream;
2406
  } 
2407
  
2408
  /*
2409
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2410
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2411
   * the guid of the first row.
2412
   */
2413
  protected String existsInObsoletes(Identifier id) throws InvalidRequest, ServiceFailure{
2414
      String guid = existsInFields("obsoletes", id);
2415
      return guid;
2416
  }
2417
  
2418
  /*
2419
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2420
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2421
   * the guid of the first row.
2422
   */
2423
  protected String existsInObsoletedBy(Identifier id) throws InvalidRequest, ServiceFailure{
2424
      String guid = existsInFields("obsoleted_by", id);
2425
      return guid;
2426
  }
2427

    
2428
  /*
2429
   * Determine if the given identifier exists in the given column in the system metadata table. 
2430
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2431
   * the guid of the first row.
2432
   */
2433
  private String existsInFields(String column, Identifier id) throws InvalidRequest, ServiceFailure {
2434
      String guid = null;
2435
      if(id == null ) {
2436
          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");
2437
      }
2438
      String sql = "SELECT guid FROM systemmetadata WHERE "+column+" = ?";
2439
      int serialNumber = -1;
2440
      DBConnection dbConn = null;
2441
      PreparedStatement stmt = null;
2442
      ResultSet result = null;
2443
      try {
2444
          dbConn = 
2445
                  DBConnectionPool.getDBConnection("D1NodeService.existsInFields");
2446
          serialNumber = dbConn.getCheckOutSerialNumber();
2447
          stmt = dbConn.prepareStatement(sql);
2448
          stmt.setString(1, id.getValue());
2449
          result = stmt.executeQuery();
2450
          if(result.next()) {
2451
              guid = result.getString(1);
2452
          }
2453
          stmt.close();
2454
      } catch (SQLException e) {
2455
          e.printStackTrace();
2456
          throw new ServiceFailure("4862","We can't determine if the id "+id.getValue()+" exists in field "+column+" in the systemmetadata table since "+e.getMessage());
2457
      } finally {
2458
          // Return database connection to the pool
2459
          DBConnectionPool.returnDBConnection(dbConn, serialNumber);
2460
          if(stmt != null) {
2461
              try {
2462
                  stmt.close();
2463
              } catch (SQLException e) {
2464
                  logMetacat.warn("We can close the PreparedStatment in D1NodeService.existsInFields since "+e.getMessage());
2465
              }
2466
          }
2467
          
2468
      }
2469
      return guid;
2470
      
2471
  }
2472
}
(2-2/8)