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: leinfelder $'
7
 *     '$Date: 2016-05-02 11:35:37 -0700 (Mon, 02 May 2016) $'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23

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

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

    
50
import javax.servlet.http.HttpServletRequest;
51

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

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

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

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

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

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

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

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

    
202
      return describeResponse;
203

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1458
    logMetacat.debug("Case DATA: starting to write to disk.");
1459
	if (locked) {
1460

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

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

    
1564
      ObjectList objectList = null;
1565

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

    
1580
      return objectList;
1581
  }
1582

    
1583

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

    
1601
        }
1602

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

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

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

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

    
1908
    return newFile;
1909
  }
1910

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

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

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

    
1989

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

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

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

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

    
2028

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

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

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

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

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

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

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