Project

General

Profile

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

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

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

    
49
import javax.servlet.http.HttpServletRequest;
50

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

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

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

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

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

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

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

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

    
201
      return describeResponse;
202

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

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

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

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

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

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

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

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

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

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

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

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

    
441
      // Science metadata (XML) or science data object?
442
      // TODO: there are cases where certain object formats are science metadata
443
      // but are not XML (netCDF ...).  Handle this.
444
      if ( isScienceMetadata(sysmeta) ) {
445
        
446
        // CASE METADATA:
447
      	//String objectAsXML = "";
448
        try {
449
	        //objectAsXML = IOUtils.toString(object, "UTF-8");
450
            String formatId = null;
451
            if(sysmeta.getFormatId() != null)  {
452
                formatId = sysmeta.getFormatId().getValue();
453
            }
454
	        localId = insertOrUpdateDocument(object,"UTF-8", pid, session, "insert", formatId);
455
	        //localId = im.getLocalId(pid.getValue());
456

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

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

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

    
504
    resultPid = pid;
505
    
506
    logMetacat.debug("create() complete for object: " + pid.getValue());
507

    
508
    return resultPid;
509
  }
510
  
511
  /*
512
   * Roll-back method when inserting data object fails.
513
   */
514
  protected void removeSystemMeta(Identifier id){
515
      HazelcastService.getInstance().getSystemMetadataMap().remove(id);
516
  }
517
  
518
  /*
519
   * Roll-back method when inserting data object fails.
520
   */
521
  protected void removeSolrIndex(SystemMetadata sysMeta) {
522
      sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
523
      sysMeta.setArchived(true);
524
      sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
525
      try {
526
          MetacatSolrIndex.getInstance().submit(sysMeta.getIdentifier(), sysMeta, null, false);
527
      } catch (Exception e) {
528
          logMetacat.warn("Can't remove the solr index for pid "+sysMeta.getIdentifier().getValue());
529
      }
530
      
531
  }
532

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

    
559
	  // only admin access to this method
560
	  // see https://redmine.dataone.org/issues/2855
561
	  if (!isAdminAuthorized(session)) {
562
		  throw new NotAuthorized("1460", "Only the CN or admin is allowed to harvest logs from this node");
563
	  }
564
    Log log = new Log();
565
    IdentifierManager im = IdentifierManager.getInstance();
566
    EventLog el = EventLog.getInstance();
567
    if ( fromDate == null ) {
568
      logMetacat.debug("setting fromdate from null");
569
      fromDate = new Date(1);
570
    }
571
    if ( toDate == null ) {
572
      logMetacat.debug("setting todate from null");
573
      toDate = new Date();
574
    }
575

    
576
    if ( start == null ) {
577
    	start = 0;	
578
    }
579
    
580
    if ( count == null ) {
581
    	count = 1000;
582
    }
583
    
584
    // safeguard against large requests
585
    if (count > MAXIMUM_DB_RECORD_COUNT) {
586
    	count = MAXIMUM_DB_RECORD_COUNT;
587
    }
588

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

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

    
695
    // if we fail to set the input stream
696
    if ( inputStream == null ) {
697
        String error ="";
698
        if(EventLog.getInstance().isDeleted(localId)) {
699
              error=DELETEDMESSAGE;
700
        }
701
        throw new NotFound("1020", "The object specified by " + 
702
                         pid.getValue() +
703
                         " does not exist at this node. "+error);
704
    }
705
    
706
	// log the read event
707
    String principal = Constants.SUBJECT_PUBLIC;
708
    if (session != null && session.getSubject() != null) {
709
    	principal = session.getSubject().getValue();
710
    }
711
    EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "read");
712
    
713
    return inputStream;
714
  }
715

    
716
  /**
717
   * Return the system metadata for a given object
718
   * 
719
   * @param session - the Session object containing the credentials for the Subject
720
   * @param pid - the object identifier for the given object
721
   * 
722
   * @return inputStream - the input stream of the given system metadata object
723
   * 
724
   * @throws InvalidToken
725
   * @throws ServiceFailure
726
   * @throws NotAuthorized
727
   * @throws NotFound
728
   * @throws InvalidRequest
729
   * @throws NotImplemented
730
   */
731
    public SystemMetadata getSystemMetadata(Session session, Identifier pid)
