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