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