732
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
733
        NotImplemented {
734

    
735
        String serviceFailureCode = "1090";
736
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
737
        if(sid != null) {
738
            pid = sid;
739
        }
740
        boolean isAuthorized = false;
741
        SystemMetadata systemMetadata = null;
742
        List<Replica> replicaList = null;
743
        NodeReference replicaNodeRef = null;
744
        List<Node> nodeListBySubject = null;
745
        Subject subject = null;
746
        
747
        if (session != null ) {
748
            subject = session.getSubject();
749
        }
750
        
751
        // check normal authorization
752
        BaseException originalAuthorizationException = null;
753
        if (!isAuthorized) {
754
            try {
755
                isAuthorized = isAuthorized(session, pid, Permission.READ);
756

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

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

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

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

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

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

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

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

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

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

    
1455
    logMetacat.debug("Case DATA: starting to write to disk.");
1456
	if (locked) {
1457

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

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

    
1561
      ObjectList objectList = null;
1562

    
1563
      try {
1564
          // safeguard against large requests
1565
          if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
1566
              count = MAXIMUM_DB_RECORD_COUNT;
1567
          }
1568
          boolean isSid = false;
1569
          if(identifier != null) {
1570
              isSid = IdentifierManager.getInstance().systemMetadataSIDExists(identifier);
1571
          }
1572
          objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, nodeId, start, count, identifier, isSid);
1573
      } catch (Exception e) {
1574
          throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
1575
      }
1576

    
1577
      return objectList;
1578
  }
1579

    
1580

    
1581
  /**
1582
   * Update a systemMetadata document
1583
   * 
1584
   * @param sysMeta - the system metadata object in the system to update
1585
   */
1586
    protected void updateSystemMetadata(SystemMetadata sysMeta)
1587
        throws ServiceFailure {
1588
        logMetacat.debug("D1NodeService.updateSystemMetadata() called.");
1589
        try {
1590
            HazelcastService.getInstance().getSystemMetadataMap().lock(sysMeta.getIdentifier());
1591
            boolean needUpdateModificationDate = true;
1592
            updateSystemMetadataWithoutLock(sysMeta, needUpdateModificationDate);
1593
        } catch (Exception e) {
1594
            throw new ServiceFailure("4862", e.getMessage());
1595
        } finally {
1596
            HazelcastService.getInstance().getSystemMetadataMap().unlock(sysMeta.getIdentifier());
1597

    
1598
        }
1599

    
1600
    }
1601
    
1602
    /**
1603
     * Update system metadata without locking the system metadata in hazelcast server. So the caller should lock it first. 
1604
     * @param sysMeta
1605
     * @param needUpdateModificationDate
1606
     * @throws ServiceFailure
1607
     */
1608
    private void updateSystemMetadataWithoutLock(SystemMetadata sysMeta, boolean needUpdateModificationDate) throws ServiceFailure {
1609
        logMetacat.debug("D1NodeService.updateSystemMetadataWithoutLock() called.");
1610
        if(needUpdateModificationDate) {
1611
            logMetacat.debug("D1NodeService.updateSystemMetadataWithoutLock() - update the modification date.");
1612
            sysMeta.setDateSysMetadataModified(new Date());
1613
        }
1614
        
1615
        // submit for indexing
1616
        try {
1617
            HazelcastService.getInstance().getSystemMetadataMap().put(sysMeta.getIdentifier(), sysMeta);
1618
            MetacatSolrIndex.getInstance().submit(sysMeta.getIdentifier(), sysMeta, null, true);
1619
        } catch (Exception e) {
1620
            throw new ServiceFailure("4862", e.getMessage());
1621
            //logMetacat.warn("D1NodeService.updateSystemMetadataWithoutLock - we can't submit the change of the system metadata to the solr index since "+e.getMessage());
1622
        }
1623
    }
1624
    
1625
    /**
1626
     * Update the system metadata of the specified pid. The caller of this method should lock the system metadata in hazelcast server.
1627
     * @param session - the identity of the client which calls the method
1628
     * @param pid - the identifier of the object which will be updated
1629
     * @param sysmeta - the new system metadata  
1630
     * @return
1631
     * @throws NotImplemented
1632
     * @throws NotAuthorized
1633
     * @throws ServiceFailure
1634
     * @throws InvalidRequest
1635
     * @throws InvalidSystemMetadata
1636
     * @throws InvalidToken
1637
     */
1638
	protected boolean updateSystemMetadata(Session session, Identifier pid,
1639
			SystemMetadata sysmeta, boolean needUpdateModificationDate, SystemMetadata currentSysmeta, boolean fromCN) throws NotImplemented, NotAuthorized,
1640
			ServiceFailure, InvalidRequest, InvalidSystemMetadata, InvalidToken {
1641
		
1642
	  // The lock to be used for this identifier
1643
      Lock lock = null;
1644
     
1645
      // verify that guid == SystemMetadata.getIdentifier()
1646
      logMetacat.debug("Comparing guid|sysmeta_guid: " + pid.getValue() + 
1647
          "|" + sysmeta.getIdentifier().getValue());
1648
      
1649
      if (!pid.getValue().equals(sysmeta.getIdentifier().getValue())) {
1650
          throw new InvalidRequest("4863", 
1651
              "The identifier in method call (" + pid.getValue() + 
1652
              ") does not match identifier in system metadata (" +
1653
              sysmeta.getIdentifier().getValue() + ").");
1654
      }
1655
      //compare serial version.
1656
      
1657
      //check the sid
1658
      //SystemMetadata currentSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1659
      logMetacat.debug("The current dateUploaded is ============"+currentSysmeta.getDateUploaded());
1660
      logMetacat.debug("the dateUploaded in the new system metadata is "+sysmeta.getDateUploaded());
1661
      logMetacat.debug("The current dateUploaded is (by time) ============"+currentSysmeta.getDateUploaded().getTime());
1662
      logMetacat.debug("the dateUploaded in the new system metadata is (by time) "+sysmeta.getDateUploaded().getTime());
1663
      if(currentSysmeta == null ) {
1664
          //do we need throw an exception?
1665
          logMetacat.warn("D1NodeService.updateSystemMetadata: Currently there is no system metadata in this node associated with the pid "+pid.getValue());
1666
      } else {
1667
          
1668
          /*BigInteger newVersion = sysmeta.getSerialVersion();
1669
          if(newVersion == null) {
1670
              throw new InvalidRequest("4869", "The serial version can't be null in the new system metadata");
1671
          }
1672
          BigInteger currentVersion = currentSysmeta.getSerialVersion();
1673
          if(currentVersion != null && newVersion.compareTo(currentVersion) <= 0) {
1674
              throw new InvalidRequest("4869", "The serial version in the new system metadata is "+newVersion.toString()+
1675
                      " which is less than or equals the previous version "+currentVersion.toString()+". This is illegal in the updateSystemMetadata method.");
1676
          }*/
1677
          Identifier currentSid = currentSysmeta.getSeriesId();
1678
          if(currentSid != null) {
1679
              logMetacat.debug("In the branch that the sid is not null in the current system metadata and the current sid is "+currentSid.getValue());
1680
              //new sid must match the current sid
1681
              Identifier newSid = sysmeta.getSeriesId();
1682
              if (!isValidIdentifier(newSid)) {
1683
                  throw new InvalidRequest("4869", "The series id in the system metadata is invalid in the request.");
1684
              } else {
1685
                  if(!newSid.getValue().equals(currentSid.getValue())) {
1686
                      throw new InvalidRequest("4869", "The series id "+newSid.getValue() +" in the system metadata doesn't match the current sid "+currentSid.getValue());
1687
                  }
1688
              }
1689
          } else {
1690
              //current system metadata doesn't have a sid. So we can have those scenarios
1691
              //1. The new sid may be null as well
1692
              //2. If the new sid does exist, it may be an identifier which hasn't bee used.
1693
              //3. If the new sid does exist, it may be an sid which equals the SID it obsoletes
1694
              //4. If the new sid does exist, it may be an sid which equauls the SID it was obsoleted by
1695
              Identifier newSid = sysmeta.getSeriesId();
1696
              if(newSid != null) {
1697
                  //It matches the rules of the checkSidInModifyingSystemMetadata
1698
                  checkSidInModifyingSystemMetadata(sysmeta, "4956", "4868");
1699
              }
1700
          }
1701
          checkModifiedImmutableFields(currentSysmeta, sysmeta);
1702
          checkOneTimeSettableSysmMetaFields(currentSysmeta, sysmeta);
1703
          if(currentSysmeta.getObsoletes() == null && sysmeta.getObsoletes() != null) {
1704
              //we are setting a value to the obsoletes field, so we should make sure if there is not object obsoletes the value
1705
              String obsoletes = existsInObsoletes(sysmeta.getObsoletes());
1706
              if( obsoletes != null) {
1707
                  throw new InvalidSystemMetadata("4956", "There is an object with id "+obsoletes +
1708
                          " already obsoletes the pid "+sysmeta.getObsoletes().getValue() +". You can't set the object "+pid.getValue()+" to obsolete the pid "+sysmeta.getObsoletes().getValue()+" again.");
1709
              }
1710
          }
1711
          
1712
          if(currentSysmeta.getObsoletedBy() == null && sysmeta.getObsoletedBy() != null) {
1713
              //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. 
1714
              String obsoletedBy = existsInObsoletedBy(sysmeta.getObsoletedBy());
1715
              if( obsoletedBy != null) {
1716
                  throw new InvalidSystemMetadata("4956", "There is an object with id "+obsoletedBy +
1717
                          " 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.");
1718
              }
1719
          }
1720
      }
1721
      
1722
      // do the actual update
1723
      if(sysmeta.getArchived() != null && sysmeta.getArchived() == true && 
1724
                 ((currentSysmeta.getArchived() != null && currentSysmeta.getArchived() == false ) || currentSysmeta.getArchived() == null)) {
1725
          boolean logArchive = false;//we log it as the update system metadata event. So don't log it again.
1726
          if(fromCN) {
1727
              logMetacat.debug("D1Node.update - this is to archive a cn object "+pid.getValue());
1728
              try {
1729
                  archiveCNObject(logArchive, session, pid, sysmeta, needUpdateModificationDate);
1730
              } catch (NotFound e) {
1731
                  throw new InvalidRequest("4869", "Can't find the pid "+pid.getValue()+" for archive.");
1732
              }
1733
          } else {
1734
              logMetacat.debug("D1Node.update - this is to archive a MN object "+pid.getValue());
1735
              try {
1736
                  archiveObject(logArchive, session, pid, sysmeta, needUpdateModificationDate);
1737
              } catch (NotFound e) {
1738
                  throw new InvalidRequest("4869", "Can't find the pid "+pid.getValue()+" for archive.");
1739
              }
1740
          }
1741
      } else {
1742
          logMetacat.debug("D1Node.update - regularly update the system metadata of the pid "+pid.getValue());
1743
          updateSystemMetadataWithoutLock(sysmeta, needUpdateModificationDate);
1744
      }
1745

    
1746
      try {
1747
    	  String localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1748
    	  EventLog.getInstance().log(request.getRemoteAddr(), 
1749
    	          request.getHeader("User-Agent"), session.getSubject().getValue(), 
1750
    	          localId, "updateSystemMetadata");
1751
      } catch (McdbDocNotFoundException e) {
1752
    	  // do nothing, no localId to log with
1753
    	  logMetacat.warn("Could not log 'updateSystemMetadata' event because no localId was found for pid: " + pid.getValue());
1754
      } catch (SQLException e) {
1755
          logMetacat.warn("Could not log 'updateSystemMetadata' event because the localId couldn't be identified for the pid: " + pid.getValue());
1756
      }
1757
      return true;
1758
	}
1759
	
1760
	
1761
	/*
1762
	 * Check if the newMeta modifies an immutable field. 
1763
	 */
1764
	private void checkModifiedImmutableFields(SystemMetadata orgMeta, SystemMetadata newMeta) throws InvalidRequest{
1765
	    logMetacat.debug("in the start of the checkModifiedImmutableFields method");
1766
	    if(orgMeta != null && newMeta != null) {
1767
	        logMetacat.debug("in the checkModifiedImmutableFields method when the org and new system metadata is not null");
1768
	        if(newMeta.getIdentifier() == null) {
1769
	            throw new InvalidRequest("4869", "The new version of the system metadata is invalid since the identifier is null");
1770
	        }
1771
	        if(!orgMeta.getIdentifier().equals(newMeta.getIdentifier())) {
1772
	            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 "+
1773
	                  "different to the orginal one "+orgMeta.getIdentifier().getValue());
1774
	        }
1775
	        if(newMeta.getSize() == null) {
1776
	            throw new InvalidRequest("4869", "The new version of the system metadata is invalid since the size is null");
1777
	        }
1778
	        if(!orgMeta.getSize().equals(newMeta.getSize())) {
1779
	            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 "+
1780
	                      "different to the orginal one "+orgMeta.getSize().longValue());
1781
	        }
1782
	        if(newMeta.getChecksum()!= null && orgMeta.getChecksum() != null && !orgMeta.getChecksum().getValue().equals(newMeta.getChecksum().getValue())) {
1783
	            logMetacat.error("The request is trying to modify an immutable field in the SystemMeta: the new system meta's checksum "+newMeta.getChecksum().getValue()+" is "+
1784
                        "different to the orginal one "+orgMeta.getChecksum().getValue());
1785
	            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 "+
1786
                        "different to the orginal one "+orgMeta.getChecksum().getValue());
1787
	        }
