Project

General

Profile

1 6179 cjones
/**
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:  $'
7
 *     '$Date:  $'
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 6228 cjones
import java.io.IOException;
27 6179 cjones
import java.io.InputStream;
28 6228 cjones
import java.security.NoSuchAlgorithmException;
29 6250 cjones
import java.sql.SQLException;
30 6351 cjones
import java.text.SimpleDateFormat;
31 6179 cjones
import java.util.Date;
32 6250 cjones
import java.util.List;
33 6389 leinfelder
import java.util.Timer;
34 6179 cjones
35 6258 cjones
import org.apache.commons.io.IOUtils;
36 6179 cjones
import org.apache.log4j.Logger;
37 6332 leinfelder
import org.dataone.client.D1Client;
38
import org.dataone.client.MNode;
39 6371 leinfelder
import org.dataone.service.util.Constants;
40 6179 cjones
import org.dataone.service.exceptions.IdentifierNotUnique;
41
import org.dataone.service.exceptions.InsufficientResources;
42
import org.dataone.service.exceptions.InvalidRequest;
43
import org.dataone.service.exceptions.InvalidSystemMetadata;
44
import org.dataone.service.exceptions.InvalidToken;
45
import org.dataone.service.exceptions.NotAuthorized;
46
import org.dataone.service.exceptions.NotFound;
47
import org.dataone.service.exceptions.NotImplemented;
48
import org.dataone.service.exceptions.ServiceFailure;
49 6185 leinfelder
import org.dataone.service.exceptions.SynchronizationFailed;
50 6179 cjones
import org.dataone.service.exceptions.UnsupportedType;
51 6366 leinfelder
import org.dataone.service.mn.tier1.v1.MNCore;
52
import org.dataone.service.mn.tier1.v1.MNRead;
53
import org.dataone.service.mn.tier2.v1.MNAuthorization;
54
import org.dataone.service.mn.tier3.v1.MNStorage;
55
import org.dataone.service.mn.tier4.v1.MNReplication;
56
import org.dataone.service.types.v1.Checksum;
57
import org.dataone.service.types.v1.ChecksumAlgorithm;
58
import org.dataone.service.types.v1.DescribeResponse;
59
import org.dataone.service.types.v1.Event;
60
import org.dataone.service.types.v1.Group;
61
import org.dataone.service.types.v1.Identifier;
62
import org.dataone.service.types.v1.Log;
63
import org.dataone.service.types.v1.LogEntry;
64
import org.dataone.service.types.v1.MonitorInfo;
65
import org.dataone.service.types.v1.MonitorList;
66
import org.dataone.service.types.v1.Node;
67
import org.dataone.service.types.v1.NodeHealth;
68
import org.dataone.service.types.v1.NodeReference;
69
import org.dataone.service.types.v1.NodeState;
70
import org.dataone.service.types.v1.NodeType;
71
import org.dataone.service.types.v1.ObjectFormatIdentifier;
72
import org.dataone.service.types.v1.ObjectList;
73
import org.dataone.service.types.v1.Permission;
74
import org.dataone.service.types.v1.Ping;
75
import org.dataone.service.types.v1.Schedule;
76
import org.dataone.service.types.v1.Service;
77
import org.dataone.service.types.v1.Services;
78
import org.dataone.service.types.v1.Session;
79
import org.dataone.service.types.v1.Status;
80
import org.dataone.service.types.v1.Subject;
81
import org.dataone.service.types.v1.Synchronization;
82
import org.dataone.service.types.v1.SystemMetadata;
83
import org.dataone.service.types.v1.util.ChecksumUtil;
84 6179 cjones
85 6250 cjones
import edu.ucsb.nceas.metacat.DocumentImpl;
86 6234 cjones
import edu.ucsb.nceas.metacat.EventLog;
87 6230 cjones
import edu.ucsb.nceas.metacat.IdentifierManager;
88 6234 cjones
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
89 6389 leinfelder
import edu.ucsb.nceas.metacat.MetacatHandler;
90 6250 cjones
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
91 6260 cjones
import edu.ucsb.nceas.metacat.database.DBConnection;
92
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
93 6340 cjones
import edu.ucsb.nceas.metacat.properties.PropertyService;
94
import edu.ucsb.nceas.metacat.util.SystemUtil;
95
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
96 6230 cjones
97 6179 cjones
/**
98
 * Represents Metacat's implementation of the DataONE Member Node
99
 * service API. Methods implement the various MN* interfaces, and methods common
100
 * to both Member Node and Coordinating Node interfaces are found in the
101
 * D1NodeService base class.
102 6288 cjones
 *
103
 * Implements:
104
 * MNCore.ping()
105
 * MNCore.getLogRecords()
106
 * MNCore.getObjectStatistics()
107
 * MNCore.getOperationStatistics()
108
 * MNCore.getStatus()
109
 * MNCore.getCapabilities()
110
 * MNRead.get()
111
 * MNRead.getSystemMetadata()
112
 * MNRead.describe()
113
 * MNRead.getChecksum()
114
 * MNRead.listObjects()
115
 * MNRead.synchronizationFailed()
116
 * MNAuthorization.isAuthorized()
117
 * MNAuthorization.setAccessPolicy()
118
 * MNStorage.create()
119
 * MNStorage.update()
120
 * MNStorage.delete()
121
 * MNReplication.replicate()
122
 *
123 6179 cjones
 */
