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 7417 leinfelder
import java.io.ByteArrayInputStream;
27 7860 leinfelder
import java.io.ByteArrayOutputStream;
28 7850 leinfelder
import java.io.File;
29
import java.io.FileOutputStream;
30 6228 cjones
import java.io.IOException;
31 6179 cjones
import java.io.InputStream;
32 8190 leinfelder
import java.io.InputStreamReader;
33 7860 leinfelder
import java.io.OutputStreamWriter;
34 7849 leinfelder
import java.io.UnsupportedEncodingException;
35 7860 leinfelder
import java.io.Writer;
36 7021 leinfelder
import java.math.BigInteger;
37 7849 leinfelder
import java.net.URISyntaxException;
38 6228 cjones
import java.security.NoSuchAlgorithmException;
39 7860 leinfelder
import java.sql.SQLException;
40 7417 leinfelder
import java.util.ArrayList;
41 6525 leinfelder
import java.util.Calendar;
42 6179 cjones
import java.util.Date;
43 7680 tao
import java.util.HashSet;
44 7860 leinfelder
import java.util.Hashtable;
45 6250 cjones
import java.util.List;
46 7849 leinfelder
import java.util.Map;
47 7417 leinfelder
import java.util.Set;
48 6389 leinfelder
import java.util.Timer;
49 7489 leinfelder
import java.util.UUID;
50 7418 leinfelder
import java.util.Vector;
51 6179 cjones
52 6542 leinfelder
import javax.servlet.http.HttpServletRequest;
53
54 8141 leinfelder
import org.apache.commons.beanutils.BeanUtils;
55 6258 cjones
import org.apache.commons.io.IOUtils;
56 6179 cjones
import org.apache.log4j.Logger;
57 6528 cjones
import org.dataone.client.CNode;
58 6332 leinfelder
import org.dataone.client.D1Client;
59
import org.dataone.client.MNode;
60 8013 leinfelder
import org.dataone.client.ObjectFormatCache;
61 6552 leinfelder
import org.dataone.client.auth.CertificateManager;
62 7850 leinfelder
import org.dataone.client.formats.ObjectFormatInfo;
63 6552 leinfelder
import org.dataone.configuration.Settings;
64 7849 leinfelder
import org.dataone.ore.ResourceMapFactory;
65 6795 cjones
import org.dataone.service.exceptions.BaseException;
66 6179 cjones
import org.dataone.service.exceptions.IdentifierNotUnique;
67
import org.dataone.service.exceptions.InsufficientResources;
68
import org.dataone.service.exceptions.InvalidRequest;
69
import org.dataone.service.exceptions.InvalidSystemMetadata;
70
import org.dataone.service.exceptions.InvalidToken;
71
import org.dataone.service.exceptions.NotAuthorized;
72
import org.dataone.service.exceptions.NotFound;
73
import org.dataone.service.exceptions.NotImplemented;
74
import org.dataone.service.exceptions.ServiceFailure;
75 6185 leinfelder
import org.dataone.service.exceptions.SynchronizationFailed;
76 6179 cjones
import org.dataone.service.exceptions.UnsupportedType;
77 6366 leinfelder
import org.dataone.service.mn.tier1.v1.MNCore;
78
import org.dataone.service.mn.tier1.v1.MNRead;
79
import org.dataone.service.mn.tier2.v1.MNAuthorization;
80
import org.dataone.service.mn.tier3.v1.MNStorage;
81
import org.dataone.service.mn.tier4.v1.MNReplication;
82 7417 leinfelder
import org.dataone.service.mn.v1.MNQuery;
83 6366 leinfelder
import org.dataone.service.types.v1.Checksum;
84 7144 leinfelder
import org.dataone.service.types.v1.DescribeResponse;
85 6366 leinfelder
import org.dataone.service.types.v1.Event;
86
import org.dataone.service.types.v1.Identifier;
87
import org.dataone.service.types.v1.Log;
88
import org.dataone.service.types.v1.LogEntry;
89
import org.dataone.service.types.v1.MonitorInfo;
90
import org.dataone.service.types.v1.MonitorList;
91
import org.dataone.service.types.v1.Node;
92 6600 cjones
import org.dataone.service.types.v1.NodeList;
93 6366 leinfelder
import org.dataone.service.types.v1.NodeReference;
94
import org.dataone.service.types.v1.NodeState;
95
import org.dataone.service.types.v1.NodeType;
96 7860 leinfelder
import org.dataone.service.types.v1.ObjectFormat;
97 6366 leinfelder
import org.dataone.service.types.v1.ObjectFormatIdentifier;
98
import org.dataone.service.types.v1.ObjectList;
99
import org.dataone.service.types.v1.Permission;
100
import org.dataone.service.types.v1.Ping;
101 6528 cjones
import org.dataone.service.types.v1.ReplicationStatus;
102 6366 leinfelder
import org.dataone.service.types.v1.Schedule;
103
import org.dataone.service.types.v1.Service;
104
import org.dataone.service.types.v1.Services;
105
import org.dataone.service.types.v1.Session;
106
import org.dataone.service.types.v1.Subject;
107
import org.dataone.service.types.v1.Synchronization;
108
import org.dataone.service.types.v1.SystemMetadata;
109 7417 leinfelder
import org.dataone.service.types.v1.util.AuthUtils;
110 6366 leinfelder
import org.dataone.service.types.v1.util.ChecksumUtil;
111 7417 leinfelder
import org.dataone.service.types.v1_1.QueryEngineDescription;
112
import org.dataone.service.types.v1_1.QueryEngineList;
113 7418 leinfelder
import org.dataone.service.types.v1_1.QueryField;
114 6476 jones
import org.dataone.service.util.Constants;
115 7849 leinfelder
import org.dspace.foresite.OREException;
116
import org.dspace.foresite.OREParserException;
117
import org.dspace.foresite.ORESerialiserException;
118
import org.dspace.foresite.ResourceMap;
119 6179 cjones
120 7448 leinfelder
import edu.ucsb.nceas.ezid.EZIDException;
121 7417 leinfelder
import edu.ucsb.nceas.metacat.DBQuery;
122 7860 leinfelder
import edu.ucsb.nceas.metacat.DBTransform;
123 6234 cjones
import edu.ucsb.nceas.metacat.EventLog;
124 6230 cjones
import edu.ucsb.nceas.metacat.IdentifierManager;
125 6234 cjones
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
126 7417 leinfelder
import edu.ucsb.nceas.metacat.MetaCatServlet;
127 6389 leinfelder
import edu.ucsb.nceas.metacat.MetacatHandler;
128 7772 tao
129
import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines;
130 7757 leinfelder
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
131 6648 leinfelder
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
132 7662 tao
import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler;
133 7620 tao
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
134 6340 cjones
import edu.ucsb.nceas.metacat.properties.PropertyService;
135 7418 leinfelder
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
136 8026 leinfelder
import edu.ucsb.nceas.metacat.util.DeleteOnCloseFileInputStream;
137 7441 leinfelder
import edu.ucsb.nceas.metacat.util.DocumentUtil;
138 6542 leinfelder
import edu.ucsb.nceas.metacat.util.SystemUtil;
139 6340 cjones
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
140 8190 leinfelder
import edu.ucsb.nceas.utilities.XMLUtilities;
141 7850 leinfelder
import gov.loc.repository.bagit.Bag;
142
import gov.loc.repository.bagit.BagFactory;
143
import gov.loc.repository.bagit.writer.impl.ZipWriter;
144 6230 cjones
145 6179 cjones
/**
146
 * Represents Metacat's implementation of the DataONE Member Node
147
 * service API. Methods implement the various MN* interfaces, and methods common
148
 * to both Member Node and Coordinating Node interfaces are found in the
149
 * D1NodeService base class.
150 6288 cjones
 *
151
 * Implements:
152
 * MNCore.ping()
153
 * MNCore.getLogRecords()
154
 * MNCore.getObjectStatistics()
155
 * MNCore.getOperationStatistics()
156
 * MNCore.getStatus()
157
 * MNCore.getCapabilities()
158
 * MNRead.get()
159
 * MNRead.getSystemMetadata()
160
 * MNRead.describe()
161
 * MNRead.getChecksum()
162
 * MNRead.listObjects()
163
 * MNRead.synchronizationFailed()
164
 * MNAuthorization.isAuthorized()
165
 * MNAuthorization.setAccessPolicy()
166
 * MNStorage.create()
167
 * MNStorage.update()
168
 * MNStorage.delete()
169
 * MNReplication.replicate()
170
 *
171 6179 cjones
 */