1788
	        if(orgMeta.getSubmitter() != null) {
1789
	            logMetacat.debug("in the checkModifiedImmutableFields method and orgMeta.getSubmitter is not null and the orginal submiter is "+orgMeta.getSubmitter().getValue());
1790
	        }
1791
	        
1792
	        if(newMeta.getSubmitter() != null) {
1793
                logMetacat.debug("in the checkModifiedImmutableFields method and newMeta.getSubmitter is not null and the submiter in the new system metadata is "+newMeta.getSubmitter().getValue());
1794
            }
1795
	        if(orgMeta.getSubmitter() != null && newMeta.getSubmitter() != null && !orgMeta.getSubmitter().equals(newMeta.getSubmitter())) {
1796
	            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 "+
1797
                        "different to the orginal one "+orgMeta.getSubmitter().getValue());
1798
	        }
1799
	        
1800
	        if(orgMeta.getDateUploaded() != null && newMeta.getDateUploaded() != null && orgMeta.getDateUploaded().getTime() != newMeta.getDateUploaded().getTime()) {
1801
	            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 "+
1802
                        "different to the orginal one "+orgMeta.getDateUploaded());
1803
	        }
1804
	        
1805
	        if(orgMeta.getOriginMemberNode() != null && newMeta.getOriginMemberNode() != null && !orgMeta.getOriginMemberNode().equals(newMeta.getOriginMemberNode())) {
1806
	            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 "+
1807
                        "different to the orginal one "+orgMeta.getOriginMemberNode().getValue());