124
public class MNodeService extends D1NodeService implements MNAuthorization,
125
  MNCore, MNRead, MNReplication, MNStorage {
126
127 6259 cjones
  /* the instance of the MNodeService object */
128 6179 cjones
  private static MNodeService instance = null;
129
130
  /* the logger instance */
131
  private Logger logMetacat = null;
132
133
  /**
134 6241 cjones
   * Singleton accessor to get an instance of MNodeService.
135
   *
136
   * @return instance - the instance of MNodeService
137 6179 cjones
   */
138 6241 cjones
  public static MNodeService getInstance() {
139 6179 cjones
    if (instance == null) {
140 6241 cjones
141 6254 cjones
      instance = new MNodeService();
142 6241 cjones
143 6179 cjones
    }
144
145
    return instance;
146
  }
147
148
  /**
149
   * Constructor, private for singleton access
150
   */
151 6254 cjones
  private MNodeService() {
152 6259 cjones
    super();
153 6179 cjones
    logMetacat = Logger.getLogger(MNodeService.class);
154
155
  }
156
157 6259 cjones
  /**
158
   * Deletes an object from the Member Node, where the object is either a
159
   * data object or a science metadata object.
160
   *
161
   * @param session - the Session object containing the credentials for the Subject
162
   * @param pid - The object identifier to be deleted
163
   *
164
   * @return pid - the identifier of the object used for the deletion
165
   *
166
   * @throws InvalidToken
167
   * @throws ServiceFailure
168
   * @throws NotAuthorized
169
   * @throws NotFound
170
   * @throws NotImplemented
171
   * @throws InvalidRequest
172
   */
173 6179 cjones
  @Override
174 6259 cjones
  public Identifier delete(Session session, Identifier pid)
175 6179 cjones
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
176
    NotImplemented, InvalidRequest {
177
178 6250 cjones
    String localId = null;
179
    boolean allowed = false;
180 6334 leinfelder
    String username = Constants.PUBLIC_SUBJECT;
181
    String[] groupnames = null;
182
    if (session != null ) {
183
    	username = session.getSubject().getValue();
184
    	if (session.getSubjectList() != null) {
185
    		List<Group> groupList = session.getSubjectList().getGroupList();
186
    		if (groupList != null) {
187
    			groupnames = new String[groupList.size()];
188
    			for (int i = 0; i > groupList.size(); i++ ) {
189
    				groupnames[i] = groupList.get(i).getGroupName();
190
    			}
191
    		}
192
    	}
193 6310 cjones
    }
194 6250 cjones
195
    // do we have a valid pid?
196
    if ( pid == null || pid.getValue().trim().equals("") ) {
197
      throw new InvalidRequest("1322", "The provided identifier was invalid.");
198
    }
199
200
    // check for the existing identifier
201
    try {
202 6334 leinfelder
      localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
203 6250 cjones
204
    } catch (McdbDocNotFoundException e) {
205
      throw new InvalidRequest("1322", "The object with the provided " +
206 6259 cjones
        "identifier was not found.");
207 6250 cjones
208
    }
209
210
    // does the subject have DELETE (a D1 CHANGE_PERMISSION level) priveleges on the pid?
211
    allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
212
213
    if ( allowed ) {
214
      try {
215
        // delete the document
216 6334 leinfelder
        DocumentImpl.delete(localId, username, groupnames, null);
217 6373 leinfelder
        EventLog.getInstance().log(metacatUrl, username, localId, Event.DELETE.xmlValue());
218 6254 cjones
219 6250 cjones
      } catch (McdbDocNotFoundException e) {
220
        throw new InvalidRequest("1322", "The provided identifier was invalid.");
221
222
      } catch (SQLException e) {
223
        throw new ServiceFailure("1350", "There was a problem deleting the object." +
224 6259 cjones
          "The error message was: " + e.getMessage());
225 6250 cjones
226
      } catch (InsufficientKarmaException e) {
227
        throw new NotAuthorized("1320", "The provided identity does not have " +
228
        "permission to DELETE objects on the Member Node.");
229
230
      } catch (Exception e) { // for some reason DocumentImpl throws a general Exception
231
        throw new ServiceFailure("1350", "There was a problem deleting the object." +
232 6259 cjones
            "The error message was: " + e.getMessage());
233 6250 cjones
234
      }
235
236
    } else {
237 6251 cjones
      throw new NotAuthorized("1320", "The provided identity does not have " +
238
      "permission to DELETE objects on the Member Node.");
239 6259 cjones
240 6250 cjones
    }
241
242 6259 cjones
    return pid;
243
  }
244 6179 cjones
245 6251 cjones
246 6179 cjones
  /**
247
   * Updates an existing object by creating a new object identified by
248
   * newPid on the Member Node which explicitly obsoletes the object
249
   * identified by pid through appropriate changes to the SystemMetadata
250
   * of pid and newPid
251
   *
252 6259 cjones
   * @param session - the Session object containing the credentials for the Subject
253
   * @param pid - The identifier of the object to be updated
254
   * @param object - the new object bytes
255
   * @param sysmeta - the new system metadata describing the object
256
   *
257
   * @return newPid - the identifier of the new object
258
   *
259
   * @throws InvalidToken
260
   * @throws ServiceFailure
261
   * @throws NotAuthorized
262
   * @throws NotFound
263
   * @throws NotImplemented
264
   * @throws IdentifierNotUnique
265
   * @throws UnsupportedType
266
   * @throws InsufficientResources
267
   * @throws InvalidSystemMetadata
268
   * @throws InvalidRequest
269 6179 cjones
   */
270
  @Override
271 6259 cjones
  public Identifier update(Session session, Identifier pid, InputStream object,
272
    Identifier newPid, SystemMetadata sysmeta)
273 6179 cjones
    throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique,
274
    UnsupportedType, InsufficientResources, NotFound, InvalidSystemMetadata,
275
    NotImplemented, InvalidRequest {
276 6341 leinfelder
277
	// check if the pid has been reserved
278
	try {
279
		boolean hasReservation = D1Client.getCN().hasReservation(session, pid);
280
		if (!hasReservation) {
281
			throw new NotAuthorized("", "No reservation for pid: " + pid.getValue());
282
		}
283
	} catch (NotFound e) {
284
		// okay to continue
285
	}
286 6179 cjones
287 6259 cjones
    String localId = null;
288
    boolean allowed = false;
289
    boolean isScienceMetadata = false;
290
    Subject subject = session.getSubject();
291 6334 leinfelder
292 6259 cjones
    // do we have a valid pid?
293
    if ( pid == null || pid.getValue().trim().equals("") ) {
294
      throw new InvalidRequest("1202", "The provided identifier was invalid.");
295 6258 cjones
296 6259 cjones
    }
297 6258 cjones
298 6259 cjones
    // check for the existing identifier
299
    try {
300 6334 leinfelder
      localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
301 6258 cjones
302 6259 cjones
    } catch (McdbDocNotFoundException e) {
303
      throw new InvalidRequest("1202", "The object with the provided " +
304
        "identifier was not found.");
305 6258 cjones
306 6259 cjones
    }
307 6258 cjones
308 6259 cjones
    // does the subject have WRITE ( == update) priveleges on the pid?
309
    allowed = isAuthorized(session, pid, Permission.WRITE);
310 6258 cjones
311 6259 cjones
    if ( allowed ) {
312
313
      // get the existing system metadata for the object
314
      SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
315
316 6354 cjones
      // add the newPid to the obsoletedBy list for the existing sysmeta
317 6366 leinfelder
      existingSysMeta.setObsoletedBy(newPid);
318 6259 cjones
319
      // then update the existing system metadata
320
      updateSystemMetadata(existingSysMeta);
321 6354 cjones
322
      // prep the new system metadata, add pid to the affected lists
323 6366 leinfelder
      sysmeta.setObsoletes(pid);
324
      //sysmeta.addDerivedFrom(pid);
325 6259 cjones
326
      isScienceMetadata = isScienceMetadata(sysmeta);
327
328
      // do we have XML metadata or a data object?
329
      if ( isScienceMetadata ) {
330
331
        // update the science metadata XML document
332
        // TODO: handle non-XML metadata/data documents (like netCDF)
333
        // TODO: don't put objects into memory using stream to string
334
        String objectAsXML = "";
335
        try {
336
          objectAsXML = IOUtils.toString(object, "UTF-8");
337
          localId = insertOrUpdateDocument(objectAsXML, newPid, session, "update");
338
          // register the newPid and the generated localId
339
          if ( newPid != null ) {
340 6334 leinfelder
        	  IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
341 6259 cjones
342
          }
343
344
        } catch (IOException e) {
345
          String msg = "The Node is unable to create the object. " +
346 6258 cjones
          "There was a problem converting the object to XML";
347 6259 cjones
          logMetacat.info(msg);
348 6258 cjones
          throw new ServiceFailure("1310", msg + ": " + e.getMessage());
349 6259 cjones
350
        }
351
352
      } else {
353
354
        // update the data object
355
        localId = insertDataObject(object, newPid, session);
356
357
      }
358 6382 cjones
359
      // and insert the new system metadata
360
      insertSystemMetadata(sysmeta);
361
362 6259 cjones
      // log the update event
363 6258 cjones
      EventLog.getInstance().log(metacatUrl, subject.getValue(), localId, "update");
364
365 6259 cjones
    } else {
366
      throw new NotAuthorized("1200", "The provided identity does not have " +
367
      "permission to UPDATE the object identified by " +
368
      pid.getValue() + " on the Member Node.");
369
370
    }
371 6254 cjones
372 6354 cjones
    return newPid;
373 6259 cjones
  }
374 6341 leinfelder
375
  public Identifier create(Session session, Identifier pid,
376
			InputStream object, SystemMetadata sysmeta) throws InvalidToken,
377
			ServiceFailure, NotAuthorized, IdentifierNotUnique,
378
			UnsupportedType, InsufficientResources, InvalidSystemMetadata,
379
			NotImplemented, InvalidRequest {
380 6179 cjones
381 6341 leinfelder
		// check if the pid has been reserved
382
		try {
383
			boolean hasReservation = D1Client.getCN().hasReservation(session, pid);
384
			if (!hasReservation) {
385
				throw new NotAuthorized("", "No reservation for pid: " + pid.getValue());
386
			}
387
		} catch (NotFound e) {
388
			// okay to continue
389
		}
390
391
		return super.create(session, pid, object, sysmeta);
392
	}
393
394 6179 cjones
  /**
395
   * Called by a Coordinating Node to request that the Member Node create a
396
   * copy of the specified object by retrieving it from another Member
397
   * Node and storing it locally so that it can be made accessible to
398
   * the DataONE system.
399
   *
400 6259 cjones
   * @param session - the Session object containing the credentials for the Subject
401
   * @param sysmeta - Copy of the CN held system metadata for the object
402
   * @param sourceNode - A reference to node from which the content should be
403
   *                     retrieved. The reference should be resolved by
404
   *                     checking the CN node registry.
405
   *
406
   * @return true if the replication succeeds
407
   *
408
   * @throws ServiceFailure
409
   * @throws NotAuthorized
410
   * @throws NotImplemented
411
   * @throws UnsupportedType
412
   * @throws InsufficientResources
413
   * @throws InvalidRequest
414 6179 cjones
   */
415
  @Override
416 6259 cjones
  public boolean replicate(Session session, SystemMetadata sysmeta,
417
    NodeReference sourceNode)
418
    throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
419
    InsufficientResources, UnsupportedType {
420 6179 cjones
421 6332 leinfelder
    boolean result = false;
422
423
    // TODO: check credentials
424
425
    // get the referenced object
426
    Identifier pid = sysmeta.getIdentifier();
427
428
    // get from the membernode
429
    // TODO: switch credentials for the server retrieval?
430
    MNode mn = D1Client.getMN(sourceNode);
431
    InputStream object = null;
432
433
	try {
434
		object = mn.get(session, pid);
435
	} catch (InvalidToken e) {
436
		e.printStackTrace();
437
		throw new ServiceFailure("2151", "Could not retrieve object to replicate (InvalidToken): " + e.getMessage());
438
	} catch (NotFound e) {
439
		e.printStackTrace();
440
		throw new ServiceFailure("2151", "Could not retrieve object to replicate (NotFound): " + e.getMessage());
441
	}
442
443
    // add it to local store
444
	Identifier retPid;
445
	try {
446
		retPid = create(session, pid, object, sysmeta);
447
		result = (retPid.getValue().equals(pid.getValue()));
448
	} catch (InvalidToken e) {
449
		e.printStackTrace();
450
		throw new ServiceFailure("2151", "Could not save object to local store (InvalidToken): " + e.getMessage());
451
	} catch (IdentifierNotUnique e) {
452
		e.printStackTrace();
453
		throw new ServiceFailure("2151", "Could not save object to local store (IdentifierNotUnique): " + e.getMessage());
454
	} catch (InvalidSystemMetadata e) {
455
		e.printStackTrace();
456
		throw new ServiceFailure("2151", "Could not save object to local store (InvalidSystemMetadata): " + e.getMessage());
457
	}
458
459
    return result;
460
461 6259 cjones
  }
462 6179 cjones
463
  /**
464
   * This method provides a lighter weight mechanism than
465
   * MN_read.getSystemMetadata() for a client to determine basic
466
   * properties of the referenced object.
467
   *
468 6259 cjones
   * @param session - the Session object containing the credentials for the Subject
469
   * @param pid - the identifier of the object to be described
470
   *
471
   * @return describeResponse - A set of values providing a basic description
472
   *                            of the object.
473
   *
474
   * @throws InvalidToken
475
   * @throws ServiceFailure
476
   * @throws NotAuthorized
477
   * @throws NotFound
478
   * @throws NotImplemented
479
   * @throws InvalidRequest
480 6179 cjones
   */
481
  @Override
482 6259 cjones
  public DescribeResponse describe(Session session, Identifier pid)
483
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
484
    NotImplemented, InvalidRequest {
485 6229 cjones
486 6259 cjones
    if(session == null) {
487 6229 cjones
      throw new InvalidToken("1370", "The session object is null");
488
489
    }
490
491
    if(pid == null || pid.getValue().trim().equals(""))
492
    {
493
      throw new InvalidRequest("1362", "The object identifier is null. " +
494
        "A valid identifier is required.");
495
496
    }
497
498
    SystemMetadata sysmeta = getSystemMetadata(session, pid);
499
    DescribeResponse describeResponse =
500 6384 cjones
      new DescribeResponse(sysmeta.getFmtid(),
501 6229 cjones
      sysmeta.getSize(), sysmeta.getDateSysMetadataModified(), sysmeta.getChecksum());
502
503
    return describeResponse;
504 6179 cjones
505 6259 cjones
  }
506 6179 cjones
507 6259 cjones
  /**
508
   * Return the object identified by the given object identifier
509
   *
510
   * @param session - the Session object containing the credentials for the Subject
511
   * @param pid - the object identifier for the given object
512
   *
513
   * @return inputStream - the input stream of the given object
514
   *
515
   * @throws InvalidToken
516
   * @throws ServiceFailure
517
   * @throws NotAuthorized
518
   * @throws InvalidRequest
519
   * @throws NotImplemented
520
   */
521 6179 cjones
  @Override
522 6259 cjones
  public InputStream get(Session session, Identifier pid)
523
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
524
    NotImplemented, InvalidRequest {
525
526
    return super.get(session, pid);
527
528
  }
529 6179 cjones
530 6259 cjones
  /**
531
   * Returns a Checksum for the specified object using an accepted hashing algorithm
532
   *
533
   * @param session - the Session object containing the credentials for the Subject
534
   * @param pid - the object identifier for the given object
535
   * @param algorithm -  the name of an algorithm that will be used to compute
536
   *                     a checksum of the bytes of the object
537
   *
538
   * @return checksum - the checksum of the given object
539
   *
540
   * @throws InvalidToken
541
   * @throws ServiceFailure
542
   * @throws NotAuthorized
543
   * @throws NotFound
544
   * @throws InvalidRequest
545
   * @throws NotImplemented
546
   */
547 6179 cjones
  @Override
548 6259 cjones
  public Checksum getChecksum(Session session, Identifier pid, String algorithm)
549
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
550
    InvalidRequest, NotImplemented {
551 6179 cjones
552 6259 cjones
    Checksum checksum = null;
553 6228 cjones
554 6259 cjones
    InputStream inputStream = get(session, pid);
555
556
    try {
557
      checksum =
558 6366 leinfelder
    	  ChecksumUtil.checksum(inputStream, ChecksumAlgorithm.convert(algorithm));
559 6259 cjones
560
    } catch (NoSuchAlgorithmException e) {
561 6228 cjones
      throw new ServiceFailure("1410", "The checksum for the object specified by " +
562
        pid.getValue() +
563
        "could not be returned due to an internal error: " +
564
        e.getMessage());
565
566
    } catch (IOException e) {
567
      throw new ServiceFailure("1410", "The checksum for the object specified by " +
568
        pid.getValue() +
569
        "could not be returned due to an internal error: " +
570
        e.getMessage());
571
572
    }
573 6259 cjones
574 6228 cjones
    if ( checksum == null ) {
575
      throw new ServiceFailure("1410", "The checksum for the object specified by " +
576
        pid.getValue() +
577
        "could not be returned.");
578
579
    }
580
581 6259 cjones
    return checksum;
582
  }
583 6179 cjones
584 6259 cjones
  /**
585
   * Return the system metadata for a given object
586
   *
587
   * @param session - the Session object containing the credentials for the Subject
588
   * @param pid - the object identifier for the given object
589
   *
590
   * @return inputStream - the input stream of the given system metadata object
591
   *
592
   * @throws InvalidToken
593
   * @throws ServiceFailure
594
   * @throws NotAuthorized
595
   * @throws NotFound
596
   * @throws InvalidRequest
597
   * @throws NotImplemented
598
   */
599 6179 cjones
  @Override
600 6259 cjones
  public SystemMetadata getSystemMetadata(Session session, Identifier pid)
601
      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
602
      InvalidRequest, NotImplemented {
603 6179 cjones
604 6259 cjones
    return super.getSystemMetadata(session, pid);
605
  }
606 6179 cjones
607 6259 cjones
  /**
608
   * Retrieve the list of objects present on the MN that match the calling parameters
609
   *
610
   * @param session - the Session object containing the credentials for the Subject
611
   * @param startTime - Specifies the beginning of the time range from which
612
   *                    to return object (>=)
613
   * @param endTime - Specifies the beginning of the time range from which
614
   *                  to return object (>=)
615
   * @param objectFormat - Restrict results to the specified object format
616
   * @param replicaStatus - Indicates if replicated objects should be returned in the list
617
   * @param start - The zero-based index of the first value, relative to the
618
   *                first record of the resultset that matches the parameters.
619
   * @param count - The maximum number of entries that should be returned in
620
   *                the response. The Member Node may return less entries
621
   *                than specified in this value.
622
   *
623
   * @return objectList - the list of objects matching the criteria
624
   *
625
   * @throws InvalidToken
626
   * @throws ServiceFailure
627
   * @throws NotAuthorized
628
   * @throws InvalidRequest
629
   * @throws NotImplemented
630
   */
631 6179 cjones
  @Override
632 6259 cjones
  public ObjectList listObjects(Session session, Date startTime, Date endTime,
633 6337 leinfelder
    ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start, Integer count)
634 6259 cjones
    throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure,
635
    InvalidToken {
636 6179 cjones
637 6259 cjones
    ObjectList objectList = null;
638
639 6300 leinfelder
    try {
640
	    objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime,
641 6337 leinfelder
	        objectFormatId, replicaStatus, start, count);
642 6300 leinfelder
    } catch (Exception e) {
643
		throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
644
	}
645 6259 cjones
646
    return objectList;
647
  }