172 6599 cjones
public class MNodeService extends D1NodeService
173 7417 leinfelder
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage, MNQuery {
174 6179 cjones
175 7772 tao
    //private static final String PATHQUERY = "pathquery";
176 7849 leinfelder
	public static final String UUID_SCHEME = "UUID";
177
	public static final String DOI_SCHEME = "DOI";
178 7489 leinfelder
	private static final String UUID_PREFIX = "urn:uuid:";
179 7418 leinfelder
180
	/* the logger instance */
181 6475 jones
    private Logger logMetacat = null;
182 6795 cjones
183
    /* A reference to a remote Memeber Node */
184
    private MNode mn;
185
186
    /* A reference to a Coordinating Node */
187
    private CNode cn;
188 6241 cjones
189 6795 cjones
190 6475 jones
    /**
191
     * Singleton accessor to get an instance of MNodeService.
192
     *
193
     * @return instance - the instance of MNodeService
194
     */
195 6542 leinfelder
    public static MNodeService getInstance(HttpServletRequest request) {
196
        return new MNodeService(request);
197 6179 cjones
    }
198
199 6475 jones
    /**
200
     * Constructor, private for singleton access
201
     */
202 6542 leinfelder
    private MNodeService(HttpServletRequest request) {
203
        super(request);
204 6475 jones
        logMetacat = Logger.getLogger(MNodeService.class);
205 6552 leinfelder
206
        // set the Member Node certificate file location
207
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
208 6310 cjones
    }
209 6475 jones
210
    /**
211
     * Deletes an object from the Member Node, where the object is either a
212
     * data object or a science metadata object.
213
     *
214
     * @param session - the Session object containing the credentials for the Subject
215
     * @param pid - The object identifier to be deleted
216
     *
217
     * @return pid - the identifier of the object used for the deletion
218
     *
219
     * @throws InvalidToken
220
     * @throws ServiceFailure
221
     * @throws NotAuthorized
222
     * @throws NotFound
223
     * @throws NotImplemented
224
     * @throws InvalidRequest
225
     */
226
    @Override
227
    public Identifier delete(Session session, Identifier pid)
228 6610 cjones
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
229 6475 jones
230 7162 leinfelder
    	// only admin of  the MN or the CN is allowed a full delete
231
        boolean allowed = false;
232 7330 leinfelder
        allowed = isAdminAuthorized(session);
233 8360 tao
234
        //check if it is the authoritative member node
235
        if(!allowed) {
236
            allowed = isAuthoritativeMNodeAdmin(session, pid);
237
        }
238
239 7162 leinfelder
        if (!allowed) {
240 7245 cjones
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
241 7162 leinfelder
        }
242
243 7077 leinfelder
    	// defer to superclass implementation
244
        return super.delete(session, pid);
245 6250 cjones
    }
246
247 6475 jones
    /**
248
     * Updates an existing object by creating a new object identified by
249
     * newPid on the Member Node which explicitly obsoletes the object
250
     * identified by pid through appropriate changes to the SystemMetadata
251
     * of pid and newPid
252
     *
253
     * @param session - the Session object containing the credentials for the Subject
254
     * @param pid - The identifier of the object to be updated
255
     * @param object - the new object bytes
256
     * @param sysmeta - the new system metadata describing the object
257
     *
258
     * @return newPid - the identifier of the new object
259
     *
260
     * @throws InvalidToken
261
     * @throws ServiceFailure
262
     * @throws NotAuthorized
263
     * @throws NotFound
264
     * @throws NotImplemented
265
     * @throws IdentifierNotUnique
266
     * @throws UnsupportedType
267
     * @throws InsufficientResources
268
     * @throws InvalidSystemMetadata
269
     * @throws InvalidRequest
270
     */
271
    @Override
272 6575 cjones
    public Identifier update(Session session, Identifier pid, InputStream object,
273
        Identifier newPid, SystemMetadata sysmeta)
274
        throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique,
275
        UnsupportedType, InsufficientResources, NotFound,
276
        InvalidSystemMetadata, NotImplemented, InvalidRequest {
277 6250 cjones
278 6475 jones
        String localId = null;
279
        boolean allowed = false;
280
        boolean isScienceMetadata = false;
281 6645 leinfelder
282
        if (session == null) {
283
        	throw new InvalidToken("1210", "No session has been provided");
284
        }
285 6475 jones
        Subject subject = session.getSubject();
286
287 7315 leinfelder
        // verify the pid is valid format
288 7318 leinfelder
        if (!isValidIdentifier(pid)) {
289 7315 leinfelder
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
290 6475 jones
        }
291
292
        // check for the existing identifier
293
        try {
294
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
295 6575 cjones
296 6475 jones
        } catch (McdbDocNotFoundException e) {
297 6575 cjones
            throw new InvalidRequest("1202", "The object with the provided " +
298
                "identifier was not found.");
299
300 6475 jones
        }
301 6518 leinfelder
302 6521 leinfelder
        // set the originating node
303
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
304
        sysmeta.setOriginMemberNode(originMemberNode);
305
306 6518 leinfelder
        // set the submitter to match the certificate
307
        sysmeta.setSubmitter(subject);
308 6525 leinfelder
        // set the dates
309
        Date now = Calendar.getInstance().getTime();
310 6575 cjones
        sysmeta.setDateSysMetadataModified(now);
311
        sysmeta.setDateUploaded(now);
312 7486 leinfelder
313
        // make sure serial version is set to something
314
        BigInteger serialVersion = sysmeta.getSerialVersion();
315
        if (serialVersion == null) {
316
        	sysmeta.setSerialVersion(BigInteger.ZERO);
317
        }
318 6475 jones
319
        // does the subject have WRITE ( == update) priveleges on the pid?
320
        allowed = isAuthorized(session, pid, Permission.WRITE);
321
322
        if (allowed) {
323 6649 leinfelder
324
        	// check quality of SM
325
        	if (sysmeta.getObsoletedBy() != null) {
326
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
327
        	}
328
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
329
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
330
        	}
331 6475 jones
332
            // get the existing system metadata for the object
333
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
334
335 7400 leinfelder
            // check for previous update
336
            // see: https://redmine.dataone.org/issues/3336
337
            Identifier existingObsoletedBy = existingSysMeta.getObsoletedBy();
338
            if (existingObsoletedBy != null) {
339
            	throw new InvalidRequest("1202",
340
            			"The previous identifier has already been made obsolete by: " + existingObsoletedBy.getValue());
341
            }
342 6475 jones
343
            isScienceMetadata = isScienceMetadata(sysmeta);
344
345
            // do we have XML metadata or a data object?
346
            if (isScienceMetadata) {
347
348
                // update the science metadata XML document
349
                // TODO: handle non-XML metadata/data documents (like netCDF)
350
                // TODO: don't put objects into memory using stream to string
351
                String objectAsXML = "";
352
                try {
353
                    objectAsXML = IOUtils.toString(object, "UTF-8");
354 7443 leinfelder
                    // give the old pid so we can calculate the new local id
355
                    localId = insertOrUpdateDocument(objectAsXML, pid, session, "update");
356 6475 jones
                    // register the newPid and the generated localId
357
                    if (newPid != null) {
358
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
359
360
                    }
361
362
                } catch (IOException e) {
363
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
364
                    logMetacat.info(msg);
365
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
366
367
                }
368
369
            } else {
370
371
                // update the data object
372
                localId = insertDataObject(object, newPid, session);
373
374
            }
375 8267 leinfelder
376
            // add the newPid to the obsoletedBy list for the existing sysmeta
377
            existingSysMeta.setObsoletedBy(newPid);
378 6475 jones
379 8267 leinfelder
            // then update the existing system metadata
380
            updateSystemMetadata(existingSysMeta);
381
382
            // prep the new system metadata, add pid to the affected lists
383
            sysmeta.setObsoletes(pid);
384
            //sysmeta.addDerivedFrom(pid);
385
386 6475 jones
            // and insert the new system metadata
387
            insertSystemMetadata(sysmeta);
388
389
            // log the update event
390 6542 leinfelder
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
391 7507 leinfelder
392
            // attempt to register the identifier - it checks if it is a doi
393
            try {
394 7512 leinfelder
    			DOIService.getInstance().registerDOI(sysmeta);
395 7507 leinfelder
    		} catch (EZIDException e) {
396
                throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
397
    		}
398 6475 jones
399
        } else {
400
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
401
                    + " on the Member Node.");
402
        }
403
404
        return newPid;
405 6250 cjones
    }
406 6254 cjones
407 6475 jones
    public Identifier create(Session session, Identifier pid, InputStream object, SystemMetadata sysmeta) throws InvalidToken, ServiceFailure, NotAuthorized,
408
            IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, InvalidRequest {
409 6250 cjones
410 6916 cjones
        // check for null session
411 6530 leinfelder
        if (session == null) {
412 6575 cjones
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
413 6530 leinfelder
        }
414 6518 leinfelder
        // set the submitter to match the certificate
415
        sysmeta.setSubmitter(session.getSubject());
416 6520 leinfelder
        // set the originating node
417
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
418
        sysmeta.setOriginMemberNode(originMemberNode);
419 6916 cjones
        sysmeta.setArchived(false);
420
421 6525 leinfelder
        // set the dates
422
        Date now = Calendar.getInstance().getTime();
423 6916 cjones
        sysmeta.setDateSysMetadataModified(now);
424
        sysmeta.setDateUploaded(now);
425 7021 leinfelder
426
        // set the serial version
427
        sysmeta.setSerialVersion(BigInteger.ZERO);
428 7083 cjones
429
        // check that we are not attempting to subvert versioning
430
        if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
431
            throw new InvalidSystemMetadata("1180",
432
              "The supplied system metadata is invalid. " +
433
              "The obsoletes field cannot have a value when creating entries.");
434
        }
435 7021 leinfelder
436 7083 cjones
        if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
437
            throw new InvalidSystemMetadata("1180",
438
              "The supplied system metadata is invalid. " +
439
              "The obsoletedBy field cannot have a value when creating entries.");
440
        }
441
442 6518 leinfelder
        // call the shared impl
443 7507 leinfelder
        Identifier resultPid = super.create(session, pid, object, sysmeta);
444
445
        // attempt to register the identifier - it checks if it is a doi
446
        try {
447 7512 leinfelder
			DOIService.getInstance().registerDOI(sysmeta);
448 7507 leinfelder
		} catch (EZIDException e) {
449 7510 leinfelder
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
450
			sf.initCause(e);
451
            throw sf;
452 7507 leinfelder
		}
453
454
        // return
455
		return resultPid ;
456 6475 jones
    }