1808
	        }
1809
	        
1810
	        if (orgMeta.getOriginMemberNode() != null && newMeta.getOriginMemberNode() == null ) {
1811
	            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 "+
1812
                        "different to the orginal one "+orgMeta.getOriginMemberNode().getValue());
1813
	        }
1814
	        
1815
	        if(orgMeta.getSeriesId() != null && newMeta.getSeriesId() != null && !orgMeta.getSeriesId().equals(newMeta.getSeriesId())) {
1816
                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 "+
1817
                        "different to the orginal one "+orgMeta.getSeriesId().getValue());
1818
            }
1819
	        
1820
	    }
1821
	}
1822
	
1823
	/*
1824
	 * Some fields in the system metadata, such as obsoletes or obsoletedBy can be set only once. 
1825
	 * After set, they are not allowed to be changed.
1826
	 */
1827
	private void checkOneTimeSettableSysmMetaFields(SystemMetadata orgMeta, SystemMetadata newMeta) throws InvalidRequest {
1828
	    if(orgMeta.getObsoletedBy() != null ) {
1829
	        if(newMeta.getObsoletedBy() == null || !orgMeta.getObsoletedBy().equals(newMeta.getObsoletedBy())) {
1830
	            throw new InvalidRequest("4869", "The request is trying to reset the obsoletedBy field in the system metadata of the object "
1831
	                    + orgMeta.getIdentifier().getValue() +". This is illegal since the obsoletedBy filed is set, you can't change it again.");
1832
	        }
1833
        }
1834
	    if(orgMeta.getObsoletes() != null) {
1835
	        if(newMeta.getObsoletes() == null || !orgMeta.getObsoletes().equals(newMeta.getObsoletes())) {
1836
	            throw new InvalidRequest("4869", "The request is trying to reset the obsoletes field in the system metadata of the object"+
1837
	               orgMeta.getIdentifier().getValue()+". This is illegal since the obsoletes filed is set, you can't change it again.");
1838
	        }
1839
	    }
1840
	}
1841
  
1842
  /**
1843
   * Given a Permission, returns a list of all permissions that it encompasses
1844
   * Permissions are hierarchical so that WRITE also allows READ.
1845
   * @param permission
1846
   * @return list of included Permissions for the given permission
1847
   */
