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