457 6250 cjones
458 6475 jones
    /**
459
     * Called by a Coordinating Node to request that the Member Node create a
460
     * copy of the specified object by retrieving it from another Member
461
     * Node and storing it locally so that it can be made accessible to
462
     * the DataONE system.
463
     *
464
     * @param session - the Session object containing the credentials for the Subject
465
     * @param sysmeta - Copy of the CN held system metadata for the object
466
     * @param sourceNode - A reference to node from which the content should be
467
     *                     retrieved. The reference should be resolved by
468
     *                     checking the CN node registry.
469
     *
470
     * @return true if the replication succeeds
471
     *
472
     * @throws ServiceFailure
473
     * @throws NotAuthorized
474
     * @throws NotImplemented
475
     * @throws UnsupportedType
476
     * @throws InsufficientResources
477
     * @throws InvalidRequest
478
     */
479
    @Override
480 6786 cjones
    public boolean replicate(Session session, SystemMetadata sysmeta,
481
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
482
            NotAuthorized, InvalidRequest, InsufficientResources,
483
            UnsupportedType {
484
485 6875 cjones
        if (session != null && sysmeta != null && sourceNode != null) {
486
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
487
                            "\tSession.Subject      = "                           +
488
                            session.getSubject().getValue() + "\n"                +
489 7082 cjones
                            "\tidentifier           = "                           +
490
                            sysmeta.getIdentifier().getValue()                    +
491 6875 cjones
                            "\n" + "\tSource NodeReference ="                     +
492
                            sourceNode.getValue());
493
        }
494 6475 jones
        boolean result = false;
495 6786 cjones
        String nodeIdStr = null;
496 6651 cjones
        NodeReference nodeId = null;
497 6786 cjones
498 6795 cjones
        // get the referenced object
499
        Identifier pid = sysmeta.getIdentifier();
500
501
        // get from the membernode
502
        // TODO: switch credentials for the server retrieval?
503
        this.mn = D1Client.getMN(sourceNode);
504
        this.cn = D1Client.getCN();
505
        InputStream object = null;
506
        Session thisNodeSession = null;
507
        SystemMetadata localSystemMetadata = null;
508
        BaseException failure = null;
509 6818 cjones
        String localId = null;
510
511 6795 cjones
        // TODO: check credentials
512
        // cannot be called by public
513 7063 leinfelder
        if (session == null || session.getSubject() == null) {
514 7082 cjones
            String msg = "No session was provided to replicate identifier " +
515
            sysmeta.getIdentifier().getValue();
516 8414 tao
            logMetacat.error(msg);
517 7192 cjones
            throw new NotAuthorized("2152", msg);
518
519 6795 cjones
        }
520
521
522 6651 cjones
        // get the local node id
523
        try {
524 7030 cjones
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
525 6651 cjones
            nodeId = new NodeReference();
526
            nodeId.setValue(nodeIdStr);
527 6786 cjones
528 6651 cjones
        } catch (PropertyNotFoundException e1) {
529 7030 cjones
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
530 6795 cjones
            failure = new ServiceFailure("2151", msg);
531 8414 tao
            //setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
532 6795 cjones
            logMetacat.error(msg);
533 8414 tao
            //return true;
534
            throw new ServiceFailure("2151", msg);
535 6786 cjones
536 6651 cjones
        }
537 6795 cjones
538 6475 jones
539
        try {
540 6786 cjones
            // do we already have a replica?
541
            try {
542 6818 cjones
                localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
543 6822 cjones
                // if we have a local id, get the local object
544
                try {
545
                    object = MetacatHandler.read(localId);
546
                } catch (Exception e) {
547 7127 leinfelder
                	// NOTE: we may already know about this ID because it could be a data file described by a metadata file
548
                	// https://redmine.dataone.org/issues/2572
549
                	// TODO: fix this so that we don't prevent ourselves from getting replicas
550
551 6822 cjones
                    // let the CN know that the replication failed
552 7127 leinfelder
                	logMetacat.warn("Object content not found on this node despite having localId: " + localId);
553
                	String msg = "Can't read the object bytes properly, replica is invalid.";
554
                    ServiceFailure serviceFailure = new ServiceFailure("2151", msg);
555 7113 leinfelder
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, serviceFailure);
556
                    logMetacat.warn(msg);
557 6822 cjones
                    throw serviceFailure;
558
559
                }
560
561 6817 cjones
            } catch (McdbDocNotFoundException e) {
562 6818 cjones
                logMetacat.info("No replica found. Continuing.");
563 6817 cjones
564 6819 cjones
            }
565
566 6786 cjones
            // no local replica, get a replica
567 6819 cjones
            if ( object == null ) {
568 6786 cjones
                // session should be null to use the default certificate
569
                // location set in the Certificate manager
570
                object = mn.getReplica(thisNodeSession, pid);
571 7082 cjones
                logMetacat.info("MNodeService.getReplica() called for identifier "
572 6786 cjones
                                + pid.getValue());
573
574
            }
575
576 6795 cjones
        } catch (InvalidToken e) {
577
            String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
578
            failure = new ServiceFailure("2151", msg);
579
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
580
            logMetacat.error(msg);
581
            throw new ServiceFailure("2151", msg);
582 6786 cjones
583 6475 jones
        } catch (NotFound e) {
584 6795 cjones
            String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
585
            failure = new ServiceFailure("2151", msg);
586
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
587
            logMetacat.error(msg);
588
            throw new ServiceFailure("2151", msg);
589 6786 cjones
590 8414 tao
        } catch (NotAuthorized e) {
591
            String msg = "Could not retrieve object to replicate (NotAuthorized): "+ e.getMessage();
592
            failure = new ServiceFailure("2151", msg);
593
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
594
            logMetacat.error(msg);
595
            throw new ServiceFailure("2151", msg);
596
        } catch (NotImplemented e) {
597
            String msg = "Could not retrieve object to replicate (mn.getReplica NotImplemented): "+ e.getMessage();
598
            failure = new ServiceFailure("2151", msg);
599
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
600
            logMetacat.error(msg);
601
            throw new ServiceFailure("2151", msg);
602
        } catch (ServiceFailure e) {
603
            String msg = "Could not retrieve object to replicate (ServiceFailure): "+ e.getMessage();
604
            failure = new ServiceFailure("2151", msg);
605
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
606
            logMetacat.error(msg);
607
            throw new ServiceFailure("2151", msg);
608
        } catch (InsufficientResources e) {
609
            String msg = "Could not retrieve object to replicate (InsufficientResources): "+ e.getMessage();
610
            failure = new ServiceFailure("2151", msg);
611
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
612
            logMetacat.error(msg);
613
            throw new ServiceFailure("2151", msg);
614 6475 jones
        }
615
616 6693 leinfelder
        // verify checksum on the object, if supported
617
        if (object.markSupported()) {
618 6786 cjones
            Checksum givenChecksum = sysmeta.getChecksum();
619
            Checksum computedChecksum = null;
620
            try {
621 7127 leinfelder
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
622 6786 cjones
                object.reset();
623 6948 cjones
624 6786 cjones
            } catch (Exception e) {
625 7127 leinfelder
                String msg = "Error computing checksum on replica: " + e.getMessage();
626 7113 leinfelder
                logMetacat.error(msg);
627 6786 cjones
                ServiceFailure sf = new ServiceFailure("2151", msg);
628
                sf.initCause(e);
629 8414 tao
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, sf);
630 6786 cjones
                throw sf;
631
            }
632 6948 cjones
            if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
633 7113 leinfelder
                logMetacat.error("Given    checksum for " + pid.getValue() +
634 6948 cjones
                    "is " + givenChecksum.getValue());
635 7113 leinfelder
                logMetacat.error("Computed checksum for " + pid.getValue() +
636 6948 cjones
                    "is " + computedChecksum.getValue());
637 8414 tao
                String msg = "Computed checksum does not match declared checksum";
638
                failure = new ServiceFailure("2151", msg);
639
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
640
                throw new ServiceFailure("2151", msg);
641 6786 cjones
            }
642 6693 leinfelder
        }
643 6786 cjones
644 6475 jones
        // add it to local store
645
        Identifier retPid;
646
        try {
647 7127 leinfelder
            // skip the MN.create -- this mutates the system metadata and we don't want it to
648 6818 cjones
            if ( localId == null ) {
649 7127 leinfelder
                // TODO: this will fail if we already "know" about the identifier
650
            	// FIXME: see https://redmine.dataone.org/issues/2572
651 6818 cjones
                retPid = super.create(session, pid, object, sysmeta);
652
                result = (retPid.getValue().equals(pid.getValue()));
653
            }
654 6795 cjones
655 7125 leinfelder
        } catch (Exception e) {
656 7126 leinfelder
            String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
657 7125 leinfelder
            failure = new ServiceFailure("2151", msg);
658
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
659
            logMetacat.error(msg);
660
            throw new ServiceFailure("2151", msg);
661
662 6475 jones
        }
663
664 6795 cjones
        // finish by setting the replication status
665
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
666 6475 jones
        return result;
667
668 6250 cjones
    }
669 6179 cjones
670 6475 jones
    /**
671
     * Return the object identified by the given object identifier
672
     *
673
     * @param session - the Session object containing the credentials for the Subject
674
     * @param pid - the object identifier for the given object
675
     *
676
     * @return inputStream - the input stream of the given object
677
     *
678
     * @throws InvalidToken
679
     * @throws ServiceFailure
680
     * @throws NotAuthorized
681
     * @throws InvalidRequest
682
     * @throws NotImplemented
683
     */
684
    @Override
685 6610 cjones
    public InputStream get(Session session, Identifier pid)
686
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
687 6258 cjones
688 6475 jones
        return super.get(session, pid);
689 6258 cjones
690 6259 cjones
    }
691 6258 cjones
692 6475 jones
    /**
693
     * Returns a Checksum for the specified object using an accepted hashing algorithm
694
     *
695
     * @param session - the Session object containing the credentials for the Subject
696
     * @param pid - the object identifier for the given object
697
     * @param algorithm -  the name of an algorithm that will be used to compute
698
     *                     a checksum of the bytes of the object
699
     *
700
     * @return checksum - the checksum of the given object
701
     *
702
     * @throws InvalidToken
703
     * @throws ServiceFailure
704
     * @throws NotAuthorized
705
     * @throws NotFound
706
     * @throws InvalidRequest
707
     * @throws NotImplemented
708
     */