1848
  protected static List<Permission> expandPermissions(Permission permission) {
1849
	  	List<Permission> expandedPermissions = new ArrayList<Permission>();
1850
	    if (permission.equals(Permission.READ)) {
1851
	    	expandedPermissions.add(Permission.READ);
1852
	    }
1853
	    if (permission.equals(Permission.WRITE)) {
1854
	    	expandedPermissions.add(Permission.READ);
1855
	    	expandedPermissions.add(Permission.WRITE);
1856
	    }
1857
	    if (permission.equals(Permission.CHANGE_PERMISSION)) {
1858
	    	expandedPermissions.add(Permission.READ);
1859
	    	expandedPermissions.add(Permission.WRITE);
1860
	    	expandedPermissions.add(Permission.CHANGE_PERMISSION);
1861
	    }
1862
	    return expandedPermissions;
1863
  }
1864

    
1865
  /*
1866
   * Write a stream to a file
1867
   * 
1868
   * @param dir - the directory to write to
1869
   * @param fileName - the file name to write to
1870
   * @param data - the object bytes as an input stream
1871
   * 
1872
   * @return newFile - the new file created
1873
   * 
1874
   * @throws ServiceFailure
1875
   */
1876
  private File writeStreamToFile(File dir, String fileName, InputStream dataStream) 
1877
    throws ServiceFailure {
1878
    
1879
    File newFile = new File(dir, fileName);
1880
    logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
1881

    
1882
    try {
1883
        if (newFile.createNewFile()) {
1884
          // write data stream to desired file
1885
          OutputStream os = new FileOutputStream(newFile);
1886
          long length = IOUtils.copyLarge(dataStream, os);
1887
          os.flush();
1888
          os.close();
1889
        } else {
1890
          logMetacat.debug("File creation failed, or file already exists.");
1891
          throw new ServiceFailure("1190", "File already exists: " + fileName);
1892
        }
1893
    } catch (FileNotFoundException e) {
1894
      logMetacat.debug("FNF: " + e.getMessage());
1895
      throw new ServiceFailure("1190", "File not found: " + fileName + " " 
1896
                + e.getMessage());
1897
    } catch (IOException e) {
1898
      logMetacat.debug("IOE: " + e.getMessage());
1899
      throw new ServiceFailure("1190", "File was not written: " + fileName 
1900
                + " " + e.getMessage());
1901
    } finally {
1902
        IOUtils.closeQuietly(dataStream);
1903
    }
1904

    
1905
    return newFile;
1906
  }
1907

    
1908
  /*
1909
   * Returns a list of nodes that have been registered with the DataONE infrastructure
1910
   * that match the given session subject
1911
   * @return nodes - List of nodes from the registry with a matching session subject
1912
   * 
1913
   * @throws ServiceFailure
1914
   * @throws NotImplemented
1915
   */
1916
  protected List<Node> listNodesBySubject(Subject subject) 
1917
      throws ServiceFailure, NotImplemented {
1918
      List<Node> nodeList = new ArrayList<Node>();
1919
      
1920
      CNode cn = D1Client.getCN();
1921
      List<Node> nodes = cn.listNodes().getNodeList();
1922
      
1923
      // find the node in the node list
1924
      for ( Node node : nodes ) {
1925
          
1926
          List<Subject> nodeSubjects = node.getSubjectList();
1927
          if (nodeSubjects != null) {    
1928
	          // check if the session subject is in the node subject list
1929
	          for (Subject nodeSubject : nodeSubjects) {
1930
	              if ( nodeSubject.equals(subject) ) { // subject of session == node subject
1931
	                  nodeList.add(node);  
1932
	              }                              
1933
	          }
1934
          }
1935
      }
1936
      
1937
      return nodeList;
1938
      
1939
  }
1940

    
1941
  /**
1942
   * Archives an object, where the object is either a 
1943
   * data object or a science metadata object.
1944
   * Note: it doesn't check the authorization; it doesn't lock the system metadata;it only accept pid.
1945
   * @param session - the Session object containing the credentials for the Subject
1946
   * @param pid - The object identifier to be archived
1947
   * 
1948
   * @return pid - the identifier of the object used for the archiving
1949
   * 
1950
   * @throws InvalidToken
1951
   * @throws ServiceFailure
1952
   * @throws NotAuthorized
1953
   * @throws NotFound
1954
   * @throws NotImplemented
1955
   * @throws InvalidRequest
1956
   */
1957
  protected Identifier archiveObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
1958
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1959

    
1960
      String localId = null;
1961
      boolean allowed = false;
1962
      String username = Constants.SUBJECT_PUBLIC;
1963
      if (session == null) {
1964
      	throw new InvalidToken("1330", "No session has been provided");
1965
      } else {
1966
          username = session.getSubject().getValue();
1967
      }
1968
      // do we have a valid pid?
1969
      if (pid == null || pid.getValue().trim().equals("")) {
1970
          throw new ServiceFailure("1350", "The provided identifier was invalid.");
1971
      }
1972
      
1973
      if(sysMeta == null) {
1974
          throw new NotFound("2911", "There is no system metadata associated with "+pid.getValue());
1975
      }
1976
      
1977
      // check for the existing identifier
1978
      try {
1979
          localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1980
      } catch (McdbDocNotFoundException e) {
1981
          throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
1982
      } catch (SQLException e) {
1983
          throw new ServiceFailure("1350", "The object with the provided identifier "+pid.getValue()+" couldn't be identified since "+e.getMessage());
1984
      }