648 6179 cjones
649 6259 cjones
  /**
650
   * Retrieve the list of objects present on the MN that match the calling parameters
651
   *
652
   * @return node - the technical capabilities of the Member Node
653
   *
654
   * @throws ServiceFailure
655
   * @throws NotAuthorized
656
   * @throws InvalidRequest
657
   * @throws NotImplemented
658
   */
659 6179 cjones
  @Override
660 6259 cjones
  public Node getCapabilities() throws NotImplemented, NotAuthorized,
661
      ServiceFailure, InvalidRequest {
662 6340 cjones
663
  	String nodeName = null;
664
    String nodeId = null;
665
    String nodeUrl = null;
666
    String nodeDesc = null;
667
    String nodeType = null;
668 6343 cjones
    String mnCoreServiceVersion = null;
669
    String mnReadServiceVersion = null;
670
    String mnAuthorizationServiceVersion = null;
671
    String mnStorageServiceVersion = null;
672
    String mnReplicationServiceVersion = null;
673 6179 cjones
674 6347 cjones
    boolean nodeSynchronize = false;
675
    boolean nodeReplicate = false;
676 6345 cjones
    boolean mnCoreServiceAvailable = false;
677
    boolean mnReadServiceAvailable = false;
678
    boolean mnAuthorizationServiceAvailable = false;
679
    boolean mnStorageServiceAvailable = false;
680
    boolean mnReplicationServiceAvailable = false;
681
682 6340 cjones
    try
683
    {
684 6347 cjones
    	// get the properties of the node based on configuration information
685 6345 cjones
      nodeId = PropertyService.getProperty("dataone.memberNodeId");
686
      nodeName = PropertyService.getProperty("dataone.nodeName");
687
      nodeUrl = SystemUtil.getContextURL() + "/d1/";
688
      nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
689
      nodeType = PropertyService.getProperty("dataone.nodeType");
690 6347 cjones
      nodeSynchronize =
691
      	new Boolean(PropertyService.getProperty(
692
      		"dataone.nodeSynchronize")).booleanValue();
693
      nodeReplicate =
694
      	new Boolean(PropertyService.getProperty(
695
      		"dataone.nodeReplicate")).booleanValue();
696
697 6345 cjones
      mnCoreServiceVersion =
698 6351 cjones
      	PropertyService.getProperty("dataone.mnCore.serviceVersion");
699 6345 cjones
      mnReadServiceVersion =
700 6351 cjones
      	PropertyService.getProperty("dataone.mnRead.serviceVersion");
701 6345 cjones
      mnAuthorizationServiceVersion =
702 6351 cjones
      	PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
703 6345 cjones
      mnStorageServiceVersion =
704 6351 cjones
      	PropertyService.getProperty("dataone.mnStorage.serviceVersion");
705 6345 cjones
      mnReplicationServiceVersion =
706 6351 cjones
      	PropertyService.getProperty("dataone.mnReplication.serviceVersion");
707 6345 cjones
708
      mnCoreServiceAvailable = new Boolean(
709 6351 cjones
      	PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
710 6345 cjones
      mnReadServiceAvailable =  new Boolean(
711
      	PropertyService.getProperty(
712 6351 cjones
      		"dataone.mnRead.serviceAvailable")).booleanValue();
713 6345 cjones
      mnAuthorizationServiceAvailable =  new Boolean(
714
      	PropertyService.getProperty(
715 6351 cjones
      		"dataone.mnAuthorization.serviceAvailable")).booleanValue();
716 6345 cjones
      mnStorageServiceAvailable =  new Boolean(
717
      	PropertyService.getProperty(
718 6351 cjones
      	  "dataone.mnStorage.serviceAvailable")).booleanValue();
719 6345 cjones
      mnReplicationServiceAvailable =  new Boolean(
720
      	PropertyService.getProperty(
721 6351 cjones
      	  "dataone.mnReplication.serviceAvailable")).booleanValue();
722 6345 cjones
723 6340 cjones
    } catch(PropertyNotFoundException pnfe) {
724
        logMetacat.error("MNodeService.getCapabilities(): " +
725
          "property not found: " + pnfe.getMessage());
726
727
    }
728
729 6351 cjones
  	// Set the properties of the node based on configuration information and
730
    // calls to current status methods
731 6330 leinfelder
	  Node node = new Node();
732 6340 cjones
	  node.setBaseURL(metacatUrl + "/" + nodeType);
733
	  node.setDescription(nodeDesc);
734 6351 cjones
735
	  // set the node's health information
736 6330 leinfelder
	  NodeHealth health = new NodeHealth();
737 6351 cjones
	  NodeState state = NodeState.UP;
738
	  health.setState(state);
739
	  // set the ping response to the current value
740
	  Ping canPing = new Ping();
741
	  canPing.setSuccess(false);
742
	  try {
743
	    canPing.setSuccess(ping());
744
    } catch (InsufficientResources e) {
745
	    e.printStackTrace();
746
747
    } catch (UnsupportedType e) {
748
	    e.printStackTrace();
749
750
    }
751
	  health.setPing(canPing);
752
	  // TODO: getStatus() should be consulted here when it's implemented
753
	  Status nodeStatus = new Status();
754
	  nodeStatus.setSuccess(true);
755
	  nodeStatus.setDateChecked(new Date());
756
	  health.setStatus(nodeStatus);
757 6340 cjones
	  node.setHealth(health);
758 6351 cjones
759 6330 leinfelder
	  NodeReference identifier = new NodeReference();
760 6340 cjones
	  identifier.setValue(nodeId);
761 6330 leinfelder
	  node.setIdentifier(identifier);
762 6340 cjones
	  node.setName(nodeName + " -- WAR version WARVERSION");
763 6347 cjones
	  node.setReplicate(new Boolean(nodeReplicate).booleanValue());
764
	  node.setSynchronize(new Boolean(nodeSynchronize).booleanValue());
765
766 6330 leinfelder
	  // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
767
	  Services services = new Services();
768 6351 cjones
769
	  Service sMNCore = new Service();
770
	  sMNCore.setName("MNCore");
771
	  sMNCore.setVersion(mnCoreServiceVersion);
772
	  sMNCore.setAvailable(mnCoreServiceAvailable);
773
774 6330 leinfelder
	  Service sMNRead = new Service();
775
	  sMNRead.setName("MNRead");
776 6343 cjones
	  sMNRead.setVersion(mnReadServiceVersion);
777 6345 cjones
	  sMNRead.setAvailable(mnReadServiceAvailable);
778 6347 cjones
779 6330 leinfelder
	  Service sMNAuthorization = new Service();
780
	  sMNAuthorization.setName("MNAuthorization");
781 6343 cjones
	  sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
782 6345 cjones
	  sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
783 6347 cjones
784 6330 leinfelder
	  Service sMNStorage = new Service();
785
	  sMNStorage.setName("MNStorage");
786 6343 cjones
	  sMNStorage.setVersion(mnStorageServiceVersion);
787 6345 cjones
	  sMNStorage.setAvailable(mnStorageServiceAvailable);
788 6347 cjones
789 6330 leinfelder
	  Service sMNReplication = new Service();
790
	  sMNReplication.setName("MNReplication");
791 6343 cjones
	  sMNReplication.setVersion(mnReplicationServiceVersion);
792 6345 cjones
	  sMNReplication.setAvailable(mnReplicationServiceAvailable);
793 6347 cjones
794 6330 leinfelder
	  services.addService(sMNRead);
795
	  services.addService(sMNCore);
796
	  services.addService(sMNAuthorization);
797
	  services.addService(sMNStorage);
798
	  services.addService(sMNReplication);
799 6340 cjones
	  node.setServices(services);
800 6347 cjones
801 6351 cjones
	  // TODO: Determine the synchronization info without mock values
802 6330 leinfelder
	  Synchronization synchronization = new Synchronization();
803 6351 cjones
	  Schedule schedule = new Schedule();
804
	  Date now = new Date();
805
	  schedule.setYear(new SimpleDateFormat("yyyy").format(now));
806
	  schedule.setMon(new SimpleDateFormat("MM").format(now));
807
	  schedule.setMday(new SimpleDateFormat("dd").format(now));
808
	  schedule.setWday(new SimpleDateFormat("dd").format(now));
809
	  schedule.setHour(new SimpleDateFormat("HH").format(now));
810
	  schedule.setMin(new SimpleDateFormat("mm").format(now));
811
	  schedule.setSec(new SimpleDateFormat("ss").format(now));
812
	  synchronization.setSchedule(schedule);
813
	  synchronization.setLastHarvested(now);
814
	  synchronization.setLastCompleteHarvest(now);
815 6340 cjones
	  node.setSynchronization(synchronization);
816 6330 leinfelder
	  node.setSynchronize(false);
817
	  node.setType(NodeType.MN);
818
819
	  return node;
820 6259 cjones
  }
821 6179 cjones
822 6259 cjones
  /**
823
   * Returns the number of operations that have been serviced by the node
824
   * over time periods of one and 24 hours.
825
   *
826
   * @param session - the Session object containing the credentials for the Subject
827
   * @param period - An ISO8601 compatible DateTime range specifying the time
828
   *                 range for which to return operation statistics.
829
   * @param requestor - Limit to operations performed by given requestor identity.
830
   * @param event -  Enumerated value indicating the type of event being examined
831
   * @param format - Limit to events involving objects of the specified format
832
   *
833
   * @return the desired log records
834
   *
835
   * @throws InvalidToken
836
   * @throws ServiceFailure
837
   * @throws NotAuthorized
838
   * @throws InvalidRequest
839
   * @throws NotImplemented
840
   */
841 6179 cjones
  @Override
842 6298 cjones
  public MonitorList getOperationStatistics(Session session, Date startTime,
843
  		Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
844 6179 cjones
    throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
845
    InsufficientResources, UnsupportedType {
846 6298 cjones
847 6331 leinfelder
	  MonitorList monitorList = new MonitorList();
848
849
	  try {
850
851
		  // get log records first
852
		  Log logs = getLogRecords(session, startTime, endTime, event, 0, null);
853
854
		  // TODO: aggregate by day or hour -- needs clarification
855
		  int count = 1;
856
		  for (LogEntry logEntry: logs.getLogEntryList()) {
857
			  Identifier pid = logEntry.getIdentifier();
858
			  Date logDate = logEntry.getDateLogged();
859
			  // if we are filtering by format
860
			  if (formatId != null) {
861
				  SystemMetadata sysmeta = IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
862 6384 cjones
				  if (!sysmeta.getFmtid().getValue().equals(formatId.getValue())) {
863 6331 leinfelder
					  // does not match
864
					  continue;
865
				  }
866
			  }
867
			  MonitorInfo item = new MonitorInfo();
868
			  item.setCount(count);
869
			  item.setDate(new java.sql.Date(logDate.getTime()));
870
			  monitorList.addMonitorInfo(item);
871
872
		  }
873
	} catch (Exception e) {
874
		e.printStackTrace();
875
		throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
876
	}
877
878
	return monitorList;
879
880 6259 cjones
  }
881 6179 cjones
882
  /**
883
   * Low level “are you alive” operation. A valid ping response is
884
   * indicated by a HTTP status of 200.
885
   *
886
   * @return true if the service is alive
887
   *
888 6259 cjones
   * @throws InvalidToken
889
   * @throws ServiceFailure
890
   * @throws NotAuthorized
891
   * @throws InvalidRequest
892
   * @throws NotImplemented
893 6179 cjones
   */
894 6259 cjones
  @Override
895
  public boolean ping()
896
    throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
897
    InsufficientResources, UnsupportedType {
898 6179 cjones
899 6260 cjones
    // test if we can get a database connection
900
    boolean alive = false;
901
    int serialNumber = -1;
902
    DBConnection dbConn = null;
903
    try {
904
      dbConn = DBConnectionPool
905
      .getDBConnection("MNodeService.ping");
906
      serialNumber = dbConn.getCheckOutSerialNumber();
907
      alive = true;
908
909
    } catch (SQLException e) {
910
      return alive;
911
912
    } finally {
913
      // Return the database connection
914
      DBConnectionPool.returnDBConnection(dbConn, serialNumber);
915
916
    }
917
918
    return alive;
919 6259 cjones
  }
920 6179 cjones
921 6213 cjones
  /**
922
   * A callback method used by a CN to indicate to a MN that it cannot
923 6234 cjones
   * complete synchronization of the science metadata identified by pid.  Log
924
   * the event in the metacat event log.
925 6213 cjones
   *
926
   * @param session
927
   * @param syncFailed
928 6259 cjones
   *
929
   * @throws ServiceFailure
930
   * @throws NotAuthorized
931
   * @throws InvalidRequest
932
   * @throws NotImplemented
933 6213 cjones
   */
934 6259 cjones
  @Override
935 6213 cjones
  public void synchronizationFailed(Session session, SynchronizationFailed syncFailed)
936
      throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest {
937 6179 cjones
938 6259 cjones
    String localId;
939
940
    try {
941 6366 leinfelder
      localId = IdentifierManager.getInstance().getLocalId(syncFailed.getPid());
942 6234 cjones
    } catch (McdbDocNotFoundException e) {
943
      throw new ServiceFailure("2161", "The identifier specified by " +
944 6366 leinfelder
          syncFailed.getPid() +
945 6259 cjones
          " was not found on this node.");
946 6234 cjones
947
    }
948 6259 cjones
    // TODO: update the CN URL below when the CNRead.SynchronizationFailed
949 6234 cjones
    // method is changed to include the URL as a parameter
950
    logMetacat.debug("Synchronization for the object identified by " +
951 6366 leinfelder
      syncFailed.getPid() +
952 6259 cjones
      " failed from " +
953 6376 cjones
      syncFailed.getNodeId() +
954 6259 cjones
      " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
955 6288 cjones
    // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
956 6376 cjones
    EventLog.getInstance().log(syncFailed.getNodeId(),
957 6288 cjones
      session.getSubject().getValue(), localId, "synchronization_failed");
958
    //EventLog.getInstance().log("CN URL WILL GO HERE",
959
    //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
960 6234 cjones
961 6213 cjones
  }
962 6185 leinfelder
963 6384 cjones
  /**
964 6389 leinfelder
   * Essentially a get() but with different logging behavior
965 6384 cjones
   */
966
	@Override
967
  public InputStream getReplica(Session session, Identifier pid)
968
    throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
969
    ServiceFailure, NotFound {
970 6389 leinfelder
971
		InputStream inputStream = null; // bytes to be returned
972
	    handler = new MetacatHandler(new Timer());
973
	    boolean allowed = false;
974
	    String localId; // the metacat docid for the pid
975
976
	    // get the local docid from Metacat
977
	    try {
978
	      localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
979
	    } catch (McdbDocNotFoundException e) {
980
	      throw new NotFound("1020", "The object specified by " +
981
	                         pid.getValue() +
982
	                         " does not exist at this node.");
983
	    }
984
985 6390 leinfelder
	    Node node = this.getCapabilities();
986
	    Subject targetNodeSubject = node.getSubject(0);
987 6389 leinfelder
988 6390 leinfelder
		// check for authorization to replicate
989 6391 leinfelder
	    allowed = D1Client.getCN().isReplicationAuthorized(session, targetNodeSubject , pid, Permission.REPLICATE);
990 6390 leinfelder
991 6389 leinfelder
	    // if the person is authorized, perform the read
992
	    if (allowed) {
993
	      try {
994
	        inputStream = handler.read(localId);
995
	      } catch (Exception e) {
996
	        throw new ServiceFailure("1020", "The object specified by " +
997
	            pid.getValue() +
998
	            "could not be returned due to error: " +
999
	            e.getMessage());
1000
	      }
1001
	    }
1002 6384 cjones
1003 6389 leinfelder
	    // if we fail to set the input stream
1004
	    if ( inputStream == null ) {
1005
	      throw new NotFound("1020", "The object specified by " +
1006
	                         pid.getValue() +
1007
	                         "does not exist at this node.");
1008
	    }
1009
1010
		// log the replica event
1011 6390 leinfelder
	    String principal = null;
1012 6389 leinfelder
	    if (session.getSubject() != null) {
1013
	    	principal = session.getSubject().getValue();
1014
	    }
1015
	    EventLog.getInstance().log(null, principal, localId, "getreplica");
1016
1017
	    return inputStream;
1018 6384 cjones
  }
1019
1020 6179 cjones
}