709
    @Override
710 6610 cjones
    public Checksum getChecksum(Session session, Identifier pid, String algorithm)
711
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
712
        InvalidRequest, NotImplemented {
713 6258 cjones
714 6475 jones
        Checksum checksum = null;
715
716
        InputStream inputStream = get(session, pid);
717
718 6259 cjones
        try {
719 6475 jones
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
720
721
        } catch (NoSuchAlgorithmException e) {
722
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
723
                    + e.getMessage());
724 6259 cjones
        } catch (IOException e) {
725 6475 jones
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
726
                    + e.getMessage());
727 6259 cjones
        }
728 6382 cjones
729 6475 jones
        if (checksum == null) {
730
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
731
        }
732 6258 cjones
733 6475 jones
        return checksum;
734 6259 cjones
    }
735 6179 cjones
736 6475 jones
    /**
737
     * Return the system metadata for a given object
738
     *
739
     * @param session - the Session object containing the credentials for the Subject
740
     * @param pid - the object identifier for the given object
741
     *
742
     * @return inputStream - the input stream of the given system metadata object
743
     *
744
     * @throws InvalidToken
745
     * @throws ServiceFailure
746
     * @throws NotAuthorized
747
     * @throws NotFound
748
     * @throws InvalidRequest
749
     * @throws NotImplemented
750
     */
751
    @Override
752 6610 cjones
    public SystemMetadata getSystemMetadata(Session session, Identifier pid)
753
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
754
        NotImplemented {
755 6341 leinfelder
756 6475 jones
        return super.getSystemMetadata(session, pid);
757
    }
758 6341 leinfelder
759 6475 jones
    /**
760
     * Retrieve the list of objects present on the MN that match the calling parameters
761
     *
762
     * @param session - the Session object containing the credentials for the Subject
763
     * @param startTime - Specifies the beginning of the time range from which
764
     *                    to return object (>=)
765
     * @param endTime - Specifies the beginning of the time range from which
766
     *                  to return object (>=)
767
     * @param objectFormat - Restrict results to the specified object format
768
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
769
     * @param start - The zero-based index of the first value, relative to the
770
     *                first record of the resultset that matches the parameters.
771
     * @param count - The maximum number of entries that should be returned in
772
     *                the response. The Member Node may return less entries
773
     *                than specified in this value.
774
     *
775
     * @return objectList - the list of objects matching the criteria
776
     *
777
     * @throws InvalidToken
778
     * @throws ServiceFailure
779
     * @throws NotAuthorized
780
     * @throws InvalidRequest
781
     * @throws NotImplemented
782
     */
783
    @Override
784
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
785
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
786 6179 cjones
787 6475 jones
        ObjectList objectList = null;
788 6332 leinfelder
789 6475 jones
        try {
790 7439 leinfelder
        	// safeguard against large requests
791
            if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
792
            	count = MAXIMUM_DB_RECORD_COUNT;
793
            }
794 6475 jones
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
795
        } catch (Exception e) {
796
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
797
        }
798 6332 leinfelder
799 6475 jones
        return objectList;
800 6229 cjones
    }
801 6179 cjones
802 6475 jones
    /**
803 6476 jones
     * Return a description of the node's capabilities and services.
804 6475 jones
     *
805
     * @return node - the technical capabilities of the Member Node
806
     *
807
     * @throws ServiceFailure
808
     * @throws NotAuthorized
809
     * @throws InvalidRequest
810
     * @throws NotImplemented
811
     */
812
    @Override
813 6610 cjones
    public Node getCapabilities()
814
        throws NotImplemented, ServiceFailure {
815 6179 cjones
816 6475 jones
        String nodeName = null;
817
        String nodeId = null;
818 6492 jones
        String subject = null;
819 6938 cjones
        String contactSubject = null;
820 6475 jones
        String nodeDesc = null;
821 6476 jones
        String nodeTypeString = null;
822
        NodeType nodeType = null;
823 6475 jones
        String mnCoreServiceVersion = null;
824
        String mnReadServiceVersion = null;
825
        String mnAuthorizationServiceVersion = null;
826
        String mnStorageServiceVersion = null;
827
        String mnReplicationServiceVersion = null;
828 6179 cjones
829 6475 jones
        boolean nodeSynchronize = false;
830
        boolean nodeReplicate = false;
831
        boolean mnCoreServiceAvailable = false;
832
        boolean mnReadServiceAvailable = false;
833
        boolean mnAuthorizationServiceAvailable = false;
834
        boolean mnStorageServiceAvailable = false;
835
        boolean mnReplicationServiceAvailable = false;
836 6179 cjones
837 6475 jones
        try {
838
            // get the properties of the node based on configuration information
839 6492 jones
            nodeName = PropertyService.getProperty("dataone.nodeName");
840 7030 cjones
            nodeId = PropertyService.getProperty("dataone.nodeId");
841 6492 jones
            subject = PropertyService.getProperty("dataone.subject");
842 6938 cjones
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
843 6475 jones
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
844 6476 jones
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
845
            nodeType = NodeType.convert(nodeTypeString);
846 6475 jones
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
847
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
848
849
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
850
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
851
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
852
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
853
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
854
855
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
856
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
857
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
858
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
859
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
860
861 6476 jones
            // Set the properties of the node based on configuration information and
862
            // calls to current status methods
863 7286 leinfelder
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
864 6476 jones
            Node node = new Node();
865 6542 leinfelder
            node.setBaseURL(serviceName + "/" + nodeTypeString);
866 6476 jones
            node.setDescription(nodeDesc);
867 6475 jones
868 6476 jones
            // set the node's health information
869
            node.setState(NodeState.UP);
870
871
            // set the ping response to the current value
872
            Ping canPing = new Ping();
873
            canPing.setSuccess(false);
874
            try {
875 6803 leinfelder
            	Date pingDate = ping();
876
                canPing.setSuccess(pingDate != null);
877
            } catch (BaseException e) {
878 6476 jones
                e.printStackTrace();
879 6803 leinfelder
                // guess it can't be pinged
880 6476 jones
            }
881 6610 cjones
882 6476 jones
            node.setPing(canPing);
883 6475 jones
884 6476 jones
            NodeReference identifier = new NodeReference();
885
            identifier.setValue(nodeId);
886
            node.setIdentifier(identifier);
887 6492 jones
            Subject s = new Subject();
888
            s.setValue(subject);
889
            node.addSubject(s);
890 6938 cjones
            Subject contact = new Subject();
891
            contact.setValue(contactSubject);
892
            node.addContactSubject(contact);
893 6476 jones
            node.setName(nodeName);
894
            node.setReplicate(nodeReplicate);
895
            node.setSynchronize(nodeSynchronize);
896 6475 jones
897 6476 jones
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
898
            Services services = new Services();
899 6475 jones
900 6476 jones
            Service sMNCore = new Service();
901
            sMNCore.setName("MNCore");
902
            sMNCore.setVersion(mnCoreServiceVersion);
903
            sMNCore.setAvailable(mnCoreServiceAvailable);
904 6475 jones
905 6476 jones
            Service sMNRead = new Service();
906
            sMNRead.setName("MNRead");
907
            sMNRead.setVersion(mnReadServiceVersion);
908
            sMNRead.setAvailable(mnReadServiceAvailable);
909 6475 jones
910 6476 jones
            Service sMNAuthorization = new Service();
911
            sMNAuthorization.setName("MNAuthorization");
912
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
913
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
914 6475 jones
915 6476 jones
            Service sMNStorage = new Service();
916
            sMNStorage.setName("MNStorage");
917
            sMNStorage.setVersion(mnStorageServiceVersion);
918
            sMNStorage.setAvailable(mnStorageServiceAvailable);
919 6475 jones
920 6476 jones
            Service sMNReplication = new Service();
921
            sMNReplication.setName("MNReplication");
922
            sMNReplication.setVersion(mnReplicationServiceVersion);
923
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
924 6475 jones
925 6476 jones
            services.addService(sMNRead);
926
            services.addService(sMNCore);
927
            services.addService(sMNAuthorization);
928
            services.addService(sMNStorage);
929
            services.addService(sMNReplication);
930
            node.setServices(services);
931 6475 jones
932 6476 jones
            // Set the schedule for synchronization
933
            Synchronization synchronization = new Synchronization();
934
            Schedule schedule = new Schedule();
935
            Date now = new Date();
936 6689 leinfelder
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
937
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
938
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
939
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
940
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
941
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
942
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
943 6476 jones
            synchronization.setSchedule(schedule);
944
            synchronization.setLastHarvested(now);
945
            synchronization.setLastCompleteHarvest(now);
946
            node.setSynchronization(synchronization);
947 6475 jones
948 6476 jones
            node.setType(nodeType);
949
            return node;
950 6475 jones
951 6476 jones
        } catch (PropertyNotFoundException pnfe) {
952
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
953
            logMetacat.error(msg);
954
            throw new ServiceFailure("2162", msg);
955
        }
956 6228 cjones
    }
957 6179 cjones
958 6475 jones
    /**
959
     * Returns the number of operations that have been serviced by the node
960
     * over time periods of one and 24 hours.
961
     *
962
     * @param session - the Session object containing the credentials for the Subject
963
     * @param period - An ISO8601 compatible DateTime range specifying the time
964
     *                 range for which to return operation statistics.
965
     * @param requestor - Limit to operations performed by given requestor identity.
966
     * @param event -  Enumerated value indicating the type of event being examined
967
     * @param format - Limit to events involving objects of the specified format
968
     *
969
     * @return the desired log records
970
     *
971
     * @throws InvalidToken
972
     * @throws ServiceFailure
973
     * @throws NotAuthorized
974
     * @throws InvalidRequest
975
     * @throws NotImplemented
976
     */