1985

    
1986

    
1987
          try {
1988
              // archive the document
1989
              DocumentImpl.delete(localId, null, null, null, false);
1990
              if(log) {
1991
                   try {
1992
                      EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
1993
                   } catch (Exception e) {
1994
                      logMetacat.warn("D1NodeService.archiveObject - can't log the delete event since "+e.getMessage());
1995
                   }
1996
              }
1997
             
1998
              
1999
              // archive it
2000
              sysMeta.setArchived(true);
2001
              if(needModifyDate) {
2002
                  sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2003
                  sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2004
              }
2005
              HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2006
              
2007
              // submit for indexing
2008
              // DocumentImpl call above should do this.
2009
              // see: https://projects.ecoinformatics.org/ecoinfo/issues/6030
2010
              //HazelcastService.getInstance().getIndexQueue().add(sysMeta);
2011
              
2012
          } catch (McdbDocNotFoundException e) {
2013
              throw new NotFound("1340", "The provided identifier was invalid.");
2014

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

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

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

    
2025

    
2026
      return pid;
2027
  }
2028
  
2029
  /**
2030
   * Archive a object on cn and notify the replica. This method doesn't lock the system metadata map. The caller should lock it.
2031
   * This method doesn't check the authorization; this method only accept a pid.
2032
   * It wouldn't notify the replca that the system metadata has been changed.
2033
   * @param session
2034
   * @param pid
2035
   * @param sysMeta
2036
   * @param notifyReplica
2037
   * @return
2038
   * @throws InvalidToken
2039
   * @throws ServiceFailure
2040
   * @throws NotAuthorized
2041
   * @throws NotFound
2042
   * @throws NotImplemented
2043
   */
2044
  protected void archiveCNObject(boolean log, Session session, Identifier pid, SystemMetadata sysMeta, boolean needModifyDate) 
2045
          throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2046

    
2047
          String localId = null; // The corresponding docid for this pid
2048
          
2049
          // Check for the existing identifier
2050
          try {
2051
              localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2052
              archiveObject(log, session, pid, sysMeta, needModifyDate);
2053
          
2054
          } catch (McdbDocNotFoundException e) {
2055
              // This object is not registered in the identifier table. Assume it is of formatType DATA,
2056
              // and set the archive flag. (i.e. the *object* doesn't exist on the CN)
2057
              
2058
              try {
2059
                  if ( sysMeta != null ) {
2060
                    sysMeta.setArchived(true);
2061
                    if (needModifyDate) {
2062
                        sysMeta.setSerialVersion(sysMeta.getSerialVersion().add(BigInteger.ONE));
2063
                        sysMeta.setDateSysMetadataModified(Calendar.getInstance().getTime());
2064
                    }
2065
                    HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
2066
                      
2067
                  } else {
2068
                      throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2069
                          ". Couldn't obtain the system metadata record.");
2070
                      
2071
                  }
2072
                  
2073
              } catch (RuntimeException re) {
2074
                  throw new ServiceFailure("4972", "Couldn't archive " + pid.getValue() + 
2075
                      ". The error message was: " + re.getMessage());
2076
                  
2077
              } 
2078

    
2079
          } catch (SQLException e) {
2080
              throw new ServiceFailure("4972", "Couldn't archive the object " + pid.getValue() +
2081
                      ". The local id of the object with the identifier can't be identified since "+e.getMessage());
2082
          }
2083
          
2084
    }
2085
  
2086
  
2087
  /**
2088
   * A utility method for v1 api to check the specified identifier exists as a pid
2089
   * @param identifier  the specified identifier
2090
   * @param serviceFailureCode  the detail error code for the service failure exception
2091
   * @param noFoundCode  the detail error code for the not found exception
2092
   * @throws ServiceFailure
2093
   * @throws NotFound
2094
   */
2095
  public void checkV1SystemMetaPidExist(Identifier identifier, String serviceFailureCode, String serviceFailureMessage,  
2096
          String noFoundCode, String notFoundMessage) throws ServiceFailure, NotFound {
2097
      boolean exists = false;
2098
      try {
2099
          exists = IdentifierManager.getInstance().systemMetadataPIDExists(identifier);
2100
      } catch (SQLException e) {
2101
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+e.getMessage());
2102
      }
2103
      if(!exists) {
2104
         //the v1 method only handles a pid. so it should throw a not-found exception.
2105
          // check if the pid was deleted.
2106
          try {
2107
              String localId = IdentifierManager.getInstance().getLocalId(identifier.getValue());
2108
              if(EventLog.getInstance().isDeleted(localId)) {
2109
                  notFoundMessage=notFoundMessage+" "+DELETEDMESSAGE;
2110
              } 
2111
            } catch (Exception e) {
2112
              logMetacat.info("Couldn't determine if the not-found identifier "+identifier.getValue()+" was deleted since "+e.getMessage());
2113
            }
2114
            throw new NotFound(noFoundCode, notFoundMessage);
2115
      }
2116
  }
2117
  
2118
  /**
2119
   * Utility method to get the PID for an SID. If the specified identifier is not an SID
2120
   * , null will be returned.
2121
   * @param sid  the specified sid
2122
   * @param serviceFailureCode  the detail error code for the service failure exception
2123
   * @return the pid for the sid. If the specified identifier is not an SID, null will be returned.
2124
   * @throws ServiceFailure
2125
   */