977 6610 cjones
    public MonitorList getOperationStatistics(Session session, Date startTime,
978
        Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
979
        throws NotImplemented, ServiceFailure, NotAuthorized, InsufficientResources, UnsupportedType {
980 6179 cjones
981 6475 jones
        MonitorList monitorList = new MonitorList();
982 6179 cjones
983 6475 jones
        try {
984 6179 cjones
985 6475 jones
            // get log records first
986 7101 leinfelder
            Log logs = getLogRecords(session, startTime, endTime, event, null, 0, null);
987 6179 cjones
988 6475 jones
            // TODO: aggregate by day or hour -- needs clarification
989
            int count = 1;
990
            for (LogEntry logEntry : logs.getLogEntryList()) {
991
                Identifier pid = logEntry.getIdentifier();
992
                Date logDate = logEntry.getDateLogged();
993
                // if we are filtering by format
994
                if (formatId != null) {
995 6692 leinfelder
                    SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
996 6561 leinfelder
                    if (!sysmeta.getFormatId().getValue().equals(formatId.getValue())) {
997 6475 jones
                        // does not match
998
                        continue;
999
                    }
1000
                }
1001
                MonitorInfo item = new MonitorInfo();
1002
                item.setCount(count);
1003
                item.setDate(new java.sql.Date(logDate.getTime()));
1004
                monitorList.addMonitorInfo(item);
1005 6179 cjones
1006 6475 jones
            }
1007
        } catch (Exception e) {
1008
            e.printStackTrace();
1009
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
1010
        }
1011 6345 cjones
1012 6475 jones
        return monitorList;
1013 6345 cjones
1014 6340 cjones
    }
1015
1016 6475 jones
    /**
1017
     * A callback method used by a CN to indicate to a MN that it cannot
1018
     * complete synchronization of the science metadata identified by pid.  Log
1019
     * the event in the metacat event log.
1020
     *
1021
     * @param session
1022
     * @param syncFailed
1023
     *
1024
     * @throws ServiceFailure
1025
     * @throws NotAuthorized
1026
     * @throws NotImplemented
1027
     */
1028
    @Override
1029 6991 leinfelder
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed)
1030 6610 cjones
        throws NotImplemented, ServiceFailure, NotAuthorized {
1031 6179 cjones
1032 6475 jones
        String localId;
1033 7075 cjones
        Identifier pid;
1034
        if ( syncFailed.getPid() != null ) {
1035
            pid = new Identifier();
1036
            pid.setValue(syncFailed.getPid());
1037
            boolean allowed;
1038
1039
            //are we allowed? only CNs
1040
            try {
1041 7142 leinfelder
                allowed = isAdminAuthorized(session);
1042 7075 cjones
                if ( !allowed ){
1043
                    throw new NotAuthorized("2162",
1044
                            "Not allowed to call synchronizationFailed() on this node.");
1045
                }
1046
            } catch (InvalidToken e) {
1047
                throw new NotAuthorized("2162",
1048
                        "Not allowed to call synchronizationFailed() on this node.");
1049 6331 leinfelder
1050 7075 cjones
            }
1051
1052
        } else {
1053
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1054
1055
        }
1056
1057 6475 jones
        try {
1058 7075 cjones
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1059 6475 jones
        } catch (McdbDocNotFoundException e) {
1060 7075 cjones
            throw new ServiceFailure("2161", "The identifier specified by " +
1061
                    syncFailed.getPid() + " was not found on this node.");
1062 6179 cjones
1063 6475 jones
        }
1064
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1065
        // method is changed to include the URL as a parameter
1066 7075 cjones
        logMetacat.debug("Synchronization for the object identified by " +
1067
                pid.getValue() + " failed from " + syncFailed.getNodeId() +
1068
                " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
1069 6475 jones
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1070 6532 leinfelder
        String principal = Constants.SUBJECT_PUBLIC;
1071 6506 leinfelder
        if (session != null && session.getSubject() != null) {
1072 6575 cjones
          principal = session.getSubject().getValue();
1073 6506 leinfelder
        }
1074
        try {
1075 6575 cjones
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1076 6506 leinfelder
        } catch (Exception e) {
1077 7075 cjones
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1078 6991 leinfelder
        }
1079 6475 jones
        //EventLog.getInstance().log("CN URL WILL GO HERE",
1080
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1081 6991 leinfelder
        return true;
1082 6179 cjones
1083 6260 cjones
    }
1084
1085 6475 jones
    /**
1086
     * Essentially a get() but with different logging behavior
1087
     */
1088
    @Override
1089 6540 cjones
    public InputStream getReplica(Session session, Identifier pid)
1090 6653 leinfelder
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1091 6179 cjones
1092 6540 cjones
        logMetacat.info("MNodeService.getReplica() called.");
1093
1094 6653 leinfelder
        // cannot be called by public
1095
        if (session == null) {
1096
        	throw new InvalidToken("2183", "No session was provided.");
1097
        }
1098
1099 6631 cjones
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1100
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1101
             "\tIdentifier           = " + pid.getValue());
1102
1103 6475 jones
        InputStream inputStream = null; // bytes to be returned
1104
        handler = new MetacatHandler(new Timer());
1105
        boolean allowed = false;
1106
        String localId; // the metacat docid for the pid
1107 6179 cjones
1108 6475 jones
        // get the local docid from Metacat
1109
        try {
1110
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1111
        } catch (McdbDocNotFoundException e) {
1112 6610 cjones
            throw new ServiceFailure("2181", "The object specified by " +
1113
                    pid.getValue() + " does not exist at this node.");
1114
1115 6475 jones
        }
1116 6234 cjones
1117 6552 leinfelder
        Subject targetNodeSubject = session.getSubject();
1118 6185 leinfelder
1119 6552 leinfelder
        // check for authorization to replicate, null session to act as this source MN
1120 6610 cjones
        try {
1121 6777 leinfelder
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1122 6610 cjones
        } catch (InvalidToken e1) {
1123
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
1124
                + e1.getMessage());
1125
1126
        } catch (NotFound e1) {
1127
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
1128
                    + e1.getMessage());
1129 6384 cjones
1130 6610 cjones
        } catch (InvalidRequest e1) {
1131
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
1132
                    + e1.getMessage());
1133
1134
        }
1135
1136 6540 cjones
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1137
            " for identifier " + pid.getValue());
1138
1139 6475 jones
        // if the person is authorized, perform the read
1140
        if (allowed) {
1141
            try {
1142 6986 jones
                inputStream = MetacatHandler.read(localId);
1143 6475 jones
            } catch (Exception e) {
1144 6610 cjones
                throw new ServiceFailure("1020", "The object specified by " +
1145
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1146 6475 jones
            }
1147
        }
1148 6384 cjones
1149 6475 jones
        // if we fail to set the input stream
1150
        if (inputStream == null) {
1151 6610 cjones
            throw new ServiceFailure("2181", "The object specified by " +
1152
                pid.getValue() + "does not exist at this node.");
1153 6475 jones
        }
1154
1155
        // log the replica event
1156
        String principal = null;
1157
        if (session.getSubject() != null) {
1158
            principal = session.getSubject().getValue();
1159
        }
1160 6576 cjones
        EventLog.getInstance().log(request.getRemoteAddr(),
1161
            request.getHeader("User-Agent"), principal, localId, "replicate");
1162 6475 jones
1163
        return inputStream;
1164
    }
1165
1166 6573 cjones
    /**
1167 6600 cjones
     * A method to notify the Member Node that the authoritative copy of
1168 6599 cjones
     * system metadata on the Coordinating Nodes has changed.
1169
     *
1170
     * @param session   Session information that contains the identity of the
1171
     *                  calling user as retrieved from the X.509 certificate
1172
     *                  which must be traceable to the CILogon service.
1173
     * @param serialVersion   The serialVersion of the system metadata
1174
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1175
     * @throws NotImplemented
1176
     * @throws ServiceFailure
1177
     * @throws NotAuthorized
1178
     * @throws InvalidRequest
1179
     * @throws InvalidToken
1180
     */
1181 6991 leinfelder
    public boolean systemMetadataChanged(Session session, Identifier pid,
1182 6599 cjones
        long serialVersion, Date dateSysMetaLastModified)
1183
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1184
        InvalidToken {
1185
1186 7600 cjones
        // cannot be called by public
1187
        if (session == null) {
1188
        	throw new InvalidToken("2183", "No session was provided.");
1189
        }
1190
1191 6600 cjones
        SystemMetadata currentLocalSysMeta = null;
1192
        SystemMetadata newSysMeta = null;
1193
        CNode cn = D1Client.getCN();
1194
        NodeList nodeList = null;
1195
        Subject callingSubject = null;
1196
        boolean allowed = false;
1197
1198
        // are we allowed to call this?
1199
        callingSubject = session.getSubject();
1200
        nodeList = cn.listNodes();
1201
1202
        for(Node node : nodeList.getNodeList()) {
1203
            // must be a CN
1204
            if ( node.getType().equals(NodeType.CN)) {
1205
               List<Subject> subjectList = node.getSubjectList();
1206
               // the calling subject must be in the subject list
1207
               if ( subjectList.contains(callingSubject)) {
1208
                   allowed = true;
1209
1210
               }
1211
1212
            }
1213
        }
1214
1215
        if (!allowed ) {
1216
            String msg = "The subject identified by " + callingSubject.getValue() +
1217
              " is not authorized to call this service.";
1218
            throw new NotAuthorized("1331", msg);
1219
1220
        }
1221
1222
        // compare what we have locally to what is sent in the change notification
1223
        try {
1224 6692 leinfelder
            currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1225
1226
        } catch (RuntimeException e) {
1227 6600 cjones
            String msg = "SystemMetadata for pid " + pid.getValue() +
1228 6692 leinfelder
              " couldn't be updated because it couldn't be found locally: " +
1229 6600 cjones
              e.getMessage();
1230 6692 leinfelder
            logMetacat.error(msg);
1231
            ServiceFailure sf = new ServiceFailure("1333", msg);
1232
            sf.initCause(e);
1233
            throw sf;
1234 6600 cjones
        }
1235
1236
        if (currentLocalSysMeta.getSerialVersion().longValue() < serialVersion ) {
1237
            try {
1238
                newSysMeta = cn.getSystemMetadata(null, pid);
1239
            } catch (NotFound e) {
1240
                // huh? you just said you had it
1241 6692 leinfelder
            	String msg = "On updating the local copy of system metadata " +
1242
                "for pid " + pid.getValue() +", the CN reports it is not found." +
1243
                " The error message was: " + e.getMessage();
1244
                logMetacat.error(msg);
1245
                ServiceFailure sf = new ServiceFailure("1333", msg);
1246
                sf.initCause(e);
1247
                throw sf;
1248 6600 cjones
            }
1249 6692 leinfelder
1250 6600 cjones
            // update the local copy of system metadata for the pid
1251
            try {
1252 6692 leinfelder
                HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1253 7812 leinfelder
                // submit for indexing
1254
                HazelcastService.getInstance().getIndexQueue().add(newSysMeta);
1255 6600 cjones
                logMetacat.info("Updated local copy of system metadata for pid " +
1256
                    pid.getValue() + " after change notification from the CN.");
1257
1258 6692 leinfelder
            } catch (RuntimeException e) {
1259 6600 cjones
                String msg = "SystemMetadata for pid " + pid.getValue() +
1260 6692 leinfelder
                  " couldn't be updated: " +
1261 6600 cjones
                  e.getMessage();
1262 6692 leinfelder
                logMetacat.error(msg);
1263
                ServiceFailure sf = new ServiceFailure("1333", msg);
1264
                sf.initCause(e);
1265
                throw sf;
1266 6600 cjones
            }
1267
        }
1268
1269 6991 leinfelder
        return true;
1270
1271 6599 cjones
    }
1272
1273 6795 cjones
    /*
1274
     * Set the replication status for the object on the Coordinating Node
1275
     *
1276
     * @param session - the session for the this target node
1277
     * @param pid - the identifier of the object being updated
1278
     * @param nodeId - the identifier of this target node
1279
     * @param status - the replication status to set
1280
     * @param failure - the exception to include, if any
1281
     */
1282
    private void setReplicationStatus(Session session, Identifier pid,
1283
        NodeReference nodeId, ReplicationStatus status, BaseException failure)
1284
        throws ServiceFailure, NotImplemented, NotAuthorized,
1285
        InvalidRequest {
1286
1287
        // call the CN as the MN to set the replication status
1288
        try {
1289
            this.cn = D1Client.getCN();
1290
            this.cn.setReplicationStatus(session, pid, nodeId,
1291
                    status, failure);
1292
1293
        } catch (InvalidToken e) {
1294 7091 leinfelder
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (InvalidToken): " + e.getMessage();
1295
            logMetacat.error(msg);
1296
        	throw new ServiceFailure("2151",
1297
                    msg);
1298 6795 cjones
1299
        } catch (NotFound e) {
1300 7091 leinfelder
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (NotFound): " + e.getMessage();
1301
            logMetacat.error(msg);
1302
        	throw new ServiceFailure("2151",
1303
                    msg);
1304 6795 cjones
1305
        }
1306
    }
1307 7099 leinfelder
1308
	@Override
1309 7441 leinfelder
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1310 7099 leinfelder
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1311
			InvalidRequest {
1312 7448 leinfelder
1313 8210 leinfelder
		// check for null session
1314
        if (session == null) {
1315
          throw new InvalidToken("2190", "Session is required to generate an Identifier at this Node.");
1316
        }
1317
1318 7441 leinfelder
		Identifier identifier = new Identifier();
1319 7448 leinfelder
1320 7489 leinfelder
		// handle different schemes
1321
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1322
			// UUID
1323
			UUID uuid = UUID.randomUUID();
1324
            identifier.setValue(UUID_PREFIX + uuid.toString());
1325
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1326 7512 leinfelder
			// generate a DOI
1327 7448 leinfelder
			try {
1328 7512 leinfelder
				identifier = DOIService.getInstance().generateDOI();
1329 7448 leinfelder
			} catch (EZIDException e) {
1330 7512 leinfelder
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1331
				sf.initCause(e);
1332
				throw sf;
1333 7448 leinfelder
			}
1334 7489 leinfelder
		} else {
1335
			// default if we don't know the scheme
1336
			if (fragment != null) {
1337
				// for now, just autogen with fragment
1338
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1339
				identifier.setValue(autogenId);
1340
			} else {
1341
				// autogen with no fragment
1342
				String autogenId = DocumentUtil.generateDocumentId(0);
1343
				identifier.setValue(autogenId);
1344
			}
1345 7448 leinfelder
		}
1346
1347 7441 leinfelder
		// TODO: reserve the identifier with the CN. We can only do this when
1348
		// 1) the MN is part of a CN cluster
1349
		// 2) the request is from an authenticated user
1350
1351
		return identifier;
1352 7099 leinfelder
	}
1353 7144 leinfelder
1354
	@Override
1355
	public boolean isAuthorized(Identifier pid, Permission permission)
1356
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
1357
			NotAuthorized, NotImplemented {
1358
1359
		return isAuthorized(null, pid, permission);
1360
	}
1361
1362
	@Override
1363
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
1364
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1365
			InvalidRequest {
1366
1367
		return systemMetadataChanged(null, pid, serialVersion, dateSysMetaLastModified);
1368
	}
1369
1370
	@Override
1371
	public Log getLogRecords(Date fromDate, Date toDate, Event event, String pidFilter,
1372
			Integer start, Integer count) throws InvalidRequest, InvalidToken,
1373
			NotAuthorized, NotImplemented, ServiceFailure {
1374
1375
		return getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
1376
	}
1377
1378
	@Override
1379
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
1380
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
1381
1382
		return describe(null, pid);
1383
	}
1384
1385
	@Override
1386
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
1387
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
1388
1389
		return get(null, pid);
1390
	}
1391
1392
	@Override
1393
	public Checksum getChecksum(Identifier pid, String algorithm)
1394
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
1395
			ServiceFailure, NotFound {
1396
1397
		return getChecksum(null, pid, algorithm);
1398
	}
1399
1400
	@Override
1401
	public SystemMetadata getSystemMetadata(Identifier pid)
1402
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1403
			NotFound {
1404
1405
		return getSystemMetadata(null, pid);
1406
	}
1407
1408
	@Override
1409
	public ObjectList listObjects(Date startTime, Date endTime,
1410
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
1411
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
1412
			NotImplemented, ServiceFailure {
1413
1414
		return listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
1415
	}
1416
1417
	@Override
1418
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
1419
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
1420
1421
		return synchronizationFailed(null, syncFailed);
1422
	}
1423
1424
	@Override
1425
	public InputStream getReplica(Identifier pid) throws InvalidToken,
1426
			NotAuthorized, NotImplemented, ServiceFailure, NotFound,
1427
			InsufficientResources {
1428
1429
		return getReplica(null, pid);
1430
	}
1431
1432
	@Override
1433
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
1434
			throws NotImplemented, ServiceFailure, NotAuthorized,
1435
			InvalidRequest, InvalidToken, InsufficientResources,
1436
			UnsupportedType {
1437
1438
		return replicate(null, sysmeta, sourceNode);
1439
	}
1440
1441
	@Override
1442
	public Identifier create(Identifier pid, InputStream object,
1443
			SystemMetadata sysmeta) throws IdentifierNotUnique,
1444
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1445
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1446
			UnsupportedType {
1447
1448
		return create(null, pid, object, sysmeta);
1449
	}
1450
1451
	@Override
1452
	public Identifier delete(Identifier pid) throws InvalidToken,
1453
			ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1454
1455
		return delete(null, pid);
1456
	}
1457
1458
	@Override
1459 7441 leinfelder
	public Identifier generateIdentifier(String scheme, String fragment)
1460 7144 leinfelder
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1461
			InvalidRequest {
1462
1463 7441 leinfelder
		return generateIdentifier(null, scheme, fragment);
1464 7144 leinfelder
	}
1465
1466
	@Override
1467
	public Identifier update(Identifier pid, InputStream object,
1468
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
1469
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
1470
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
1471
			UnsupportedType, NotFound {
1472
1473
		return update(null, pid, object, newPid, sysmeta);
1474
	}
1475 7417 leinfelder
1476
	@Override
1477
	public QueryEngineDescription getQueryEngineDescription(String engine)
1478
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1479
			NotFound {
1480 7772 tao
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1481 8162 tao
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1482
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1483
            }
1484 7634 tao
	        QueryEngineDescription qed = new QueryEngineDescription();
1485 7772 tao
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1486 7634 tao
	        qed.setQueryEngineVersion("1.0");
1487
	        qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1488
	        Vector<String> pathsForIndexing = null;
1489
	        try {
1490
	            pathsForIndexing = SystemUtil.getPathsForIndexing();
1491
	        } catch (MetacatUtilException e) {
1492
	            logMetacat.warn("Could not get index paths", e);
1493
	        }
1494
	        for (String fieldName: pathsForIndexing) {
1495
	            QueryField field = new QueryField();
1496
	            field.addDescription("Indexed field for path '" + fieldName + "'");
1497
	            field.setName(fieldName);
1498
	            field.setReturnable(true);
1499
	            field.setSearchable(true);
1500
	            field.setSortable(false);
1501
	            // TODO: determine type and multivaluedness
1502
	            field.setType(String.class.getName());
1503
	            //field.setMultivalued(true);
1504
	            qed.addQueryField(field);
1505
	        }
1506
	        return qed;
1507 7772 tao
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1508 7781 tao
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1509 7772 tao
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1510 7781 tao
            }