2126
  protected Identifier getPIDForSID(Identifier sid, String serviceFailureCode) throws ServiceFailure {
2127
      Identifier id = null;
2128
      String serviceFailureMessage = "The PID "+" couldn't be identified for the sid " + sid.getValue();
2129
      try {
2130
          //determine if the given pid is a sid or not.
2131
          if(IdentifierManager.getInstance().systemMetadataSIDExists(sid)) {
2132
              try {
2133
                  //set the header pid for the sid if the identifier is a sid.
2134
                  id = IdentifierManager.getInstance().getHeadPID(sid);
2135
              } catch (SQLException sqle) {
2136
                  throw new ServiceFailure(serviceFailureCode, serviceFailureMessage+" since "+sqle.getMessage());
2137
              }
2138
              
2139
          }
2140
      } catch (SQLException e) {
2141
          throw new ServiceFailure(serviceFailureCode, serviceFailureMessage + " since "+e.getMessage());
2142
      }
2143
      return id;
2144
  }
2145

    
2146
  /*
2147
   * 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:
2148
   * A. If the sysmeta doesn't have an SID, nothing needs to be checked for the SID.
2149
   * B. If the sysmeta does have an SID, it may be an identifier which doesn't exist in the system.
2150
   * C. If the sysmeta does have an SID and it exists as an SID in the system, those scenarios are acceptable:
2151
   *    i. The sysmeta has an obsoletes field, the SID has the same value as the SID of the system metadata of the obsoleting pid.
2152
   *    ii. The sysmeta has an obsoletedBy field, the SID has the same value as the SID of the system metadata of the obsoletedBy pid. 
2153
   */
2154
  protected boolean checkSidInModifyingSystemMetadata(SystemMetadata sysmeta, String invalidSystemMetadataCode, String serviceFailureCode) throws InvalidSystemMetadata, ServiceFailure{
2155
      boolean pass = false;
2156
      if(sysmeta == null) {
2157
          throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The system metadata is null in the request.");
2158
      }
2159
      Identifier sid = sysmeta.getSeriesId();
2160
      if(sid != null) {
2161
          // the series id exists
2162
          if (!isValidIdentifier(sid)) {
2163
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id in the system metadata is invalid in the request.");
2164
          }
2165
          Identifier pid = sysmeta.getIdentifier();
2166
          if (!isValidIdentifier(pid)) {
2167
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The pid in the system metadata is invalid in the request.");
2168
          }
2169
          //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 )
2170
          if(sid.getValue().equals(pid.getValue())) {
2171
              throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
2172
          }
2173
          try {
2174
              if (IdentifierManager.getInstance().identifierExists(sid.getValue())) {
2175
                  //the sid exists in system
2176
                  if(sysmeta.getObsoletes() != null) {
2177
                      SystemMetadata obsoletesSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletes());
2178
                      if(obsoletesSysmeta != null) {
2179
                          Identifier obsoletesSid = obsoletesSysmeta.getSeriesId();
2180
                          if(obsoletesSid != null && obsoletesSid.getValue() != null && !obsoletesSid.getValue().trim().equals("")) {
2181
                              if(sid.getValue().equals(obsoletesSid.getValue())) {
2182
                                  pass = true;// the i of rule C
2183
                              }
2184
                          }
2185
                      } else {
2186
                           logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue()+
2187
                                                                         " which is the value of the obsoletes. So we can't check if the sid " +sid.getValue()+" is legitimate ");
2188
                      }
2189
                  }
2190
                  if(!pass) {
2191
                      // the sid doesn't match the sid of the obsoleting identifier. So we check the obsoletedBy
2192
                      if(sysmeta.getObsoletedBy() != null) {
2193
                          SystemMetadata obsoletedBySysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(sysmeta.getObsoletedBy());
2194
                          if(obsoletedBySysmeta != null) {
2195
                              Identifier obsoletedBySid = obsoletedBySysmeta.getSeriesId();
2196
                              if(obsoletedBySid != null && obsoletedBySid.getValue() != null && !obsoletedBySid.getValue().trim().equals("")) {
2197
                                  if(sid.getValue().equals(obsoletedBySid.getValue())) {
2198
                                      pass = true;// the ii of the rule C
2199
                                  }
2200
                              }
2201
                          } else {
2202
                              logMetacat.warn("D1NodeService.checkSidInModifyingSystemMetacat - Can't find the system metadata for the pid "+sysmeta.getObsoletes().getValue() 
2203
                                                                            +" which is the value of the obsoletedBy. So we can't check if the sid "+sid.getValue()+" is legitimate.");
2204
                          }
2205
                      }
2206
                  }
2207
                  if(!pass) {
2208
                      throw new InvalidSystemMetadata(invalidSystemMetadataCode, "The series id "+sid.getValue()+
2209
                              " in the system metadata exists in the system. And it doesn't match either previous object's sid or the next object's sid.");
2210
                  }
2211
              } else {
2212
                  pass = true; //Rule B
2213
              }
2214
          } catch (SQLException e) {
2215
              throw new ServiceFailure(serviceFailureCode, "Can't determine if the sid in the system metadata is unique or not since "+e.getMessage());
2216
          }
2217
          
2218
      } else {
2219
          //no sid. Rule A.
2220
          pass = true;
2221
      }
2222
      return pass;
2223
      
2224
  }
2225
  
2226
  //@Override
2227
  public OptionList listViews(Session arg0) throws InvalidToken,
2228
          ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented {
2229
      OptionList views = new OptionList();
2230
      views.setKey("views");
2231
      views.setDescription("List of views for objects on the node");
2232
      Vector<String> skinNames = null;
2233
      try {
2234
          skinNames = SkinUtil.getSkinNames();
2235
      } catch (PropertyNotFoundException e) {
2236
          throw new ServiceFailure("2841", e.getMessage());
2237
      }
2238
      for (String skinName: skinNames) {
2239
          views.addOption(skinName);
2240
      }
2241
      return views;
2242
  }