1511 7634 tao
	        try {
1512 7662 tao
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1513 7634 tao
	            return qed;
1514
	        } catch (Exception e) {
1515
	            e.printStackTrace();
1516
	            throw new ServiceFailure("Solr server error", e.getMessage());
1517
	        }
1518
	    } else {
1519
	        throw new NotFound("404", "The Metacat member node can't find the query engine - "+engine);
1520
	    }
1521
1522 7417 leinfelder
	}
1523
1524
	@Override
1525
	public QueryEngineList listQueryEngines() throws InvalidToken,
1526
			ServiceFailure, NotAuthorized, NotImplemented {
1527
		QueryEngineList qel = new QueryEngineList();
1528 7781 tao
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1529
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1530
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1531 7772 tao
		for(String name : enables) {
1532
		    qel.addQueryEngine(name);
1533 7781 tao
		}
1534 7417 leinfelder
		return qel;
1535
	}
1536
1537
	@Override
1538
	public InputStream query(String engine, String query) throws InvalidToken,
1539
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1540
			NotFound {
1541 7648 tao
	    String user = Constants.SUBJECT_PUBLIC;
1542
        String[] groups= null;
1543 7680 tao
        Set<Subject> subjects = null;
1544 7648 tao
        if (session != null) {
1545
            user = session.getSubject().getValue();
1546 7680 tao
            subjects = AuthUtils.authorizedClientSubjects(session);
1547 7648 tao
            if (subjects != null) {
1548
                List<String> groupList = new ArrayList<String>();
1549
                for (Subject subject: subjects) {
1550
                    groupList.add(subject.getValue());
1551
                }
1552
                groups = groupList.toArray(new String[0]);
1553
            }
1554 7680 tao
        } else {
1555
            //add the public user subject to the set
1556
            Subject subject = new Subject();
1557
            subject.setValue(Constants.SUBJECT_PUBLIC);
1558 7757 leinfelder
            subjects = new HashSet<Subject>();
1559 7680 tao
            subjects.add(subject);
1560 7648 tao
        }
1561 7680 tao
        //System.out.println("====== user is "+user);
1562
        //System.out.println("====== groups are "+groups);
1563 7772 tao
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1564 8162 tao
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1565
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1566
            }
1567 7417 leinfelder
			try {
1568
				DBQuery queryobj = new DBQuery();
1569 7648 tao
1570 7417 leinfelder
				String results = queryobj.performPathquery(query, user, groups);
1571 7757 leinfelder
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1572
				ctbais.setContentType("text/xml");
1573
				return ctbais;
1574 7417 leinfelder
1575
			} catch (Exception e) {
1576 7757 leinfelder
				throw new ServiceFailure("Pathquery error", e.getMessage());
1577 7417 leinfelder
			}
1578
1579 7772 tao
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1580 7781 tao
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1581 7772 tao
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1582 7781 tao
		    }
1583 7634 tao
		    logMetacat.info("The query is ==================================== \n"+query);
1584 7620 tao
		    try {
1585 7634 tao
1586 7680 tao
                return MetacatSolrIndex.getInstance().query(query, subjects);
1587 7620 tao
            } catch (Exception e) {
1588
                // TODO Auto-generated catch block
1589
                throw new ServiceFailure("Solr server error", e.getMessage());
1590
            }
1591 7417 leinfelder
		}
1592
		return null;
1593
	}
1594 7849 leinfelder
1595
	/**
1596
	 * Given an existing Science Metadata PID, this method mints a DOI
1597
	 * and updates the original object "publishing" the update with the DOI.
1598
	 * This includes updating the ORE map that describes the Science Metadata+data.
1599
	 * TODO: ensure all referenced objects allow public read
1600
	 *
1601
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1602
	 *
1603
	 * @param originalIdentifier
1604
	 * @param request
1605 7864 leinfelder
	 * @throws InvalidRequest
1606
	 * @throws NotImplemented
1607
	 * @throws NotAuthorized
1608
	 * @throws ServiceFailure
1609
	 * @throws InvalidToken
1610 7849 leinfelder
	 * @throws NotFound
1611 7864 leinfelder
	 * @throws InvalidSystemMetadata
1612
	 * @throws InsufficientResources
1613
	 * @throws UnsupportedType
1614
	 * @throws IdentifierNotUnique
1615 7849 leinfelder
	 */
1616 7864 leinfelder
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata {
1617 7849 leinfelder
1618 8141 leinfelder
1619
		// get the original SM
1620
		SystemMetadata originalSystemMetadata = this.getSystemMetadata(session, originalIdentifier);
1621
1622
		// make copy of it
1623
		SystemMetadata sysmeta = new SystemMetadata();
1624
		try {
1625
			BeanUtils.copyProperties(sysmeta, originalSystemMetadata);
1626
		} catch (Exception e) {
1627
			// report as service failure
1628
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1629
			sf.initCause(e);
1630
			throw sf;
1631
		}
1632
1633 7849 leinfelder
		// mint a DOI for the new revision
1634
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1635 8141 leinfelder
1636
		// set new metadata values
1637 7849 leinfelder
		sysmeta.setIdentifier(newIdentifier);
1638
		sysmeta.setObsoletes(originalIdentifier);
1639
		sysmeta.setObsoletedBy(null);
1640
1641
		// get the bytes
1642
		InputStream inputStream = this.get(session, originalIdentifier);
1643
1644
		// update the object
1645 7864 leinfelder
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
1646 7849 leinfelder
1647 7864 leinfelder
		// update ORE that references the scimeta
1648 8190 leinfelder
		// first try the naive method, then check the SOLR index
1649 7849 leinfelder
		try {
1650 7864 leinfelder
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1651 7849 leinfelder
1652 7864 leinfelder
			Identifier potentialOreIdentifier = new Identifier();
1653
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1654 7849 leinfelder
1655 7864 leinfelder
			InputStream oreInputStream = null;
1656
			try {
1657
				oreInputStream = this.get(session, potentialOreIdentifier);
1658
			} catch (NotFound nf) {
1659
				// this is probably okay for many sci meta data docs
1660
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1661 8190 leinfelder
				// try the SOLR index
1662 8200 leinfelder
				List<Identifier> potentialOreIdentifiers = this.lookupOreFor(originalIdentifier, false);
1663 8190 leinfelder
				if (potentialOreIdentifiers != null) {
1664
					potentialOreIdentifier = potentialOreIdentifiers.get(0);
1665
					try {
1666
						oreInputStream = this.get(session, potentialOreIdentifier);
1667
					} catch (NotFound nf2) {
1668
						// this is probably okay for many sci meta data docs
1669
						logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1670
					}
1671
				}
1672 7864 leinfelder
			}
1673
			if (oreInputStream != null) {
1674
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1675
1676
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1677
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1678
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1679
1680
				// TODO: ensure all data package objects allow public read
1681
1682
				// reconstruct the ORE with the new identifiers
1683
				sciMetaMap.remove(originalIdentifier);
1684
				sciMetaMap.put(newIdentifier, dataIdentifiers);
1685
1686
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1687
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1688
1689
				// get the original ORE SM and update the values
1690 8141 leinfelder
				SystemMetadata originalOreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1691
				SystemMetadata oreSysMeta = new SystemMetadata();
1692
				try {
1693
					BeanUtils.copyProperties(oreSysMeta, originalOreSysMeta);
1694
				} catch (Exception e) {
1695
					// report as service failure
1696
					ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1697
					sf.initCause(e);
1698
					throw sf;
1699
				}
1700
1701 7864 leinfelder
				oreSysMeta.setIdentifier(newOreIdentifier);
1702
				oreSysMeta.setObsoletes(potentialOreIdentifier);
1703
				oreSysMeta.setObsoletedBy(null);
1704
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
1705
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
1706
1707
				// save the updated ORE
1708
				this.update(
1709
						session,
1710
						potentialOreIdentifier,
1711
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")),
1712
						newOreIdentifier,
1713
						oreSysMeta);
1714
1715 8362 leinfelder
			} else {
1716
				// create a new ORE for them
1717
				// https://projects.ecoinformatics.org/ecoinfo/issues/6194
1718
				try {
1719
					// find the local id for the NEW package.
1720
					String newLocalId = IdentifierManager.getInstance().getLocalId(newIdentifier.getValue());
1721
1722
					@SuppressWarnings("unused")
1723
					SystemMetadata extraSysMeta = SystemMetadataFactory.createSystemMetadata(newLocalId, true, false);
1724
					// should be done generating the ORE here
1725
1726
				} catch (Exception e) {
1727
					// oops, guess there was a problem - no package for you
1728
					logMetacat.error("Could not generate new ORE for published object: " + newIdentifier.getValue(), e);
1729
				}
1730 7864 leinfelder
			}
1731
		} catch (McdbDocNotFoundException e) {
1732
			// report as service failure
1733
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1734
			sf.initCause(e);
1735
			throw sf;
1736
		} catch (UnsupportedEncodingException e) {
1737
			// report as service failure
1738
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1739
			sf.initCause(e);
1740
			throw sf;
1741
		} catch (OREException e) {
1742
			// report as service failure
1743
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1744
			sf.initCause(e);
1745
			throw sf;
1746
		} catch (URISyntaxException e) {
1747
			// report as service failure
1748
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1749
			sf.initCause(e);
1750
			throw sf;
1751
		} catch (OREParserException e) {
1752
			// report as service failure
1753
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1754
			sf.initCause(e);
1755
			throw sf;
1756
		} catch (ORESerialiserException e) {
1757
			// report as service failure
1758
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1759
			sf.initCause(e);
1760
			throw sf;
1761
		} catch (NoSuchAlgorithmException e) {
1762
			// report as service failure
1763
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1764
			sf.initCause(e);
1765
			throw sf;
1766 7849 leinfelder
		}
1767
1768
		return newIdentifier;
1769
	}
1770 7850 leinfelder
1771
	/**
1772 8190 leinfelder
	 * Determines if we already have registered an ORE map for this package
1773
	 * NOTE: uses a solr query to locate OREs for the object
1774
	 * @param guid of the EML/packaging object
1775
	 * @return list of resource map identifiers for the given pid
1776
	 */
1777 8200 leinfelder
	public List<Identifier> lookupOreFor(Identifier guid, boolean includeObsolete) {
1778 8190 leinfelder
		// Search for the ORE if we can find it
1779
		String pid = guid.getValue();
1780
		List<Identifier> retList = null;
1781
		try {
1782 8200 leinfelder
			String query = "fl=id,resourceMap&wt=xml&q=-obsoletedBy:*+resourceMap:*+id:\"" + pid + "\"";;
1783
			if (includeObsolete) {
1784
				query = "fl=id,resourceMap&wt=xml&q=resourceMap:*+id:\"" + pid + "\"";
1785
			}
1786
1787 8190 leinfelder
			InputStream results = this.query("solr", query);
1788
			org.w3c.dom.Node rootNode = XMLUtilities.getXMLReaderAsDOMTreeRootNode(new InputStreamReader(results, "UTF-8"));
1789
			//String resultString = XMLUtilities.getDOMTreeAsString(rootNode);
1790
			org.w3c.dom.NodeList nodeList = XMLUtilities.getNodeListWithXPath(rootNode, "//arr[@name=\"resourceMap\"]/str");
1791
			if (nodeList != null && nodeList.getLength() > 0) {
1792
				retList = new ArrayList<Identifier>();
1793
				for (int i = 0; i < nodeList.getLength(); i++) {
1794
					String found = nodeList.item(i).getFirstChild().getNodeValue();
1795
					Identifier oreId = new Identifier();
1796
					oreId.setValue(found);
1797
					retList.add(oreId);
1798
				}
1799
			}
1800
		} catch (Exception e) {
1801
			logMetacat.error("Error checking for resourceMap[s] on pid " + pid + ". " + e.getMessage(), e);
1802
		}
1803
1804
		return retList;
1805
	}
1806
1807
	/**
1808 7850 leinfelder
	 * Packages the given package in a Bagit collection for download
1809
	 * @param pid
1810
	 * @throws NotImplemented
1811
	 * @throws NotFound
1812
	 * @throws NotAuthorized
1813
	 * @throws ServiceFailure
1814
	 * @throws InvalidToken
1815
	 */
1816 7855 leinfelder
	public InputStream getPackage(Session session, Identifier pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1817
1818 7850 leinfelder
		InputStream bagInputStream = null;
1819
		BagFactory bagFactory = new BagFactory();
1820
		Bag bag = bagFactory.createBag();
1821
1822
		// track the temp files we use so we can delete them when finished
1823
		List<File> tempFiles = new ArrayList<File>();
1824
1825
		// the pids to include in the package
1826
		List<Identifier> packagePids = new ArrayList<Identifier>();
1827
1828 7855 leinfelder
		// catch non-D1 service errors and throw as ServiceFailures
1829
		try {
1830
1831
			// find the package contents
1832
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1833 8025 leinfelder
			if (ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
1834 7855 leinfelder
				InputStream oreInputStream = this.get(session, pid);
1835
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1836
				packagePids.addAll(resourceMapStructure.keySet());
1837
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
1838
					packagePids.addAll(entries.keySet());
1839
					for (List<Identifier> dataPids: entries.values()) {
1840
						packagePids.addAll(dataPids);
1841
					}
1842 7850 leinfelder
				}
1843 7855 leinfelder
			} else {
1844
				// just the lone pid in this package
1845
				packagePids.add(pid);
1846 7850 leinfelder
			}
1847 7855 leinfelder
1848
			// track the pid-to-file mapping
1849
			StringBuffer pidMapping = new StringBuffer();
1850
			// loop through the package contents
1851
			for (Identifier entryPid: packagePids) {
1852
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);
1853
				String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
1854
		        String prefix = entryPid.getValue();
1855
		        prefix = "entry";
1856 8160 leinfelder
				File tempFile = File.createTempFile(prefix + "-", extension);
1857 7855 leinfelder
				tempFiles.add(tempFile);
1858
				InputStream entryInputStream = this.get(session, entryPid);
1859
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
1860
				bag.addFileToPayload(tempFile);
1861
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
1862
			}
1863
1864
			//add the the pid to data file map
1865 8160 leinfelder
			File pidMappingFile = File.createTempFile("pid-mapping-", ".txt");
1866 7855 leinfelder
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
1867
			bag.addFileAsTag(pidMappingFile);
1868 8026 leinfelder
			tempFiles.add(pidMappingFile);
1869
1870 7855 leinfelder
			bag = bag.makeComplete();
1871 8026 leinfelder
1872 8160 leinfelder
			// TODO: consider using mangled-PID for filename
1873
			File bagFile = File.createTempFile("dataPackage-", ".zip");
1874
1875 7855 leinfelder
			bag.setFile(bagFile);
1876 7860 leinfelder
			ZipWriter zipWriter = new ZipWriter(bagFactory);
1877 7855 leinfelder
			bag.write(zipWriter, bagFile);
1878
			bagFile = bag.getFile();
1879 8026 leinfelder
			// use custom FIS that will delete the file when closed
1880
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
1881
			// also mark for deletion on shutdown in case the stream is never closed
1882
			bagFile.deleteOnExit();
1883 7855 leinfelder
1884 8026 leinfelder
			// clean up other temp files
1885 7855 leinfelder
			for (File tf: tempFiles) {
1886
				tf.delete();
1887
			}
1888
		} catch (IOException e) {
1889
			// report as service failure
1890
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1891
			sf.initCause(e);
1892
			throw sf;
1893
		} catch (OREException e) {
1894
			// report as service failure
1895
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1896
			sf.initCause(e);
1897
			throw sf;
1898
		} catch (URISyntaxException e) {
1899
			// report as service failure
1900
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1901
			sf.initCause(e);
1902
			throw sf;
1903
		} catch (OREParserException e) {
1904
			// report as service failure
1905
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1906
			sf.initCause(e);
1907
			throw sf;
1908 7850 leinfelder
		}
1909
1910
		return bagInputStream;
1911
1912
	}
1913 7860 leinfelder
1914
	/**
1915
	 * Get a rendered view of the object identified by pid.
1916
	 * Uses the registered format given by the format parameter.
1917
	 * Typically, this is structured HTML that can be styled with CSS.
1918
	 * @param session
1919
	 * @param pid
1920
	 * @param format
1921
	 * @return
1922
	 * @throws InvalidToken
1923
	 * @throws ServiceFailure
1924
	 * @throws NotAuthorized
1925
	 * @throws NotFound
1926
	 * @throws NotImplemented
1927
	 */
1928 7861 leinfelder
	public InputStream getView(Session session, Identifier pid, String format) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
1929 7860 leinfelder
		InputStream resultInputStream = null;
1930
1931
		SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1932
		InputStream object = this.get(session, pid);
1933
1934
		try {
1935
			// can only transform metadata, really
1936 8013 leinfelder
			ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
1937 7860 leinfelder
			if (objectFormat.getFormatType().equals("METADATA")) {
1938
				// transform
1939
				DBTransform transformer = new DBTransform();
1940
	            String documentContent = IOUtils.toString(object, "UTF-8");
1941
	            String sourceType = objectFormat.getFormatId().getValue();
1942
	            String targetType = "-//W3C//HTML//EN";
1943
	            ByteArrayOutputStream baos = new ByteArrayOutputStream();
1944
	            Writer writer = new OutputStreamWriter(baos , "UTF-8");
1945
	            // TODO: include more params?
1946
	            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
1947 8014 leinfelder
	            String localId = null;
1948
				try {
1949
					localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1950
				} catch (McdbDocNotFoundException e) {
1951
					throw new NotFound("1020", e.getMessage());
1952
				}
1953
	            params.put("qformat", new String[] {format});
1954
	            params.put("docid", new String[] {localId});
1955
	            params.put("pid", new String[] {pid.getValue()});
1956 7860 leinfelder
	            transformer.transformXMLDocument(
1957
	                    documentContent ,
1958
	                    sourceType,
1959
	                    targetType ,
1960
	                    format,
1961
	                    writer,
1962
	                    params,
1963
	                    null //sessionid
1964
	                    );
1965
1966
	            // finally, get the HTML back
1967
	            resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
1968
	            ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
1969
1970
			} else {
1971
				// just return the raw bytes
1972
				resultInputStream = object;
1973
			}
1974
		} catch (IOException e) {
1975
			// report as service failure
1976
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1977
			sf.initCause(e);
1978
			throw sf;
1979
		} catch (PropertyNotFoundException e) {
1980
			// report as service failure
1981
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1982
			sf.initCause(e);
1983
			throw sf;
1984
		} catch (SQLException e) {
1985
			// report as service failure
1986
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1987
			sf.initCause(e);
1988
			throw sf;
1989
		} catch (ClassNotFoundException e) {
1990
			// report as service failure
1991
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1992
			sf.initCause(e);
1993
			throw sf;
1994
		}
1995
1996
		return resultInputStream;
1997
1998 8026 leinfelder
	}
1999 6795 cjones
2000 6179 cjones
}