2243
  
2244
  public OptionList listViews() throws InvalidToken,
2245
  ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented { 
2246
      return listViews(null);
2247
  }
2248

    
2249
  //@Override
2250
  public InputStream view(Session session, String format, Identifier pid)
2251
          throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
2252
          NotImplemented, NotFound {
2253
      InputStream resultInputStream = null;
2254
      
2255
      String serviceFailureCode = "2831";
2256
      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2257
      if(sid != null) {
2258
          pid = sid;
2259
      }
2260
      
2261
      SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2262
      InputStream object = this.get(session, pid);
2263

    
2264
      try {
2265
          // can only transform metadata, really
2266
          ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2267
          if (objectFormat.getFormatType().equals("METADATA")) {
2268
              // transform
2269
              DBTransform transformer = new DBTransform();
2270
              String documentContent = IOUtils.toString(object, "UTF-8");
2271
              String sourceType = objectFormat.getFormatId().getValue();
2272
              String targetType = "-//W3C//HTML//EN";
2273
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
2274
              Writer writer = new OutputStreamWriter(baos , "UTF-8");
2275
              // TODO: include more params?
2276
              Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2277
              String localId = null;
2278
              try {
2279
                  localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2280
              } catch (McdbDocNotFoundException e) {
2281
                  throw new NotFound("1020", e.getMessage());
2282
              }
2283
              params.put("qformat", new String[] {format});               
2284
              params.put("docid", new String[] {localId});
2285
              params.put("pid", new String[] {pid.getValue()});
2286
              transformer.transformXMLDocument(
2287
                      documentContent , 
2288
                      sourceType, 
2289
                      targetType , 
2290
                      format, 
2291
                      writer, 
2292
                      params, 
2293
                      null //sessionid
2294
                      );
2295
              
2296
              // finally, get the HTML back
2297
              resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2298
              ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2299
  
2300
          } else {
2301
              // just return the raw bytes
2302
              resultInputStream = object;
2303
          }
2304
      } catch (IOException e) {
2305
          // report as service failure
2306
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2307
          sf.initCause(e);
2308
          throw sf;
2309
      } catch (PropertyNotFoundException e) {
2310
          // report as service failure
2311
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2312
          sf.initCause(e);
2313
          throw sf;
2314
      } catch (SQLException e) {
2315
          // report as service failure
2316
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2317
          sf.initCause(e);
2318
          throw sf;
2319
      } catch (ClassNotFoundException e) {
2320
          // report as service failure
2321
          ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2322
          sf.initCause(e);
2323
          throw sf;
2324
      }
2325
      
2326
      return resultInputStream;
2327
  } 
2328
  
2329
  /*
2330
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2331
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2332
   * the guid of the first row.
2333
   */
2334
  protected String existsInObsoletes(Identifier id) throws InvalidRequest, ServiceFailure{
2335
      String guid = existsInFields("obsoletes", id);
2336
      return guid;
2337
  }
2338
  
2339
  /*
2340
   * Determine if the given identifier exists in the obsoletes field in the system metadata table.
2341
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2342
   * the guid of the first row.
2343
   */
2344
  protected String existsInObsoletedBy(Identifier id) throws InvalidRequest, ServiceFailure{
2345
      String guid = existsInFields("obsoleted_by", id);
2346
      return guid;
2347
  }
2348

    
2349
  /*
2350
   * Determine if the given identifier exists in the given column in the system metadata table. 
2351
   * If the return value is not null, the given identifier exists in the given cloumn. The return value is 
2352
   * the guid of the first row.
2353
   */
2354
  private String existsInFields(String column, Identifier id) throws InvalidRequest, ServiceFailure {
2355
      String guid = null;
2356
      if(id == null ) {
2357
          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");
2358
      }
2359
      String sql = "SELECT guid FROM systemmetadata WHERE "+column+" = ?";
2360
      int serialNumber = -1;
2361
      DBConnection dbConn = null;
2362
      PreparedStatement stmt = null;
2363
      ResultSet result = null;
2364
      try {
2365
          dbConn = 
2366
                  DBConnectionPool.getDBConnection("D1NodeService.existsInFields");
2367
          serialNumber = dbConn.getCheckOutSerialNumber();
2368
          stmt = dbConn.prepareStatement(sql);
2369
          stmt.setString(1, id.getValue());
2370
          result = stmt.executeQuery();
2371
          if(result.next()) {
2372
              guid = result.getString(1);
2373
          }
2374
          stmt.close();
2375
      } catch (SQLException e) {
2376
          e.printStackTrace();
2377
          throw new ServiceFailure("4862","We can't determine if the id "+id.getValue()+" exists in field "+column+" in the systemmetadata table since "+e.getMessage());
2378
      } finally {
2379
          // Return database connection to the pool
2380
          DBConnectionPool.returnDBConnection(dbConn, serialNumber);
2381
          if(stmt != null) {
2382
              try {
2383
                  stmt.close();
2384
              } catch (SQLException e) {
2385
                  logMetacat.warn("We can close the PreparedStatment in D1NodeService.existsInFields since "+e.getMessage());
2386
              }
2387
          }
2388
          
2389
      }
2390
      return guid;
2391
      
2392
  }
2393
}
(2-2/8)