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 9387 walker
import java.io.StringReader;
36 7849 leinfelder
import java.io.UnsupportedEncodingException;
37 7860 leinfelder
import java.io.Writer;
38 7021 leinfelder
import java.math.BigInteger;
39 7849 leinfelder
import java.net.URISyntaxException;
40 9352 walker
import java.nio.charset.Charset;
41 6228 cjones
import java.security.NoSuchAlgorithmException;
42 7860 leinfelder
import java.sql.SQLException;
43 7417 leinfelder
import java.util.ArrayList;
44 6525 leinfelder
import java.util.Calendar;
45 6179 cjones
import java.util.Date;
46 8437 walker
import java.util.HashMap;
47 7680 tao
import java.util.HashSet;
48 7860 leinfelder
import java.util.Hashtable;
49 6250 cjones
import java.util.List;
50 7849 leinfelder
import java.util.Map;
51 7417 leinfelder
import java.util.Set;
52 6389 leinfelder
import java.util.Timer;
53 7489 leinfelder
import java.util.UUID;
54 7418 leinfelder
import java.util.Vector;
55 10104 tao
import java.util.concurrent.ExecutorService;
56
import java.util.concurrent.Executors;
57 6179 cjones
58 6542 leinfelder
import javax.servlet.http.HttpServletRequest;
59 9387 walker
import javax.xml.transform.Result;
60
import javax.xml.transform.Source;
61
import javax.xml.transform.TransformerException;
62
import javax.xml.transform.TransformerFactory;
63
import javax.xml.transform.dom.DOMSource;
64
import javax.xml.transform.stream.StreamResult;
65 6542 leinfelder
66 6258 cjones
import org.apache.commons.io.IOUtils;
67 9997 tao
import org.apache.commons.lang.NotImplementedException;
68 6179 cjones
import org.apache.log4j.Logger;
69 8810 leinfelder
import org.dataone.client.v2.CNode;
70
import org.dataone.client.v2.itk.D1Client;
71
import org.dataone.client.v2.MNode;
72
import org.dataone.client.v2.formats.ObjectFormatCache;
73 6552 leinfelder
import org.dataone.client.auth.CertificateManager;
74 8810 leinfelder
import org.dataone.client.v2.formats.ObjectFormatInfo;
75 6552 leinfelder
import org.dataone.configuration.Settings;
76 7849 leinfelder
import org.dataone.ore.ResourceMapFactory;
77 6795 cjones
import org.dataone.service.exceptions.BaseException;
78 6179 cjones
import org.dataone.service.exceptions.IdentifierNotUnique;
79
import org.dataone.service.exceptions.InsufficientResources;
80
import org.dataone.service.exceptions.InvalidRequest;
81
import org.dataone.service.exceptions.InvalidSystemMetadata;
82
import org.dataone.service.exceptions.InvalidToken;
83
import org.dataone.service.exceptions.NotAuthorized;
84
import org.dataone.service.exceptions.NotFound;
85
import org.dataone.service.exceptions.NotImplemented;
86
import org.dataone.service.exceptions.ServiceFailure;
87 6185 leinfelder
import org.dataone.service.exceptions.SynchronizationFailed;
88 6179 cjones
import org.dataone.service.exceptions.UnsupportedType;
89 8810 leinfelder
import org.dataone.service.mn.tier1.v2.MNCore;
90
import org.dataone.service.mn.tier1.v2.MNRead;
91
import org.dataone.service.mn.tier2.v2.MNAuthorization;
92
import org.dataone.service.mn.tier3.v2.MNStorage;
93
import org.dataone.service.mn.tier4.v2.MNReplication;
94
import org.dataone.service.mn.v2.MNPackage;
95
import org.dataone.service.mn.v2.MNQuery;
96
import org.dataone.service.mn.v2.MNView;
97 8591 leinfelder
import org.dataone.service.types.v1.AccessRule;
98 6366 leinfelder
import org.dataone.service.types.v1.Checksum;
99 7144 leinfelder
import org.dataone.service.types.v1.DescribeResponse;
100 6366 leinfelder
import org.dataone.service.types.v1.Event;
101
import org.dataone.service.types.v1.Identifier;
102 8810 leinfelder
import org.dataone.service.types.v2.Log;
103
import org.dataone.service.types.v2.LogEntry;
104
import org.dataone.service.types.v2.OptionList;
105 6366 leinfelder
import org.dataone.service.types.v1.MonitorInfo;
106
import org.dataone.service.types.v1.MonitorList;
107 8810 leinfelder
import org.dataone.service.types.v2.Node;
108
import org.dataone.service.types.v2.NodeList;
109 6366 leinfelder
import org.dataone.service.types.v1.NodeReference;
110
import org.dataone.service.types.v1.NodeState;
111
import org.dataone.service.types.v1.NodeType;
112 8810 leinfelder
import org.dataone.service.types.v2.ObjectFormat;
113 9353 tao
import org.dataone.service.types.v1.Group;
114 6366 leinfelder
import org.dataone.service.types.v1.ObjectFormatIdentifier;
115
import org.dataone.service.types.v1.ObjectList;
116
import org.dataone.service.types.v1.Permission;
117
import org.dataone.service.types.v1.Ping;
118 9340 tao
import org.dataone.service.types.v1.Replica;
119 6528 cjones
import org.dataone.service.types.v1.ReplicationStatus;
120 6366 leinfelder
import org.dataone.service.types.v1.Schedule;
121
import org.dataone.service.types.v1.Service;
122
import org.dataone.service.types.v1.Services;
123
import org.dataone.service.types.v1.Session;
124
import org.dataone.service.types.v1.Subject;
125
import org.dataone.service.types.v1.Synchronization;
126 8810 leinfelder
import org.dataone.service.types.v2.SystemMetadata;
127 7417 leinfelder
import org.dataone.service.types.v1.util.AuthUtils;
128 6366 leinfelder
import org.dataone.service.types.v1.util.ChecksumUtil;
129 7417 leinfelder
import org.dataone.service.types.v1_1.QueryEngineDescription;
130
import org.dataone.service.types.v1_1.QueryEngineList;
131 7418 leinfelder
import org.dataone.service.types.v1_1.QueryField;
132 6476 jones
import org.dataone.service.util.Constants;
133 8594 leinfelder
import org.dataone.service.util.TypeMarshaller;
134 7849 leinfelder
import org.dspace.foresite.OREException;
135
import org.dspace.foresite.OREParserException;
136
import org.dspace.foresite.ORESerialiserException;
137
import org.dspace.foresite.ResourceMap;
138 8437 walker
import org.ecoinformatics.datamanager.parser.DataPackage;
139
import org.ecoinformatics.datamanager.parser.Entity;
140
import org.ecoinformatics.datamanager.parser.generic.DataPackageParserInterface;
141
import org.ecoinformatics.datamanager.parser.generic.Eml200DataPackageParser;
142 9387 walker
import org.w3c.dom.Document;
143 6179 cjones
144 7448 leinfelder
import edu.ucsb.nceas.ezid.EZIDException;
145 7417 leinfelder
import edu.ucsb.nceas.metacat.DBQuery;
146 7860 leinfelder
import edu.ucsb.nceas.metacat.DBTransform;
147 6234 cjones
import edu.ucsb.nceas.metacat.EventLog;
148 6230 cjones
import edu.ucsb.nceas.metacat.IdentifierManager;
149 6234 cjones
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
150 7417 leinfelder
import edu.ucsb.nceas.metacat.MetaCatServlet;
151 6389 leinfelder
import edu.ucsb.nceas.metacat.MetacatHandler;
152 9478 tao
import edu.ucsb.nceas.metacat.ReadOnlyChecker;
153 7772 tao
import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines;
154 7757 leinfelder
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
155 6648 leinfelder
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
156 7662 tao
import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler;
157 7620 tao
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
158 10172 leinfelder
import edu.ucsb.nceas.metacat.mdq.MDQClient;
159 6340 cjones
import edu.ucsb.nceas.metacat.properties.PropertyService;
160 7418 leinfelder
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
161 8026 leinfelder
import edu.ucsb.nceas.metacat.util.DeleteOnCloseFileInputStream;
162 7441 leinfelder
import edu.ucsb.nceas.metacat.util.DocumentUtil;
163 8810 leinfelder
import edu.ucsb.nceas.metacat.util.SkinUtil;
164 6542 leinfelder
import edu.ucsb.nceas.metacat.util.SystemUtil;
165 6340 cjones
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
166 8190 leinfelder
import edu.ucsb.nceas.utilities.XMLUtilities;
167 8800 leinfelder
import edu.ucsb.nceas.utilities.export.HtmlToPdf;
168 7850 leinfelder
import gov.loc.repository.bagit.Bag;
169
import gov.loc.repository.bagit.BagFactory;
170
import gov.loc.repository.bagit.writer.impl.ZipWriter;
171 6230 cjones
172 6179 cjones
/**
173
 * Represents Metacat's implementation of the DataONE Member Node
174
 * service API. Methods implement the various MN* interfaces, and methods common
175
 * to both Member Node and Coordinating Node interfaces are found in the
176
 * D1NodeService base class.
177 6288 cjones
 *
178
 * Implements:
179
 * MNCore.ping()
180
 * MNCore.getLogRecords()
181
 * MNCore.getObjectStatistics()
182
 * MNCore.getOperationStatistics()
183
 * MNCore.getStatus()
184
 * MNCore.getCapabilities()
185
 * MNRead.get()
186
 * MNRead.getSystemMetadata()
187
 * MNRead.describe()
188
 * MNRead.getChecksum()
189
 * MNRead.listObjects()
190
 * MNRead.synchronizationFailed()
191
 * MNAuthorization.isAuthorized()
192
 * MNAuthorization.setAccessPolicy()
193
 * MNStorage.create()
194
 * MNStorage.update()
195
 * MNStorage.delete()
196 9177 tao
 * MNStorage.updateSystemMetadata()
197 6288 cjones
 * MNReplication.replicate()
198
 *
199 6179 cjones
 */
200 6599 cjones
public class MNodeService extends D1NodeService
201 8810 leinfelder
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage, MNQuery, MNView, MNPackage {
202 6179 cjones
203 7772 tao
    //private static final String PATHQUERY = "pathquery";
204 7849 leinfelder
	public static final String UUID_SCHEME = "UUID";
205
	public static final String DOI_SCHEME = "DOI";
206 7489 leinfelder
	private static final String UUID_PREFIX = "urn:uuid:";
207 9387 walker
208
	private static String XPATH_EML_ID = "/eml:eml/@packageId";
209 7418 leinfelder
210
	/* the logger instance */
211 6475 jones
    private Logger logMetacat = null;
212 6795 cjones
213
    /* A reference to a remote Memeber Node */
214 9295 tao
    //private MNode mn;
215 6795 cjones
216
    /* A reference to a Coordinating Node */
217
    private CNode cn;
218 10104 tao
219
    // shared executor
220
    private static ExecutorService executor = null;
221 6241 cjones
222 10104 tao
    static {
223
        // use a shared executor service with nThreads == one less than available processors
224
        int availableProcessors = Runtime.getRuntime().availableProcessors();
225
        int nThreads = availableProcessors * 1;
226
        nThreads--;
227
        nThreads = Math.max(1, nThreads);
228
        executor = Executors.newFixedThreadPool(nThreads);
229
    }
230 6795 cjones
231 10104 tao
232 6475 jones
    /**
233
     * Singleton accessor to get an instance of MNodeService.
234
     *
235
     * @return instance - the instance of MNodeService
236
     */
237 6542 leinfelder
    public static MNodeService getInstance(HttpServletRequest request) {
238
        return new MNodeService(request);
239 6179 cjones
    }
240
241 6475 jones
    /**
242
     * Constructor, private for singleton access
243
     */
244 6542 leinfelder
    private MNodeService(HttpServletRequest request) {
245
        super(request);
246 6475 jones
        logMetacat = Logger.getLogger(MNodeService.class);
247 6552 leinfelder
248
        // set the Member Node certificate file location
249
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
250 6310 cjones
    }
251 6475 jones
252
    /**
253
     * Deletes an object from the Member Node, where the object is either a
254
     * data object or a science metadata object.
255
     *
256
     * @param session - the Session object containing the credentials for the Subject
257
     * @param pid - The object identifier to be deleted
258
     *
259
     * @return pid - the identifier of the object used for the deletion
260
     *
261
     * @throws InvalidToken
262
     * @throws ServiceFailure
263
     * @throws NotAuthorized
264
     * @throws NotFound
265
     * @throws NotImplemented
266
     * @throws InvalidRequest
267
     */
268
    @Override
269
    public Identifier delete(Session session, Identifier pid)
270 6610 cjones
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
271 6475 jones
272 9478 tao
        if(isReadOnlyMode()) {
273 9481 tao
            throw new ServiceFailure("2902", ReadOnlyChecker.DATAONEERROR);
274 9478 tao
        }
275 7162 leinfelder
    	// only admin of  the MN or the CN is allowed a full delete
276
        boolean allowed = false;
277 7330 leinfelder
        allowed = isAdminAuthorized(session);
278 8360 tao
279 9050 tao
        String serviceFailureCode = "2902";
280
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
281
        if(sid != null) {
282
            pid = sid;
283
        }
284
285 8360 tao
        //check if it is the authoritative member node
286
        if(!allowed) {
287
            allowed = isAuthoritativeMNodeAdmin(session, pid);
288
        }
289
290 7162 leinfelder
        if (!allowed) {
291 7245 cjones
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
292 7162 leinfelder
        }
293
294 7077 leinfelder
    	// defer to superclass implementation
295
        return super.delete(session, pid);
296 6250 cjones
    }
297
298 6475 jones
    /**
299
     * Updates an existing object by creating a new object identified by
300
     * newPid on the Member Node which explicitly obsoletes the object
301
     * identified by pid through appropriate changes to the SystemMetadata
302
     * of pid and newPid
303
     *
304
     * @param session - the Session object containing the credentials for the Subject
305
     * @param pid - The identifier of the object to be updated
306
     * @param object - the new object bytes
307
     * @param sysmeta - the new system metadata describing the object
308
     *
309
     * @return newPid - the identifier of the new object
310
     *
311
     * @throws InvalidToken
312
     * @throws ServiceFailure
313
     * @throws NotAuthorized
314
     * @throws NotFound
315
     * @throws NotImplemented
316
     * @throws IdentifierNotUnique
317
     * @throws UnsupportedType
318
     * @throws InsufficientResources
319
     * @throws InvalidSystemMetadata
320
     * @throws InvalidRequest
321
     */
322
    @Override
323 6575 cjones
    public Identifier update(Session session, Identifier pid, InputStream object,
324
        Identifier newPid, SystemMetadata sysmeta)
325
        throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique,
326
        UnsupportedType, InsufficientResources, NotFound,
327
        InvalidSystemMetadata, NotImplemented, InvalidRequest {
328 9781 cjones
329 10118 tao
        long startTime = System.currentTimeMillis();
330
331 9781 cjones
        if(isReadOnlyMode()) {
332
            throw new ServiceFailure("1310", ReadOnlyChecker.DATAONEERROR);
333
        }
334
335
        //transform a sid to a pid if it is applicable
336
        String serviceFailureCode = "1310";
337
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
338
        if(sid != null) {
339
            pid = sid;
340
        }
341
342
        String localId = null;
343
        boolean allowed = false;
344
        boolean isScienceMetadata = false;
345
346
        if (session == null) {
347
        	throw new InvalidToken("1210", "No session has been provided");
348
        }
349
        Subject subject = session.getSubject();
350
351
        // verify the pid is valid format
352
        if (!isValidIdentifier(pid)) {
353
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
354
        }
355
356
        // verify the new pid is valid format
357
        if (!isValidIdentifier(newPid)) {
358
            throw new InvalidRequest("1202", "The provided identifier is invalid.");
359
        }
360
361
        // make sure that the newPid doesn't exists
362
        boolean idExists = true;
363 9600 tao
        try {
364 9781 cjones
            idExists = IdentifierManager.getInstance().identifierExists(newPid.getValue());
365
        } catch (SQLException e) {
366
            throw new ServiceFailure("1310",
367
                                    "The requested identifier " + newPid.getValue() +
368
                                    " couldn't be determined if it is unique since : "+e.getMessage());
369
        }
370
        if (idExists) {
371
                throw new IdentifierNotUnique("1220",
372
                          "The requested identifier " + newPid.getValue() +
373
                          " is already used by another object and" +
374
                          "therefore can not be used for this object. Clients should choose" +
375
                          "a new identifier that is unique and retry the operation or " +
376
                          "use CN.reserveIdentifier() to reserve one.");
377 9044 tao
378 9781 cjones
        }
379
380
381
382
        // check for the existing identifier
383
        try {
384
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
385 6575 cjones
386 9781 cjones
        } catch (McdbDocNotFoundException e) {
387
            throw new InvalidRequest("1202", "The object with the provided " +
388
                "identifier was not found.");
389 6575 cjones
390 9781 cjones
        } catch (SQLException ee) {
391
            throw new ServiceFailure("1310", "The object with the provided " +
392
                    "identifier "+pid.getValue()+" can't be identified since - "+ee.getMessage());
393
        }
394
395 10118 tao
        long end =System.currentTimeMillis();
396
        logMetacat.debug("MNodeService.update - the time spending on checking the validation of the old pid "+pid.getValue()+" and the new pid "+newPid.getValue()+" is "+(end- startTime)+ " milli seconds.");
397
398 9781 cjones
        // set the originating node
399
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
400
        sysmeta.setOriginMemberNode(originMemberNode);
401
402
        // set the submitter to match the certificate
403
        sysmeta.setSubmitter(subject);
404
        // set the dates
405
        Date now = Calendar.getInstance().getTime();
406
        sysmeta.setDateSysMetadataModified(now);
407
        sysmeta.setDateUploaded(now);
408
409
        // make sure serial version is set to something
410
        BigInteger serialVersion = sysmeta.getSerialVersion();
411
        if (serialVersion == null) {
412
        	sysmeta.setSerialVersion(BigInteger.ZERO);
413
        }
414 10118 tao
        long startTime2 = System.currentTimeMillis();
415 9781 cjones
        // does the subject have WRITE ( == update) priveleges on the pid?
416
        //allowed = isAuthorized(session, pid, Permission.WRITE);
417
        //CN having the permission is allowed; user with the write permission and calling on the authoritative node is allowed.
418 9997 tao
        try {
419
            allowed = allowUpdating(session, pid, Permission.WRITE);
420
        }   catch (NotFound e) {
421
            throw new NotFound("1280", "Can't determine if the client has the permission to update the object with id "+pid.getValue()+" since "+e.getDescription());
422
        } catch(ServiceFailure e) {
423
            throw new ServiceFailure("1310", "Can't determine if the client has the permission to update the object with id "+pid.getValue()+" since "+e.getDescription());
424
        } catch(NotAuthorized e) {
425
            throw new NotAuthorized("1200", "Can't determine if the client has the permission to update the object with id "+pid.getValue()+" since "+e.getDescription());
426
        } catch(NotImplemented e) {
427
            throw new NotImplemented("1201","Can't determine if the client has the permission to update he object with id "+pid.getValue()+" since "+e.getDescription());
428
        } catch(InvalidRequest e) {
429
            throw new InvalidRequest("1202", "Can't determine if the client has the permission to update the object with id "+pid.getValue()+" since "+e.getDescription());
430
        } catch(InvalidToken e) {
431
            throw new InvalidToken("1210", "Can't determine if the client has the permission to update the object with id "+pid.getValue()+" since "+e.getDescription());
432
        }
433
434 10118 tao
        end =System.currentTimeMillis();
435
        logMetacat.debug("MNodeService.update - the time spending on checking if the user has the permission to update the old pid "+pid.getValue()+" with the new pid "+newPid.getValue()+" is "+(end- startTime2)+ " milli seconds.");
436
437 9781 cjones
        if (allowed) {
438 10118 tao
            long startTime3 = System.currentTimeMillis();
439 9781 cjones
        	// check quality of SM
440
        	if (sysmeta.getObsoletedBy() != null) {
441
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
442
        	}
443
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
444
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
445
        	}
446
447
            // get the existing system metadata for the object
448
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
449
            //System.out.println("the archive is "+existingSysMeta.getArchived());
450
            //Base on documentation, we can't update an archived object:
451
            //The update operation MUST fail with Exceptions.InvalidRequest on objects that have the Types.SystemMetadata.archived property set to true.
452
            if(existingSysMeta.getArchived() != null && existingSysMeta.getArchived()) {
453
                throw new InvalidRequest("1202","An archived object"+pid.getValue()+" can't be updated");
454 9200 tao
            }
455 9781 cjones
456
            // check for previous update
457
            // see: https://redmine.dataone.org/issues/3336
458
            Identifier existingObsoletedBy = existingSysMeta.getObsoletedBy();
459
            if (existingObsoletedBy != null) {
460
            	throw new InvalidRequest("1202",
461
            			"The previous identifier has already been made obsolete by: " + existingObsoletedBy.getValue());
462 7400 leinfelder
            }
463 10118 tao
            end =System.currentTimeMillis();
464
            logMetacat.debug("MNodeService.update - the time spending on checking the quality of the system metadata of the old pid "+pid.getValue()+" and the new pid "+newPid.getValue()+" is "+(end- startTime3)+ " milli seconds.");
465
466 9781 cjones
            //check the sid in the system metadata. If it exists, it should be non-exist or match the old sid in the previous system metadata.
467
            Identifier sidInSys = sysmeta.getSeriesId();
468
            if(sidInSys != null) {
469
                if (!isValidIdentifier(sidInSys)) {
470
                    throw new InvalidSystemMetadata("1300", "The provided series id in the system metadata is invalid.");
471 9046 tao
                }
472 9781 cjones
                Identifier previousSid = existingSysMeta.getSeriesId();
473
                if(previousSid != null) {
474
                    // there is a previous sid, if the new sid doesn't match it, the new sid should be non-existing.
475
                    if(!sidInSys.getValue().equals(previousSid.getValue())) {
476 9044 tao
                        try {
477
                            idExists = IdentifierManager.getInstance().identifierExists(sidInSys.getValue());
478
                        } catch (SQLException e) {
479
                            throw new ServiceFailure("1310",
480
                                                    "The requested identifier " + sidInSys.getValue() +
481
                                                    " couldn't be determined if it is unique since : "+e.getMessage());
482
                        }
483
                        if(idExists) {
484 9781 cjones
                            throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata doesn't match the previous series id "
485
                                                            +previousSid.getValue()+", so it should NOT exist. However, it was used by another object.");
486 9044 tao
                        }
487
                    }
488 9781 cjones
                } else {
489
                    // there is no previous sid, the new sid should be non-existing.
490
                    try {
491
                        idExists = IdentifierManager.getInstance().identifierExists(sidInSys.getValue());
492
                    } catch (SQLException e) {
493
                        throw new ServiceFailure("1310",
494
                                                "The requested identifier " + sidInSys.getValue() +
495
                                                " couldn't be determined if it is unique since : "+e.getMessage());
496 9601 tao
                    }
497 9781 cjones
                    if(idExists) {
498
                        throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata should NOT exist since the previous series id is null."
499
                                                        +"However, it was used by another object.");
500
                    }
501 9601 tao
                }
502 9781 cjones
                //the series id equals the pid (new pid hasn't been registered in the system, so IdentifierManager.getInstance().identifierExists method can't exclude this scenario)
503
                if(sidInSys.getValue().equals(newPid.getValue())) {
504
                    throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata shouldn't have the same value of the pid.");
505
                }
506
            }
507 10118 tao
508
            long end2 =System.currentTimeMillis();
509
            logMetacat.debug("MNodeService.update - the time spending on checking the sid validation of the old pid "+pid.getValue()+" and the new pid "+newPid.getValue()+" is "+(end2- end)+ " milli seconds.");
510
511 9781 cjones
512
            isScienceMetadata = isScienceMetadata(sysmeta);
513
514
            // do we have XML metadata or a data object?
515
            if (isScienceMetadata) {
516
517
                // update the science metadata XML document
518
                // TODO: handle non-XML metadata/data documents (like netCDF)
519
                // TODO: don't put objects into memory using stream to string
520
                //String objectAsXML = "";
521
                try {
522
                    //objectAsXML = IOUtils.toString(object, "UTF-8");
523 9783 leinfelder
                	String formatId = null;
524
                	if(sysmeta.getFormatId() != null) {
525
                	    formatId = sysmeta.getFormatId().getValue();
526
                	}
527 10280 tao
                    localId = insertOrUpdateDocument(object, "UTF-8", pid, session, "update", formatId, sysmeta.getChecksum());
528 9781 cjones
529
                    // register the newPid and the generated localId
530
                    if (newPid != null) {
531
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
532 9044 tao
                    }
533 9781 cjones
534
                } catch (IOException e) {
535 10129 tao
                    String msg = "The Node is unable to create the object: "+pid.getValue() + "There was a problem converting the object to XML";
536
                    logMetacat.error(msg, e);
537 10300 tao
                    removeIdFromIdentifierTable(newPid);
538 9781 cjones
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
539
540 10129 tao
                }  catch (PropertyNotFoundException e) {
541
                    String msg = "The Node is unable to create the object. " +pid.getValue()+ " since the properties are not configured well "+e.getMessage();
542
                    logMetacat.error(msg, e);
543 10300 tao
                    removeIdFromIdentifierTable(newPid);
544 10129 tao
                    throw new ServiceFailure("1310", msg);
545 10300 tao
                } catch (Exception e) {
546
                    logMetacat.error("MNService.update - couldn't write the metadata object to the disk since "+e.getMessage(), e);
547
                    removeIdFromIdentifierTable(newPid);
548
                    throw e;
549 9044 tao
                }
550 9781 cjones
551 6475 jones
            } else {
552 9781 cjones
553
                // update the data object
554 10300 tao
                try {
555
                    localId = insertDataObject(object, newPid, session, sysmeta.getChecksum());
556
                } catch (Exception e) {
557
                    logMetacat.error("MNService.update - couldn't write the data object to the disk since "+e.getMessage(), e);
558
                    removeIdFromIdentifierTable(newPid);
559
                    throw e;
560
                }
561
562 9781 cjones
563 6475 jones
            }
564 9781 cjones
565 10118 tao
            long end3 =System.currentTimeMillis();
566
            logMetacat.debug("MNodeService.update - the time spending on saving the object with the new pid "+newPid.getValue()+" is "+(end3- end2)+ " milli seconds.");
567
568 9781 cjones
            // add the newPid to the obsoletedBy list for the existing sysmeta
569
            existingSysMeta.setObsoletedBy(newPid);
570
            //increase version
571
            BigInteger current = existingSysMeta.getSerialVersion();
572
            //System.out.println("the current version is "+current);
573
            current = current.add(BigInteger.ONE);
574
            //System.out.println("the new current version is "+current);
575
            existingSysMeta.setSerialVersion(current);
576
            // then update the existing system metadata
577
            updateSystemMetadata(existingSysMeta);
578
579
            // prep the new system metadata, add pid to the affected lists
580
            sysmeta.setObsoletes(pid);
581
            //sysmeta.addDerivedFrom(pid);
582
583
            // and insert the new system metadata
584
            insertSystemMetadata(sysmeta);
585
586
            // log the update event
587
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
588
589 10172 leinfelder
            // queue for QC reporting
590
            MDQClient.submit(sysmeta);
591
592 10118 tao
            long end4 =System.currentTimeMillis();
593
            logMetacat.debug("MNodeService.update - the time spending on updating/saving system metadata  of the old pid "+pid.getValue()+" and the new pid "+newPid.getValue()+" and saving the log information is "+(end4- end3)+ " milli seconds.");
594
595 9781 cjones
            // attempt to register the identifier - it checks if it is a doi
596
            try {
597
    			DOIService.getInstance().registerDOI(sysmeta);
598
    		} catch (Exception e) {
599
                throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
600
    		}
601 10118 tao
            long end5 =System.currentTimeMillis();
602
            logMetacat.debug("MNodeService.update - the time spending on registering the doi (if it is doi ) of the new pid "+newPid.getValue()+" is "+(end5- end4)+ " milli seconds.");
603
604 9781 cjones
        } else {
605
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
606
                    + " on the Member Node.");
607 9600 tao
        }
608 9781 cjones
609 10118 tao
        long end6 =System.currentTimeMillis();
610
        logMetacat.debug("MNodeService.update - the total time of updating the old pid " +pid.getValue() +" whth the new pid "+newPid.getValue()+" is "+(end6- startTime)+ " milli seconds.");
611 6475 jones
        return newPid;
612 6250 cjones
    }
613 10300 tao
614
    /*
615
     * Roll-back method when inserting data object fails.
616
     */
617
    protected void removeIdFromIdentifierTable(Identifier id){
618
        if(id != null) {
619
            try {
620
                if(IdentifierManager.getInstance().mappingExists(id.getValue())) {
621
                   String localId = IdentifierManager.getInstance().getLocalId(id.getValue());
622
                   IdentifierManager.getInstance().removeMapping(id.getValue(), localId);
623
                   logMetacat.info("MNodeService.removeIdFromIdentifierTable - the identifier "+id.getValue()+" and local id "+localId+" have been removed from the identifier table since the object creation failed");
624
                }
625
            } catch (Exception e) {
626
                logMetacat.warn("MNodeService.removeIdFromIdentifierTable - can't decide if the mapping of  the pid "+id.getValue()+" exists on the identifier table.");
627
            }
628
        }
629
    }
630 6254 cjones
631 6475 jones
    public Identifier create(Session session, Identifier pid, InputStream object, SystemMetadata sysmeta) throws InvalidToken, ServiceFailure, NotAuthorized,
632
            IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, InvalidRequest {
633 9781 cjones
634
        if(isReadOnlyMode()) {
635
            throw new ServiceFailure("1190", ReadOnlyChecker.DATAONEERROR);
636
        }
637
        // check for null session
638
        if (session == null) {
639
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
640
        }
641
        // verify the pid is valid format
642
        if (!isValidIdentifier(pid)) {
643
            throw new InvalidRequest("1102", "The provided identifier is invalid.");
644
        }
645
        // set the submitter to match the certificate
646
        sysmeta.setSubmitter(session.getSubject());
647
        // set the originating node
648
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
649
        sysmeta.setOriginMemberNode(originMemberNode);
650
651
        // if no authoritative MN, set it to the same
652
        if (sysmeta.getAuthoritativeMemberNode() == null) {
653
        	sysmeta.setAuthoritativeMemberNode(originMemberNode);
654
        }
655
656
        sysmeta.setArchived(false);
657
658
        // set the dates
659
        Date now = Calendar.getInstance().getTime();
660
        sysmeta.setDateSysMetadataModified(now);
661
        sysmeta.setDateUploaded(now);
662
663
        // set the serial version
664
        sysmeta.setSerialVersion(BigInteger.ZERO);
665
666
        // check that we are not attempting to subvert versioning
667
        if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
668
            throw new InvalidSystemMetadata("1180",
669
              "The supplied system metadata is invalid. " +
670
              "The obsoletes field cannot have a value when creating entries.");
671
        }
672
673
        if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
674
            throw new InvalidSystemMetadata("1180",
675
              "The supplied system metadata is invalid. " +
676
              "The obsoletedBy field cannot have a value when creating entries.");
677
        }
678
679
        // verify the sid in the system metadata
680
        Identifier sid = sysmeta.getSeriesId();
681
        boolean idExists = false;
682
        if(sid != null) {
683
            if (!isValidIdentifier(sid)) {
684
                throw new InvalidSystemMetadata("1180", "The provided series id is invalid.");
685 9068 tao
            }
686 9781 cjones
            try {
687
                idExists = IdentifierManager.getInstance().identifierExists(sid.getValue());
688
            } catch (SQLException e) {
689
                throw new ServiceFailure("1190",
690
                                        "The series identifier " + sid.getValue() +
691
                                        " in the system metadata couldn't be determined if it is unique since : "+e.getMessage());
692 9068 tao
            }
693 9781 cjones
            if (idExists) {
694
                    throw new InvalidSystemMetadata("1180",
695
                              "The series identifier " + sid.getValue() +
696
                              " is already used by another object and" +
697
                              "therefore can not be used for this object. Clients should choose" +
698
                              "a new identifier that is unique and retry the operation or " +
699
                              "use CN.reserveIdentifier() to reserve one.");
700
701 9068 tao
            }
702 9781 cjones
            //the series id equals the pid (new pid hasn't been registered in the system, so IdentifierManager.getInstance().identifierExists method can't exclude this scenario )
703
            if(sid.getValue().equals(pid.getValue())) {
704
                throw new InvalidSystemMetadata("1180", "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
705 9068 tao
            }
706 9600 tao
        }
707 9781 cjones
708
        // call the shared impl
709
        Identifier resultPid = super.create(session, pid, object, sysmeta);
710
711
        // attempt to register the identifier - it checks if it is a doi
712
        try {
713
			DOIService.getInstance().registerDOI(sysmeta);
714
		} catch (Exception e) {
715
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
716
			sf.initCause(e);
717
            throw sf;
718
		}
719
720 10172 leinfelder
        // queue for QC reporting
721
        MDQClient.submit(sysmeta);
722
723 7507 leinfelder
        // return
724
		return resultPid ;
725 6475 jones
    }
726 6250 cjones
727 6475 jones
    /**
728
     * Called by a Coordinating Node to request that the Member Node create a
729
     * copy of the specified object by retrieving it from another Member
730
     * Node and storing it locally so that it can be made accessible to
731
     * the DataONE system.
732
     *
733
     * @param session - the Session object containing the credentials for the Subject
734
     * @param sysmeta - Copy of the CN held system metadata for the object
735
     * @param sourceNode - A reference to node from which the content should be
736
     *                     retrieved. The reference should be resolved by
737
     *                     checking the CN node registry.
738
     *
739
     * @return true if the replication succeeds
740
     *
741
     * @throws ServiceFailure
742
     * @throws NotAuthorized
743
     * @throws NotImplemented
744
     * @throws UnsupportedType
745
     * @throws InsufficientResources
746
     * @throws InvalidRequest
747
     */
748
    @Override
749 6786 cjones
    public boolean replicate(Session session, SystemMetadata sysmeta,
750
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
751
            NotAuthorized, InvalidRequest, InsufficientResources,
752
            UnsupportedType {
753 9480 tao
        /*if(isReadOnlyMode()) {
754 9478 tao
            throw new InvalidRequest("2153", "The Metacat member node is on the read-only mode and your request can't be fulfiled. Please try again later.");
755 9480 tao
        }*/
756 6786 cjones
757 6875 cjones
        if (session != null && sysmeta != null && sourceNode != null) {
758
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
759
                            "\tSession.Subject      = "                           +
760
                            session.getSubject().getValue() + "\n"                +
761 7082 cjones
                            "\tidentifier           = "                           +
762
                            sysmeta.getIdentifier().getValue()                    +
763 6875 cjones
                            "\n" + "\tSource NodeReference ="                     +
764
                            sourceNode.getValue());
765
        }
766 6475 jones
        boolean result = false;
767 6786 cjones
        String nodeIdStr = null;
768 6651 cjones
        NodeReference nodeId = null;
769 6786 cjones
770 6795 cjones
        // get the referenced object
771
        Identifier pid = sysmeta.getIdentifier();
772 9068 tao
        // verify the pid is valid format
773
        if (!isValidIdentifier(pid)) {
774
            throw new InvalidRequest("2153", "The provided identifier in the system metadata is invalid.");
775
        }
776 6795 cjones
777
        // get from the membernode
778
        // TODO: switch credentials for the server retrieval?
779
        this.cn = D1Client.getCN();
780
        InputStream object = null;
781
        Session thisNodeSession = null;
782
        SystemMetadata localSystemMetadata = null;
783
        BaseException failure = null;
784 6818 cjones
        String localId = null;
785
786 6795 cjones
        // TODO: check credentials
787
        // cannot be called by public
788 7063 leinfelder
        if (session == null || session.getSubject() == null) {
789 7082 cjones
            String msg = "No session was provided to replicate identifier " +
790
            sysmeta.getIdentifier().getValue();
791 8414 tao
            logMetacat.error(msg);
792 7192 cjones
            throw new NotAuthorized("2152", msg);
793
794 6795 cjones
        }
795
796
797 6651 cjones
        // get the local node id
798
        try {
799 7030 cjones
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
800 6651 cjones
            nodeId = new NodeReference();
801
            nodeId.setValue(nodeIdStr);
802 6786 cjones
803 6651 cjones
        } catch (PropertyNotFoundException e1) {
804 7030 cjones
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
805 6795 cjones
            failure = new ServiceFailure("2151", msg);
806 8414 tao
            //setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
807 6795 cjones
            logMetacat.error(msg);
808 8414 tao
            //return true;
809
            throw new ServiceFailure("2151", msg);
810 6786 cjones
811 6651 cjones
        }
812 6795 cjones
813 6475 jones
        try {
814 6786 cjones
            try {
815 9544 tao
                // do we already have a replica?
816 6822 cjones
                try {
817 9544 tao
                    localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
818
                    // if we have a local id, get the local object
819
                    try {
820
                        object = MetacatHandler.read(localId);
821
                    } catch (Exception e) {
822
                        // NOTE: we may already know about this ID because it could be a data file described by a metadata file
823
                        // https://redmine.dataone.org/issues/2572
824
                        // TODO: fix this so that we don't prevent ourselves from getting replicas
825
826
                        // let the CN know that the replication failed
827
                        logMetacat.warn("Object content not found on this node despite having localId: " + localId);
828
                        String msg = "Can't read the object bytes properly, replica is invalid.";
829
                        ServiceFailure serviceFailure = new ServiceFailure("2151", msg);
830
                        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, serviceFailure);
831
                        logMetacat.warn(msg);
832
                        throw serviceFailure;
833
834
                    }
835
836
                } catch (McdbDocNotFoundException e) {
837
                    logMetacat.info("No replica found. Continuing.");
838 6822 cjones
839 9544 tao
                } catch (SQLException ee) {
840
                    throw new ServiceFailure("2151", "Couldn't identify the local id of the object with the specified identifier "
841
                                            +pid.getValue()+" since - "+ee.getMessage());
842 6822 cjones
                }
843 9544 tao
844
                // no local replica, get a replica
845
                if ( object == null ) {
846
                    /*boolean success = true;
847
                    try {
848
                        //use the v2 ping api to connect the source node
849
                        mn.ping();
850
                    } catch (Exception e) {
851
                        success = false;
852
                    }*/
853
                    D1NodeVersionChecker checker = new D1NodeVersionChecker(sourceNode);
854
                    String nodeVersion = checker.getVersion("MNRead");
855
                    if(nodeVersion != null && nodeVersion.equals(D1NodeVersionChecker.V1)) {
856
                        //The source node is a v1 node, we use the v1 api
857
                        org.dataone.client.v1.MNode mNodeV1 =  org.dataone.client.v1.itk.D1Client.getMN(sourceNode);
858
                        object = mNodeV1.getReplica(thisNodeSession, pid);
859
                    } else if (nodeVersion != null && nodeVersion.equals(D1NodeVersionChecker.V2)){
860
                     // session should be null to use the default certificate
861
                        // location set in the Certificate manager
862
                        MNode mn = D1Client.getMN(sourceNode);
863
                        object = mn.getReplica(thisNodeSession, pid);
864
                    } else {
865 10249 tao
                        throw new ServiceFailure("2151", "The version of MNRead service is "+nodeVersion+" in the source node "+sourceNode.getValue()+" and it is not supported. Please check the information in the cn");
866 9544 tao
                    }
867
868
                    logMetacat.info("MNodeService.getReplica() called for identifier "
869
                                    + pid.getValue());
870 6822 cjones
871 9544 tao
                }
872
873
            } catch (InvalidToken e) {
874
                String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
875
                failure = new ServiceFailure("2151", msg);
876
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
877
                logMetacat.error(msg);
878
                throw new ServiceFailure("2151", msg);
879
880
            } catch (NotFound e) {
881
                String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
882
                failure = new ServiceFailure("2151", msg);
883
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
884
                logMetacat.error(msg);
885
                throw new ServiceFailure("2151", msg);
886
887
            } catch (NotAuthorized e) {
888
                String msg = "Could not retrieve object to replicate (NotAuthorized): "+ e.getMessage();
889
                failure = new ServiceFailure("2151", msg);
890
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
891
                logMetacat.error(msg);
892
                throw new ServiceFailure("2151", msg);
893
            } catch (NotImplemented e) {
894
                String msg = "Could not retrieve object to replicate (mn.getReplica NotImplemented): "+ e.getMessage();
895
                failure = new ServiceFailure("2151", msg);
896
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
897
                logMetacat.error(msg);
898
                throw new ServiceFailure("2151", msg);
899
            } catch (ServiceFailure e) {
900
                String msg = "Could not retrieve object to replicate (ServiceFailure): "+ e.getMessage();
901
                failure = new ServiceFailure("2151", msg);
902
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
903
                logMetacat.error(msg);
904
                throw new ServiceFailure("2151", msg);
905
            } catch (InsufficientResources e) {
906
                String msg = "Could not retrieve object to replicate (InsufficientResources): "+ e.getMessage();
907
                failure = new ServiceFailure("2151", msg);
908
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
909
                logMetacat.error(msg);
910
                throw new ServiceFailure("2151", msg);
911 6819 cjones
            }
912 9544 tao
913
            // verify checksum on the object, if supported
914 10249 tao
            logMetacat.info("MNodeService.replicate - the class of object inputstream is "+object.getClass().getCanonicalName()+". Does it support the reset method? The answer is "+object.markSupported());
915 9544 tao
916
            // add it to local store
917
            Identifier retPid;
918 6786 cjones
            try {
919 9544 tao
                // skip the MN.create -- this mutates the system metadata and we don't want it to
920
                if ( localId == null ) {
921
                    // TODO: this will fail if we already "know" about the identifier
922
                    // FIXME: see https://redmine.dataone.org/issues/2572
923
                    retPid = super.create(session, pid, object, sysmeta);
924
                    result = (retPid.getValue().equals(pid.getValue()));
925
                }
926
927 6786 cjones
            } catch (Exception e) {
928 9544 tao
                String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
929 8414 tao
                failure = new ServiceFailure("2151", msg);
930
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
931 9544 tao
                logMetacat.error(msg);
932 8414 tao
                throw new ServiceFailure("2151", msg);
933 9544 tao
934 6786 cjones
            }
935 9544 tao
        } finally {
936
            IOUtils.closeQuietly(object);
937 6693 leinfelder
        }
938 9544 tao
939 6786 cjones
940 6795 cjones
        // finish by setting the replication status
941
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
942 6475 jones
        return result;
943
944 6250 cjones
    }
945 9287 tao
946
    /*
947
     * If the given node supports v2 replication.
948
     */
949
    private boolean supportV2Replication(Node node) throws InvalidRequest {
950
        return supportVersionReplication(node, "v2");
951
    }
952
953
    /*
954
     * If the given node support the the given version replication. Return true if it does.
955
     */
956
    private boolean supportVersionReplication(Node node, String version) throws InvalidRequest{
957
        boolean support = false;
958
        if(node == null) {
959
            throw new InvalidRequest("2153", "There is no capacity information about the node in the replicate.");
960
        } else {
961
            Services services = node.getServices();
962
            if(services == null) {
963
                throw new InvalidRequest("2153", "Can't get replica from a node which doesn't have the replicate service.");
964
            } else {
965
               List<Service> list = services.getServiceList();
966
               if(list == null) {
967
                   throw new InvalidRequest("2153", "Can't get replica from a node which doesn't have the replicate service.");
968
               } else {
969
                   for(Service service : list) {
970
                       if(service != null && service.getName() != null && service.getName().equals("MNReplication") &&
971
                               service.getVersion() != null && service.getVersion().equalsIgnoreCase(version) && service.getAvailable() == true ) {
972
                           support = true;
973
974
                       }
975
                   }
976
               }
977
            }
978
        }
979
        return support;
980
    }
981 6179 cjones
982 6475 jones
    /**
983
     * Return the object identified by the given object identifier
984
     *
985
     * @param session - the Session object containing the credentials for the Subject
986
     * @param pid - the object identifier for the given object
987
     *
988
     * @return inputStream - the input stream of the given object
989
     *
990
     * @throws InvalidToken
991
     * @throws ServiceFailure
992
     * @throws NotAuthorized
993
     * @throws InvalidRequest
994
     * @throws NotImplemented
995
     */
996
    @Override
997 6610 cjones
    public InputStream get(Session session, Identifier pid)
998
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
999 6258 cjones
1000 6475 jones
        return super.get(session, pid);
1001 6258 cjones
1002 6259 cjones
    }
1003 6258 cjones
1004 6475 jones
    /**
1005
     * Returns a Checksum for the specified object using an accepted hashing algorithm
1006
     *
1007
     * @param session - the Session object containing the credentials for the Subject
1008
     * @param pid - the object identifier for the given object
1009
     * @param algorithm -  the name of an algorithm that will be used to compute
1010
     *                     a checksum of the bytes of the object
1011
     *
1012
     * @return checksum - the checksum of the given object
1013
     *
1014
     * @throws InvalidToken
1015
     * @throws ServiceFailure
1016
     * @throws NotAuthorized
1017
     * @throws NotFound
1018
     * @throws InvalidRequest
1019
     * @throws NotImplemented
1020
     */
1021
    @Override
1022 6610 cjones
    public Checksum getChecksum(Session session, Identifier pid, String algorithm)
1023
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
1024
        InvalidRequest, NotImplemented {
1025 6258 cjones
1026 6475 jones
        Checksum checksum = null;
1027 9040 tao
        String serviceFailure = "1410";
1028
        String notFound = "1420";
1029
        //Checkum only handles the pid, not sid
1030
        checkV1SystemMetaPidExist(pid, serviceFailure, "The checksum for the object specified by "+pid.getValue()+" couldn't be returned ",  notFound,
1031
                "The object specified by "+pid.getValue()+" does not exist at this node.");
1032 6475 jones
        InputStream inputStream = get(session, pid);
1033
1034 6259 cjones
        try {
1035 6475 jones
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
1036
1037
        } catch (NoSuchAlgorithmException e) {
1038
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
1039
                    + e.getMessage());
1040 6259 cjones
        } catch (IOException e) {
1041 6475 jones
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
1042
                    + e.getMessage());
1043 6259 cjones
        }
1044 6382 cjones
1045 6475 jones
        if (checksum == null) {
1046
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
1047
        }
1048 6258 cjones
1049 6475 jones
        return checksum;
1050 6259 cjones
    }
1051 6179 cjones
1052 6475 jones
    /**
1053
     * Return the system metadata for a given object
1054
     *
1055
     * @param session - the Session object containing the credentials for the Subject
1056
     * @param pid - the object identifier for the given object
1057
     *
1058
     * @return inputStream - the input stream of the given system metadata object
1059
     *
1060
     * @throws InvalidToken
1061
     * @throws ServiceFailure
1062
     * @throws NotAuthorized
1063
     * @throws NotFound
1064
     * @throws InvalidRequest
1065
     * @throws NotImplemented
1066
     */
1067
    @Override
1068 6610 cjones
    public SystemMetadata getSystemMetadata(Session session, Identifier pid)
1069
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
1070
        NotImplemented {
1071 6341 leinfelder
1072 6475 jones
        return super.getSystemMetadata(session, pid);
1073
    }
1074 6341 leinfelder
1075 6475 jones
    /**
1076
     * Retrieve the list of objects present on the MN that match the calling parameters
1077
     *
1078
     * @param session - the Session object containing the credentials for the Subject
1079
     * @param startTime - Specifies the beginning of the time range from which
1080
     *                    to return object (>=)
1081
     * @param endTime - Specifies the beginning of the time range from which
1082
     *                  to return object (>=)
1083
     * @param objectFormat - Restrict results to the specified object format
1084
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
1085
     * @param start - The zero-based index of the first value, relative to the
1086
     *                first record of the resultset that matches the parameters.
1087
     * @param count - The maximum number of entries that should be returned in
1088
     *                the response. The Member Node may return less entries
1089
     *                than specified in this value.
1090
     *
1091
     * @return objectList - the list of objects matching the criteria
1092
     *
1093
     * @throws InvalidToken
1094
     * @throws ServiceFailure
1095
     * @throws NotAuthorized
1096
     * @throws InvalidRequest
1097
     * @throws NotImplemented
1098
     */
1099
    @Override
1100 8810 leinfelder
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Identifier identifier, Boolean replicaStatus, Integer start,
1101 6475 jones
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
1102 9249 tao
        NodeReference nodeId = null;
1103
        if(!replicaStatus) {
1104
            //not include those objects whose authoritative node is not this mn
1105
            nodeId = new NodeReference();
1106
            try {
1107
                String currentNodeId = PropertyService.getInstance().getProperty("dataone.nodeId"); // return only pids for which this mn
1108
                nodeId.setValue(currentNodeId);
1109
            } catch(Exception e) {
1110
                throw new ServiceFailure("1580", e.getMessage());
1111
            }
1112
        }
1113
        return super.listObjects(session, startTime, endTime, objectFormatId, identifier, nodeId, start, count);
1114 6229 cjones
    }
1115 6179 cjones
1116 6475 jones
    /**
1117 6476 jones
     * Return a description of the node's capabilities and services.
1118 6475 jones
     *
1119
     * @return node - the technical capabilities of the Member Node
1120
     *
1121
     * @throws ServiceFailure
1122
     * @throws NotAuthorized
1123
     * @throws InvalidRequest
1124
     * @throws NotImplemented
1125
     */
1126
    @Override
1127 6610 cjones
    public Node getCapabilities()
1128
        throws NotImplemented, ServiceFailure {
1129 6179 cjones
1130 6475 jones
        String nodeName = null;
1131
        String nodeId = null;
1132 6492 jones
        String subject = null;
1133 6938 cjones
        String contactSubject = null;
1134 6475 jones
        String nodeDesc = null;
1135 6476 jones
        String nodeTypeString = null;
1136
        NodeType nodeType = null;
1137 9253 tao
        List<String> mnCoreServiceVersions = null;
1138
        List<String> mnReadServiceVersions = null;
1139
        List<String> mnAuthorizationServiceVersions = null;
1140
        List<String> mnStorageServiceVersions = null;
1141
        List<String> mnReplicationServiceVersions = null;
1142 6179 cjones
1143 6475 jones
        boolean nodeSynchronize = false;
1144
        boolean nodeReplicate = false;
1145 9253 tao
        List<String> mnCoreServiceAvailables = null;
1146
        List<String> mnReadServiceAvailables = null;
1147
        List<String> mnAuthorizationServiceAvailables = null;
1148
        List<String> mnStorageServiceAvailables = null;
1149
        List<String> mnReplicationServiceAvailables = null;
1150 6179 cjones
1151 6475 jones
        try {
1152
            // get the properties of the node based on configuration information
1153 9253 tao
            nodeName = Settings.getConfiguration().getString("dataone.nodeName");
1154
            nodeId = Settings.getConfiguration().getString("dataone.nodeId");
1155
            subject = Settings.getConfiguration().getString("dataone.subject");
1156
            contactSubject = Settings.getConfiguration().getString("dataone.contactSubject");
1157
            nodeDesc = Settings.getConfiguration().getString("dataone.nodeDescription");
1158
            nodeTypeString = Settings.getConfiguration().getString("dataone.nodeType");
1159 6476 jones
            nodeType = NodeType.convert(nodeTypeString);
1160 9253 tao
            nodeSynchronize = new Boolean(Settings.getConfiguration().getString("dataone.nodeSynchronize")).booleanValue();
1161
            nodeReplicate = new Boolean(Settings.getConfiguration().getString("dataone.nodeReplicate")).booleanValue();
1162 6475 jones
1163 6476 jones
            // Set the properties of the node based on configuration information and
1164
            // calls to current status methods
1165 9809 tao
            String serviceName = SystemUtil.getContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
1166 6476 jones
            Node node = new Node();
1167 6542 leinfelder
            node.setBaseURL(serviceName + "/" + nodeTypeString);
1168 6476 jones
            node.setDescription(nodeDesc);
1169 6475 jones
1170 6476 jones
            // set the node's health information
1171
            node.setState(NodeState.UP);
1172
1173
            // set the ping response to the current value
1174
            Ping canPing = new Ping();
1175
            canPing.setSuccess(false);
1176
            try {
1177 6803 leinfelder
            	Date pingDate = ping();
1178
                canPing.setSuccess(pingDate != null);
1179
            } catch (BaseException e) {
1180 6476 jones
                e.printStackTrace();
1181 6803 leinfelder
                // guess it can't be pinged
1182 6476 jones
            }
1183 6610 cjones
1184 6476 jones
            node.setPing(canPing);
1185 6475 jones
1186 6476 jones
            NodeReference identifier = new NodeReference();
1187
            identifier.setValue(nodeId);
1188
            node.setIdentifier(identifier);
1189 6492 jones
            Subject s = new Subject();
1190
            s.setValue(subject);
1191
            node.addSubject(s);
1192 6938 cjones
            Subject contact = new Subject();
1193
            contact.setValue(contactSubject);
1194
            node.addContactSubject(contact);
1195 6476 jones
            node.setName(nodeName);
1196
            node.setReplicate(nodeReplicate);
1197
            node.setSynchronize(nodeSynchronize);
1198 6475 jones
1199 6476 jones
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
1200
            Services services = new Services();
1201 6475 jones
1202 9253 tao
            mnCoreServiceVersions = Settings.getConfiguration().getList("dataone.mnCore.serviceVersion");
1203
            mnCoreServiceAvailables = Settings.getConfiguration().getList("dataone.mnCore.serviceAvailable");
1204
            if(mnCoreServiceVersions != null && mnCoreServiceAvailables != null && mnCoreServiceVersions.size() == mnCoreServiceAvailables.size()) {
1205
                for(int i=0; i<mnCoreServiceVersions.size(); i++) {
1206
                    String version = mnCoreServiceVersions.get(i);
1207
                    boolean available = new Boolean(mnCoreServiceAvailables.get(i)).booleanValue();
1208
                    Service sMNCore = new Service();
1209
                    sMNCore.setName("MNCore");
1210
                    sMNCore.setVersion(version);
1211
                    sMNCore.setAvailable(available);
1212
                    services.addService(sMNCore);
1213
                }
1214
            }
1215
1216
            mnReadServiceVersions = Settings.getConfiguration().getList("dataone.mnRead.serviceVersion");
1217
            mnReadServiceAvailables = Settings.getConfiguration().getList("dataone.mnRead.serviceAvailable");
1218
            if(mnReadServiceVersions != null && mnReadServiceAvailables != null && mnReadServiceVersions.size()==mnReadServiceAvailables.size()) {
1219
                for(int i=0; i<mnReadServiceVersions.size(); i++) {
1220
                    String version = mnReadServiceVersions.get(i);
1221
                    boolean available = new Boolean(mnReadServiceAvailables.get(i)).booleanValue();
1222
                    Service sMNRead = new Service();
1223
                    sMNRead.setName("MNRead");
1224
                    sMNRead.setVersion(version);
1225
                    sMNRead.setAvailable(available);
1226
                    services.addService(sMNRead);
1227
                }
1228
            }
1229
1230
            mnAuthorizationServiceVersions = Settings.getConfiguration().getList("dataone.mnAuthorization.serviceVersion");
1231
            mnAuthorizationServiceAvailables = Settings.getConfiguration().getList("dataone.mnAuthorization.serviceAvailable");
1232
            if(mnAuthorizationServiceVersions != null && mnAuthorizationServiceAvailables != null && mnAuthorizationServiceVersions.size()==mnAuthorizationServiceAvailables.size()) {
1233
                for(int i=0; i<mnAuthorizationServiceVersions.size(); i++) {
1234
                    String version = mnAuthorizationServiceVersions.get(i);
1235
                    boolean available = new Boolean(mnAuthorizationServiceAvailables.get(i)).booleanValue();
1236
                    Service sMNAuthorization = new Service();
1237
                    sMNAuthorization.setName("MNAuthorization");
1238
                    sMNAuthorization.setVersion(version);
1239
                    sMNAuthorization.setAvailable(available);
1240
                    services.addService(sMNAuthorization);
1241
                }
1242
            }
1243
1244
            mnStorageServiceVersions = Settings.getConfiguration().getList("dataone.mnStorage.serviceVersion");
1245
            mnStorageServiceAvailables = Settings.getConfiguration().getList("dataone.mnStorage.serviceAvailable");
1246
            if(mnStorageServiceVersions != null && mnStorageServiceAvailables != null && mnStorageServiceVersions.size() == mnStorageServiceAvailables.size()) {
1247
                for(int i=0; i<mnStorageServiceVersions.size(); i++) {
1248
                    String version = mnStorageServiceVersions.get(i);
1249
                    boolean available = new Boolean(mnStorageServiceAvailables.get(i)).booleanValue();
1250
                    Service sMNStorage = new Service();
1251
                    sMNStorage.setName("MNStorage");
1252
                    sMNStorage.setVersion(version);
1253
                    sMNStorage.setAvailable(available);
1254
                    services.addService(sMNStorage);
1255
                }
1256
            }
1257
1258
            mnReplicationServiceVersions = Settings.getConfiguration().getList("dataone.mnReplication.serviceVersion");
1259
            mnReplicationServiceAvailables = Settings.getConfiguration().getList("dataone.mnReplication.serviceAvailable");
1260
            if(mnReplicationServiceVersions != null && mnReplicationServiceAvailables != null && mnReplicationServiceVersions.size() == mnReplicationServiceAvailables.size()) {
1261
                for (int i=0; i<mnReplicationServiceVersions.size(); i++) {
1262
                    String version = mnReplicationServiceVersions.get(i);
1263
                    boolean available = new Boolean(mnReplicationServiceAvailables.get(i)).booleanValue();
1264
                    Service sMNReplication = new Service();
1265
                    sMNReplication.setName("MNReplication");
1266
                    sMNReplication.setVersion(version);
1267
                    sMNReplication.setAvailable(available);
1268
                    services.addService(sMNReplication);
1269
                }
1270
            }
1271
1272 6476 jones
            node.setServices(services);
1273 6475 jones
1274 6476 jones
            // Set the schedule for synchronization
1275
            Synchronization synchronization = new Synchronization();
1276
            Schedule schedule = new Schedule();
1277
            Date now = new Date();
1278 6689 leinfelder
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
1279
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
1280
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
1281
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
1282
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
1283
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
1284
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
1285 6476 jones
            synchronization.setSchedule(schedule);
1286
            synchronization.setLastHarvested(now);
1287
            synchronization.setLastCompleteHarvest(now);
1288
            node.setSynchronization(synchronization);
1289 6475 jones
1290 6476 jones
            node.setType(nodeType);
1291
            return node;
1292 6475 jones
1293 6476 jones
        } catch (PropertyNotFoundException pnfe) {
1294
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
1295
            logMetacat.error(msg);
1296
            throw new ServiceFailure("2162", msg);
1297
        }
1298 6228 cjones
    }
1299 6179 cjones
1300 8810 leinfelder
1301 6179 cjones
1302 6475 jones
    /**
1303
     * A callback method used by a CN to indicate to a MN that it cannot
1304
     * complete synchronization of the science metadata identified by pid.  Log
1305
     * the event in the metacat event log.
1306
     *
1307
     * @param session
1308
     * @param syncFailed
1309
     *
1310
     * @throws ServiceFailure
1311
     * @throws NotAuthorized
1312
     * @throws NotImplemented
1313
     */
1314
    @Override
1315 6991 leinfelder
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed)
1316 6610 cjones
        throws NotImplemented, ServiceFailure, NotAuthorized {
1317 6179 cjones
1318 6475 jones
        String localId;
1319 7075 cjones
        Identifier pid;
1320 10218 tao
        if ( syncFailed.getPid() != null ) {
1321 7075 cjones
            pid = new Identifier();
1322
            pid.setValue(syncFailed.getPid());
1323
            boolean allowed;
1324
1325
            //are we allowed? only CNs
1326
            try {
1327 7142 leinfelder
                allowed = isAdminAuthorized(session);
1328 7075 cjones
                if ( !allowed ){
1329
                    throw new NotAuthorized("2162",
1330
                            "Not allowed to call synchronizationFailed() on this node.");
1331
                }
1332
            } catch (InvalidToken e) {
1333
                throw new NotAuthorized("2162",
1334
                        "Not allowed to call synchronizationFailed() on this node.");
1335 6331 leinfelder
1336 7075 cjones
            }
1337
1338
        } else {
1339
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1340
1341
        }
1342
1343 6475 jones
        try {
1344 7075 cjones
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1345 6475 jones
        } catch (McdbDocNotFoundException e) {
1346 7075 cjones
            throw new ServiceFailure("2161", "The identifier specified by " +
1347
                    syncFailed.getPid() + " was not found on this node.");
1348 6179 cjones
1349 9024 tao
        } catch (SQLException e) {
1350
            throw new ServiceFailure("2161", "Couldn't identify the local id of the identifier specified by " +
1351
                    syncFailed.getPid() + " since "+e.getMessage());
1352 6475 jones
        }
1353
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1354
        // method is changed to include the URL as a parameter
1355 9380 rnahf
        logMetacat.warn("Synchronization for the object identified by " +
1356 7075 cjones
                pid.getValue() + " failed from " + syncFailed.getNodeId() +
1357 9380 rnahf
                " with message: " + syncFailed.getDescription() +
1358
                ". Logging the event to the Metacat EventLog as a 'syncFailed' event.");
1359 6475 jones
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1360 6532 leinfelder
        String principal = Constants.SUBJECT_PUBLIC;
1361 6506 leinfelder
        if (session != null && session.getSubject() != null) {
1362 6575 cjones
          principal = session.getSubject().getValue();
1363 6506 leinfelder
        }
1364
        try {
1365 6575 cjones
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1366 6506 leinfelder
        } catch (Exception e) {
1367 7075 cjones
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1368 6991 leinfelder
        }
1369 6475 jones
        //EventLog.getInstance().log("CN URL WILL GO HERE",
1370
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1371 6991 leinfelder
        return true;
1372 6179 cjones
1373 6260 cjones
    }
1374
1375 6475 jones
    /**
1376
     * Essentially a get() but with different logging behavior
1377
     */
1378
    @Override
1379 6540 cjones
    public InputStream getReplica(Session session, Identifier pid)
1380 9314 tao
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken, NotFound {
1381 6179 cjones
1382 6540 cjones
        logMetacat.info("MNodeService.getReplica() called.");
1383
1384 6653 leinfelder
        // cannot be called by public
1385
        if (session == null) {
1386
        	throw new InvalidToken("2183", "No session was provided.");
1387
        }
1388
1389 6631 cjones
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1390
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1391
             "\tIdentifier           = " + pid.getValue());
1392
1393 6475 jones
        InputStream inputStream = null; // bytes to be returned
1394
        handler = new MetacatHandler(new Timer());
1395
        boolean allowed = false;
1396
        String localId; // the metacat docid for the pid
1397 6179 cjones
1398 6475 jones
        // get the local docid from Metacat
1399
        try {
1400
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1401
        } catch (McdbDocNotFoundException e) {
1402 9314 tao
            throw new NotFound("2185", "The object specified by " +
1403 6610 cjones
                    pid.getValue() + " does not exist at this node.");
1404
1405 9024 tao
        } catch (SQLException e) {
1406
            throw new ServiceFailure("2181", "The local id of the object specified by " +
1407
                    pid.getValue() + " couldn't be identified since "+e.getMessage());
1408 6475 jones
        }
1409 6234 cjones
1410 6552 leinfelder
        Subject targetNodeSubject = session.getSubject();
1411 6185 leinfelder
1412 6552 leinfelder
        // check for authorization to replicate, null session to act as this source MN
1413 6610 cjones
        try {
1414 6777 leinfelder
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1415 6610 cjones
        } catch (InvalidToken e1) {
1416
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
1417
                + e1.getMessage());
1418
1419
        } catch (NotFound e1) {
1420 9314 tao
            throw new NotFound("2185", "Could not find the object "+pid.getValue()+" in this node - "
1421 6610 cjones
                    + e1.getMessage());
1422 6384 cjones
1423 6610 cjones
        } catch (InvalidRequest e1) {
1424
            throw new ServiceFailure("2181", "Could not determine if node is authorized: "
1425
                    + e1.getMessage());
1426
1427
        }
1428
1429 6540 cjones
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1430
            " for identifier " + pid.getValue());
1431
1432 6475 jones
        // if the person is authorized, perform the read
1433
        if (allowed) {
1434
            try {
1435 6986 jones
                inputStream = MetacatHandler.read(localId);
1436 6475 jones
            } catch (Exception e) {
1437 9314 tao
                throw new ServiceFailure("2181", "The object specified by " +
1438 6610 cjones
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1439 6475 jones
            }
1440 9314 tao
        } else {
1441
            throw new NotAuthorized("2182", "The pid "+pid.getValue()+" is not authorized to be read by the client.");
1442 6475 jones
        }
1443 6384 cjones
1444 6475 jones
        // if we fail to set the input stream
1445
        if (inputStream == null) {
1446 6610 cjones
            throw new ServiceFailure("2181", "The object specified by " +
1447 9314 tao
                pid.getValue() + " can't be returned from the node.");
1448 6475 jones
        }
1449
1450
        // log the replica event
1451
        String principal = null;
1452
        if (session.getSubject() != null) {
1453
            principal = session.getSubject().getValue();
1454
        }
1455 6576 cjones
        EventLog.getInstance().log(request.getRemoteAddr(),
1456
            request.getHeader("User-Agent"), principal, localId, "replicate");
1457 6475 jones
1458
        return inputStream;
1459
    }
1460 9340 tao
1461 6573 cjones
    /**
1462 6600 cjones
     * A method to notify the Member Node that the authoritative copy of
1463 6599 cjones
     * system metadata on the Coordinating Nodes has changed.
1464 9340 tao
     *
1465 6599 cjones
     * @param session   Session information that contains the identity of the
1466
     *                  calling user as retrieved from the X.509 certificate
1467
     *                  which must be traceable to the CILogon service.
1468
     * @param serialVersion   The serialVersion of the system metadata
1469
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1470
     * @throws NotImplemented
1471
     * @throws ServiceFailure
1472
     * @throws NotAuthorized
1473
     * @throws InvalidRequest
1474
     * @throws InvalidToken
1475
     */
1476 6991 leinfelder
    public boolean systemMetadataChanged(Session session, Identifier pid,
1477 6599 cjones
        long serialVersion, Date dateSysMetaLastModified)
1478
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1479
        InvalidToken {
1480 9340 tao
        boolean needCheckAuthoriativeNode = true;
1481
        return systemMetadataChanged(needCheckAuthoriativeNode, session, pid,serialVersion, dateSysMetaLastModified);
1482
    }
1483
1484
    /**
1485
     * A method to notify the Member Node that the authoritative copy of
1486
     * system metadata on the Coordinating Nodes has changed.
1487
     * @param needCheckAuthoriativeNode  this is for the dataone version 2. In the
1488
     * version 2, there are two scenarios:
1489
     * 1. If the node is the authoritative node, it only accepts serial version and replica list.
1490
     * 2. If the node is a replica, it accepts everything.
1491
     * For the v1, api, the parameter should be false.
1492
     * @param session   Session information that contains the identity of the
1493
     *                  calling user as retrieved from the X.509 certificate
1494
     *                  which must be traceable to the CILogon service.
1495
     * @param serialVersion   The serialVersion of the system metadata
1496
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1497
     * @throws NotImplemented
1498
     * @throws ServiceFailure
1499
     * @throws NotAuthorized
1500
     * @throws InvalidRequest
1501
     * @throws InvalidToken
1502
     */
1503
    public boolean systemMetadataChanged(boolean needCheckAuthoriativeNode, Session session, Identifier pid,
1504
        long serialVersion, Date dateSysMetaLastModified)
1505
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1506
        InvalidToken {
1507 6599 cjones
1508 9479 tao
        /*if(isReadOnlyMode()) {
1509 9478 tao
            throw new InvalidRequest("1334", "The Metacat member node is on the read-only mode and your request can't be fulfiled. Please try again later.");
1510 9479 tao
        }*/
1511 7600 cjones
        // cannot be called by public
1512
        if (session == null) {
1513 9051 tao
        	throw new InvalidToken("1332", "No session was provided.");
1514 7600 cjones
        }
1515
1516 9051 tao
        String serviceFailureCode = "1333";
1517
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
1518
        if(sid != null) {
1519
            pid = sid;
1520
        }
1521
1522 6600 cjones
        SystemMetadata currentLocalSysMeta = null;
1523
        SystemMetadata newSysMeta = null;
1524
        CNode cn = D1Client.getCN();
1525
        NodeList nodeList = null;
1526
        Subject callingSubject = null;
1527
        boolean allowed = false;
1528
1529
        // are we allowed to call this?
1530
        callingSubject = session.getSubject();
1531
        nodeList = cn.listNodes();
1532
1533
        for(Node node : nodeList.getNodeList()) {
1534
            // must be a CN
1535
            if ( node.getType().equals(NodeType.CN)) {
1536
               List<Subject> subjectList = node.getSubjectList();
1537
               // the calling subject must be in the subject list
1538
               if ( subjectList.contains(callingSubject)) {
1539
                   allowed = true;
1540
1541
               }
1542
1543
            }
1544
        }
1545
1546
        if (!allowed ) {
1547
            String msg = "The subject identified by " + callingSubject.getValue() +
1548
              " is not authorized to call this service.";
1549
            throw new NotAuthorized("1331", msg);
1550
1551
        }
1552
        try {
1553 9340 tao
            HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1554 6600 cjones
1555 9340 tao
            // compare what we have locally to what is sent in the change notification
1556 6600 cjones
            try {
1557 9340 tao
                currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1558
1559
            } catch (RuntimeException e) {
1560
                String msg = "SystemMetadata for pid " + pid.getValue() +
1561
                  " couldn't be updated because it couldn't be found locally: " +
1562
                  e.getMessage();
1563 6692 leinfelder
                logMetacat.error(msg);
1564 9340 tao
                ServiceFailure sf = new ServiceFailure("1333", msg);
1565 6692 leinfelder
                sf.initCause(e);
1566 9340 tao
                throw sf;
1567 6600 cjones
            }
1568 6692 leinfelder
1569 9340 tao
            if(currentLocalSysMeta == null) {
1570
                throw new InvalidRequest("1334", "We can't find the system metadata in the node for the id "+pid.getValue());
1571
            }
1572 9382 tao
            if (currentLocalSysMeta.getSerialVersion().longValue() <= serialVersion ) {
1573 9340 tao
                try {
1574
                    newSysMeta = cn.getSystemMetadata(null, pid);
1575
                } catch (NotFound e) {
1576
                    // huh? you just said you had it
1577
                	String msg = "On updating the local copy of system metadata " +
1578
                    "for pid " + pid.getValue() +", the CN reports it is not found." +
1579
                    " The error message was: " + e.getMessage();
1580
                    logMetacat.error(msg);
1581
                    //ServiceFailure sf = new ServiceFailure("1333", msg);
1582
                    InvalidRequest sf = new InvalidRequest("1334", msg);
1583
                    sf.initCause(e);
1584
                    throw sf;
1585 9056 tao
                }
1586 9340 tao
1587
                //check about the sid in the system metadata
1588
                Identifier newSID = newSysMeta.getSeriesId();
1589
                if(newSID != null) {
1590
                    if (!isValidIdentifier(newSID)) {
1591
                        throw new InvalidRequest("1334", "The series identifier in the new system metadata is invalid.");
1592
                    }
1593
                    Identifier currentSID = currentLocalSysMeta.getSeriesId();
1594
                    if( currentSID != null && currentSID.getValue() != null) {
1595
                        if(!newSID.getValue().equals(currentSID.getValue())) {
1596
                            //newSID doesn't match the currentSID. The newSID shouldn't be used.
1597
                            try {
1598
                                if(IdentifierManager.getInstance().identifierExists(newSID.getValue())) {
1599
                                    throw new InvalidRequest("1334", "The series identifier "+newSID.getValue()+" in the new system metadata has been used by another object.");
1600
                                }
1601
                            } catch (SQLException sql) {
1602
                                throw new ServiceFailure("1333", "Couldn't determine if the SID "+newSID.getValue()+" in the system metadata exists in the node since "+sql.getMessage());
1603
                            }
1604
1605
                        }
1606
                    } else {
1607
                        //newSID shouldn't be used
1608 9056 tao
                        try {
1609
                            if(IdentifierManager.getInstance().identifierExists(newSID.getValue())) {
1610
                                throw new InvalidRequest("1334", "The series identifier "+newSID.getValue()+" in the new system metadata has been used by another object.");
1611
                            }
1612
                        } catch (SQLException sql) {
1613
                            throw new ServiceFailure("1333", "Couldn't determine if the SID "+newSID.getValue()+" in the system metadata exists in the node since "+sql.getMessage());
1614
                        }
1615
                    }
1616 9340 tao
                }
1617
                // update the local copy of system metadata for the pid
1618
                try {
1619
                    if(needCheckAuthoriativeNode) {
1620
                        //this is for the v2 api.
1621
                        if(isAuthoritativeNode(pid)) {
1622
                            //this is the authoritative node, so we only accept replica and serial version
1623 9375 tao
                            logMetacat.debug("MNodeService.systemMetadataChanged - this is the authoritative node for the pid "+pid.getValue());
1624 9340 tao
                            List<Replica> replicas = newSysMeta.getReplicaList();
1625
                            newSysMeta = currentLocalSysMeta;
1626
                            newSysMeta.setSerialVersion(new BigInteger((new Long(serialVersion)).toString()));
1627
                            newSysMeta.setReplicaList(replicas);
1628 9375 tao
                        } else {
1629
                            //we need to archive the object in the replica node
1630
                            logMetacat.debug("MNodeService.systemMetadataChanged - this is NOT the authoritative node for the pid "+pid.getValue());
1631
                            logMetacat.debug("MNodeService.systemMetadataChanged - the new value of archive is "+newSysMeta.getArchived()+" for the pid "+pid.getValue());
1632
                            logMetacat.debug("MNodeService.systemMetadataChanged - the local value of archive is "+currentLocalSysMeta.getArchived()+" for the pid "+pid.getValue());
1633
                            if (newSysMeta.getArchived() != null && newSysMeta.getArchived() == true  &&
1634
                                    ((currentLocalSysMeta.getArchived() != null && currentLocalSysMeta.getArchived() == false ) || currentLocalSysMeta.getArchived() == null)){
1635
                                logMetacat.debug("MNodeService.systemMetadataChanged - start to archive object "+pid.getValue());
1636
                                boolean logArchive = false;
1637
                                boolean needUpdateModificationDate = false;
1638
                                try {
1639
                                    archiveObject(logArchive, session, pid, newSysMeta, needUpdateModificationDate);
1640
                                } catch (NotFound e) {
1641
                                    throw new InvalidRequest("1334", "Can't find the pid "+pid.getValue()+" for archive.");
1642
                                }
1643
1644 9384 tao
                            } else if((newSysMeta.getArchived() == null || newSysMeta.getArchived() == false) && (currentLocalSysMeta.getArchived() != null && currentLocalSysMeta.getArchived() == true )) {
1645
                                throw new InvalidRequest("1334", "The pid "+pid.getValue()+" has been archived and it can't be reset to false.");
1646 9375 tao
                            }
1647 9056 tao
                        }
1648
                    }
1649 9340 tao
                    HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1650
                    logMetacat.info("Updated local copy of system metadata for pid " +
1651
                        pid.getValue() + " after change notification from the CN.");
1652
1653
                    // TODO: consider inspecting the change for archive
1654
                    // see: https://projects.ecoinformatics.org/ecoinfo/issues/6417
1655
    //                if (newSysMeta.getArchived() != null && newSysMeta.getArchived().booleanValue()) {
1656
    //                	try {
1657
    //						this.archive(session, newSysMeta.getIdentifier());
1658
    //					} catch (NotFound e) {
1659
    //						// do we care? nothing to do about it now
1660
    //						logMetacat.error(e.getMessage(), e);
1661
    //					}
1662
    //                }
1663
1664
                } catch (RuntimeException e) {
1665
                    String msg = "SystemMetadata for pid " + pid.getValue() +
1666
                      " couldn't be updated: " +
1667
                      e.getMessage();
1668
                    logMetacat.error(msg);
1669
                    ServiceFailure sf = new ServiceFailure("1333", msg);
1670
                    sf.initCause(e);
1671
                    throw sf;
1672 9056 tao
                }
1673 6600 cjones
1674 9375 tao
                try {
1675
                    String localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1676
                    EventLog.getInstance().log(request.getRemoteAddr(),
1677
                            request.getHeader("User-Agent"), session.getSubject().getValue(),
1678
                            localId, "updateSystemMetadata");
1679
                } catch (Exception e) {
1680
                    // do nothing, no localId to log with
1681
                    logMetacat.warn("MNodeService.systemMetadataChanged - Could not log 'updateSystemMetadata' event because no localId was found for pid: " + pid.getValue());
1682
                }
1683
1684 9340 tao
1685 6600 cjones
            }
1686 9340 tao
        } finally {
1687
            HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
1688
        }
1689
1690 9382 tao
        if (currentLocalSysMeta.getSerialVersion().longValue() <= serialVersion ) {
1691 9248 leinfelder
            // attempt to re-register the identifier (it checks if it is a doi)
1692
            try {
1693 9340 tao
                DOIService.getInstance().registerDOI(newSysMeta);
1694
            } catch (Exception e) {
1695
                logMetacat.warn("Could not [re]register DOI: " + e.getMessage(), e);
1696
            }
1697 9248 leinfelder
1698 8464 leinfelder
            // submit for indexing
1699
            try {
1700 9340 tao
                MetacatSolrIndex.getInstance().submit(newSysMeta.getIdentifier(), newSysMeta, null, true);
1701
            } catch (Exception e) {
1702 8464 leinfelder
                logMetacat.error("Could not submit changed systemMetadata for indexing, pid: " + newSysMeta.getIdentifier().getValue(), e);
1703 9340 tao
            }
1704 6600 cjones
        }
1705
1706 6991 leinfelder
        return true;
1707
1708 6599 cjones
    }
1709
1710 6795 cjones
    /*
1711
     * Set the replication status for the object on the Coordinating Node
1712
     *
1713
     * @param session - the session for the this target node
1714
     * @param pid - the identifier of the object being updated
1715
     * @param nodeId - the identifier of this target node
1716
     * @param status - the replication status to set
1717
     * @param failure - the exception to include, if any
1718
     */
1719
    private void setReplicationStatus(Session session, Identifier pid,
1720
        NodeReference nodeId, ReplicationStatus status, BaseException failure)
1721
        throws ServiceFailure, NotImplemented, NotAuthorized,
1722
        InvalidRequest {
1723
1724
        // call the CN as the MN to set the replication status
1725
        try {
1726
            this.cn = D1Client.getCN();
1727
            this.cn.setReplicationStatus(session, pid, nodeId,
1728
                    status, failure);
1729
1730
        } catch (InvalidToken e) {
1731 7091 leinfelder
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (InvalidToken): " + e.getMessage();
1732
            logMetacat.error(msg);
1733
        	throw new ServiceFailure("2151",
1734
                    msg);
1735 6795 cjones
1736
        } catch (NotFound e) {
1737 7091 leinfelder
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (NotFound): " + e.getMessage();
1738
            logMetacat.error(msg);
1739
        	throw new ServiceFailure("2151",
1740
                    msg);
1741 6795 cjones
1742
        }
1743
    }
1744 8591 leinfelder
1745
    private SystemMetadata makePublicIfNot(SystemMetadata sysmeta, Identifier pid) throws ServiceFailure, InvalidToken, NotFound, NotImplemented, InvalidRequest {
1746
    	// check if it is publicly readable
1747
		boolean isPublic = false;
1748
		Subject publicSubject = new Subject();
1749
		publicSubject.setValue(Constants.SUBJECT_PUBLIC);
1750
		Session publicSession = new Session();
1751
		publicSession.setSubject(publicSubject);
1752
		AccessRule publicRule = new AccessRule();
1753
		publicRule.addPermission(Permission.READ);
1754
		publicRule.addSubject(publicSubject);
1755
1756
		// see if we need to add the rule
1757
		try {
1758
			isPublic = this.isAuthorized(publicSession, pid, Permission.READ);
1759
		} catch (NotAuthorized na) {
1760
			// well, certainly not authorized for public read!
1761
		}
1762
		if (!isPublic) {
1763
			sysmeta.getAccessPolicy().addAllow(publicRule);
1764
		}
1765
1766
		return sysmeta;
1767
    }
1768 7099 leinfelder
1769
	@Override
1770 7441 leinfelder
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1771 7099 leinfelder
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1772
			InvalidRequest {
1773 7448 leinfelder
1774 8210 leinfelder
		// check for null session
1775
        if (session == null) {
1776
          throw new InvalidToken("2190", "Session is required to generate an Identifier at this Node.");
1777
        }
1778
1779 7441 leinfelder
		Identifier identifier = new Identifier();
1780 7448 leinfelder
1781 7489 leinfelder
		// handle different schemes
1782
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1783
			// UUID
1784
			UUID uuid = UUID.randomUUID();
1785
            identifier.setValue(UUID_PREFIX + uuid.toString());
1786
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1787 7512 leinfelder
			// generate a DOI
1788 7448 leinfelder
			try {
1789 7512 leinfelder
				identifier = DOIService.getInstance().generateDOI();
1790 7448 leinfelder
			} catch (EZIDException e) {
1791 7512 leinfelder
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1792
				sf.initCause(e);
1793
				throw sf;
1794 7448 leinfelder
			}
1795 7489 leinfelder
		} else {
1796
			// default if we don't know the scheme
1797
			if (fragment != null) {
1798
				// for now, just autogen with fragment
1799
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1800
				identifier.setValue(autogenId);
1801
			} else {
1802
				// autogen with no fragment
1803
				String autogenId = DocumentUtil.generateDocumentId(0);
1804
				identifier.setValue(autogenId);
1805
			}
1806 7448 leinfelder
		}
1807
1808 7441 leinfelder
		// TODO: reserve the identifier with the CN. We can only do this when
1809
		// 1) the MN is part of a CN cluster
1810
		// 2) the request is from an authenticated user
1811
1812
		return identifier;
1813 7099 leinfelder
	}
1814 7144 leinfelder
1815 8810 leinfelder
1816 7144 leinfelder
1817
	@Override
1818 8810 leinfelder
	public QueryEngineDescription getQueryEngineDescription(Session session, String engine)
1819 7144 leinfelder
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1820
			NotFound {
1821 7772 tao
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1822 8162 tao
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1823
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1824
            }
1825 7634 tao
	        QueryEngineDescription qed = new QueryEngineDescription();
1826 7772 tao
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1827 7634 tao
	        qed.setQueryEngineVersion("1.0");
1828
	        qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1829
	        Vector<String> pathsForIndexing = null;
1830
	        try {
1831
	            pathsForIndexing = SystemUtil.getPathsForIndexing();
1832
	        } catch (MetacatUtilException e) {
1833
	            logMetacat.warn("Could not get index paths", e);
1834
	        }
1835
	        for (String fieldName: pathsForIndexing) {
1836
	            QueryField field = new QueryField();
1837
	            field.addDescription("Indexed field for path '" + fieldName + "'");
1838
	            field.setName(fieldName);
1839
	            field.setReturnable(true);
1840
	            field.setSearchable(true);
1841
	            field.setSortable(false);
1842
	            // TODO: determine type and multivaluedness
1843
	            field.setType(String.class.getName());
1844
	            //field.setMultivalued(true);
1845
	            qed.addQueryField(field);
1846
	        }
1847
	        return qed;
1848 7772 tao
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1849 7781 tao
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1850 7772 tao
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1851 7781 tao
            }
1852 7634 tao
	        try {
1853 7662 tao
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1854 7634 tao
	            return qed;
1855
	        } catch (Exception e) {
1856
	            e.printStackTrace();
1857
	            throw new ServiceFailure("Solr server error", e.getMessage());
1858
	        }
1859
	    } else {
1860
	        throw new NotFound("404", "The Metacat member node can't find the query engine - "+engine);
1861
	    }
1862
1863 7417 leinfelder
	}
1864
1865
	@Override
1866 8810 leinfelder
	public QueryEngineList listQueryEngines(Session session) throws InvalidToken,
1867 7417 leinfelder
			ServiceFailure, NotAuthorized, NotImplemented {
1868
		QueryEngineList qel = new QueryEngineList();
1869 7781 tao
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1870
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1871
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1872 7772 tao
		for(String name : enables) {
1873
		    qel.addQueryEngine(name);
1874 7781 tao
		}
1875 7417 leinfelder
		return qel;
1876
	}
1877
1878
	@Override
1879 8810 leinfelder
	public InputStream query(Session session, String engine, String query) throws InvalidToken,
1880 7417 leinfelder
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1881
			NotFound {
1882 7648 tao
	    String user = Constants.SUBJECT_PUBLIC;
1883
        String[] groups= null;
1884 7680 tao
        Set<Subject> subjects = null;
1885 7648 tao
        if (session != null) {
1886
            user = session.getSubject().getValue();
1887 7680 tao
            subjects = AuthUtils.authorizedClientSubjects(session);
1888 7648 tao
            if (subjects != null) {
1889
                List<String> groupList = new ArrayList<String>();
1890
                for (Subject subject: subjects) {
1891
                    groupList.add(subject.getValue());
1892
                }
1893
                groups = groupList.toArray(new String[0]);
1894
            }
1895 7680 tao
        } else {
1896
            //add the public user subject to the set
1897
            Subject subject = new Subject();
1898
            subject.setValue(Constants.SUBJECT_PUBLIC);
1899 7757 leinfelder
            subjects = new HashSet<Subject>();
1900 7680 tao
            subjects.add(subject);
1901 7648 tao
        }
1902 7680 tao
        //System.out.println("====== user is "+user);
1903
        //System.out.println("====== groups are "+groups);
1904 7772 tao
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1905 8162 tao
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1906
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1907
            }
1908 7417 leinfelder
			try {
1909
				DBQuery queryobj = new DBQuery();
1910 7648 tao
1911 7417 leinfelder
				String results = queryobj.performPathquery(query, user, groups);
1912 7757 leinfelder
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1913
				ctbais.setContentType("text/xml");
1914
				return ctbais;
1915 7417 leinfelder
1916
			} catch (Exception e) {
1917 7757 leinfelder
				throw new ServiceFailure("Pathquery error", e.getMessage());
1918 7417 leinfelder
			}
1919
1920 7772 tao
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1921 7781 tao
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1922 7772 tao
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1923 7781 tao
		    }
1924 7634 tao
		    logMetacat.info("The query is ==================================== \n"+query);
1925 7620 tao
		    try {
1926 7634 tao
1927 7680 tao
                return MetacatSolrIndex.getInstance().query(query, subjects);
1928 7620 tao
            } catch (Exception e) {
1929
                // TODO Auto-generated catch block
1930
                throw new ServiceFailure("Solr server error", e.getMessage());
1931
            }
1932 7417 leinfelder
		}
1933
		return null;
1934
	}
1935 7849 leinfelder
1936
	/**
1937
	 * Given an existing Science Metadata PID, this method mints a DOI
1938
	 * and updates the original object "publishing" the update with the DOI.
1939
	 * This includes updating the ORE map that describes the Science Metadata+data.
1940
	 * TODO: ensure all referenced objects allow public read
1941
	 *
1942
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1943
	 *
1944
	 * @param originalIdentifier
1945
	 * @param request
1946 7864 leinfelder
	 * @throws InvalidRequest
1947
	 * @throws NotImplemented
1948
	 * @throws NotAuthorized
1949
	 * @throws ServiceFailure
1950
	 * @throws InvalidToken
1951 7849 leinfelder
	 * @throws NotFound
1952 7864 leinfelder
	 * @throws InvalidSystemMetadata
1953
	 * @throws InsufficientResources
1954
	 * @throws UnsupportedType
1955
	 * @throws IdentifierNotUnique
1956 7849 leinfelder
	 */
1957 9387 walker
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken,
1958
	ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique,
1959
	UnsupportedType, InsufficientResources, InvalidSystemMetadata, IOException {
1960 7849 leinfelder
1961 9092 tao
	    String serviceFailureCode = "1030";
1962
	    Identifier sid = getPIDForSID(originalIdentifier, serviceFailureCode);
1963
	    if(sid != null) {
1964
	        originalIdentifier = sid;
1965
	    }
1966 8141 leinfelder
		// get the original SM
1967
		SystemMetadata originalSystemMetadata = this.getSystemMetadata(session, originalIdentifier);
1968
1969 8594 leinfelder
		// make copy of it using the marshaller to ensure DEEP copy
1970
		SystemMetadata sysmeta = null;
1971 8141 leinfelder
		try {
1972 8594 leinfelder
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
1973
			TypeMarshaller.marshalTypeToOutputStream(originalSystemMetadata, baos);
1974
			sysmeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1975 8141 leinfelder
		} catch (Exception e) {
1976
			// report as service failure
1977
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1978
			sf.initCause(e);
1979
			throw sf;
1980
		}
1981
1982 7849 leinfelder
		// mint a DOI for the new revision
1983
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1984 8141 leinfelder
1985
		// set new metadata values
1986 7849 leinfelder
		sysmeta.setIdentifier(newIdentifier);
1987
		sysmeta.setObsoletes(originalIdentifier);
1988
		sysmeta.setObsoletedBy(null);
1989
1990 8591 leinfelder
		// ensure it is publicly readable
1991
		sysmeta = makePublicIfNot(sysmeta, originalIdentifier);
1992
1993 9387 walker
		//Get the bytes
1994
		InputStream inputStream = null;
1995
		boolean isScienceMetadata = isScienceMetadata(sysmeta);
1996
		//If it's a science metadata doc, we want to update the packageId first
1997
		if(isScienceMetadata){
1998 10321 tao
		    boolean isEML = false;
1999
		    //Get the formatId
2000
            ObjectFormatIdentifier objFormatId = originalSystemMetadata.getFormatId();
2001
            String formatId = objFormatId.getValue();
2002
            //For all EML formats
2003
            if(formatId.indexOf("eml") == 0){
2004
                logMetacat.debug("~~~~~~~~~~~~~~~~~~~~~~MNodeService.publish - the object "+originalIdentifier.getValue()+" with format id "+formatId+" is an eml document.");
2005
                isEML = true;
2006
            }
2007 9387 walker
			InputStream originalObject = this.get(session, originalIdentifier);
2008
2009
			//Edit the science metadata with the new package Id (EML)
2010 10321 tao
			inputStream = editScienceMetadata(session, originalObject, originalIdentifier, newIdentifier, isEML, sysmeta);
2011 9387 walker
		}
2012
		else{
2013
			inputStream = this.get(session, originalIdentifier);
2014
		}
2015 7849 leinfelder
2016
		// update the object
2017 7864 leinfelder
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
2018 7849 leinfelder
2019 7864 leinfelder
		// update ORE that references the scimeta
2020 8190 leinfelder
		// first try the naive method, then check the SOLR index
2021 7849 leinfelder
		try {
2022 7864 leinfelder
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
2023 7849 leinfelder
2024 7864 leinfelder
			Identifier potentialOreIdentifier = new Identifier();
2025
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
2026 7849 leinfelder
2027 7864 leinfelder
			InputStream oreInputStream = null;
2028
			try {
2029
				oreInputStream = this.get(session, potentialOreIdentifier);
2030
			} catch (NotFound nf) {
2031
				// this is probably okay for many sci meta data docs
2032
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
2033 8190 leinfelder
				// try the SOLR index
2034 8200 leinfelder
				List<Identifier> potentialOreIdentifiers = this.lookupOreFor(originalIdentifier, false);
2035 8190 leinfelder
				if (potentialOreIdentifiers != null) {
2036
					potentialOreIdentifier = potentialOreIdentifiers.get(0);
2037
					try {
2038
						oreInputStream = this.get(session, potentialOreIdentifier);
2039
					} catch (NotFound nf2) {
2040
						// this is probably okay for many sci meta data docs
2041
						logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
2042
					}
2043
				}
2044 7864 leinfelder
			}
2045
			if (oreInputStream != null) {
2046
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
2047
2048
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
2049
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
2050
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
2051 8591 leinfelder
2052 7864 leinfelder
				// reconstruct the ORE with the new identifiers
2053
				sciMetaMap.remove(originalIdentifier);
2054
				sciMetaMap.put(newIdentifier, dataIdentifiers);
2055
2056
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
2057
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
2058
2059
				// get the original ORE SM and update the values
2060 8141 leinfelder
				SystemMetadata originalOreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
2061
				SystemMetadata oreSysMeta = new SystemMetadata();
2062
				try {
2063 8594 leinfelder
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
2064
					TypeMarshaller.marshalTypeToOutputStream(originalOreSysMeta, baos);
2065
					oreSysMeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
2066 8141 leinfelder
				} catch (Exception e) {
2067
					// report as service failure
2068
					ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2069
					sf.initCause(e);
2070
					throw sf;
2071
				}
2072
2073 7864 leinfelder
				oreSysMeta.setIdentifier(newOreIdentifier);
2074
				oreSysMeta.setObsoletes(potentialOreIdentifier);
2075
				oreSysMeta.setObsoletedBy(null);
2076
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
2077
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
2078
2079 8591 leinfelder
				// ensure ORE is publicly readable
2080 8594 leinfelder
				oreSysMeta = makePublicIfNot(oreSysMeta, potentialOreIdentifier);
2081 8591 leinfelder
2082
				// ensure all data objects allow public read
2083
				List<String> pidsToSync = new ArrayList<String>();
2084
				for (Identifier dataId: dataIdentifiers) {
2085
					SystemMetadata dataSysMeta = this.getSystemMetadata(session, dataId);
2086
					dataSysMeta = makePublicIfNot(dataSysMeta, dataId);
2087
					this.updateSystemMetadata(dataSysMeta);
2088
					pidsToSync.add(dataId.getValue());
2089
				}
2090
				SyncAccessPolicy sap = new SyncAccessPolicy();
2091
				try {
2092
					sap.sync(pidsToSync);
2093
				} catch (Exception e) {
2094
					// ignore
2095
					logMetacat.warn("Error attempting to sync access for data objects when publishing package");
2096
				}
2097
2098 7864 leinfelder
				// save the updated ORE
2099
				this.update(
2100
						session,
2101
						potentialOreIdentifier,
2102
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")),
2103
						newOreIdentifier,
2104
						oreSysMeta);
2105
2106 8362 leinfelder
			} else {
2107
				// create a new ORE for them
2108
				// https://projects.ecoinformatics.org/ecoinfo/issues/6194
2109
				try {
2110
					// find the local id for the NEW package.
2111
					String newLocalId = IdentifierManager.getInstance().getLocalId(newIdentifier.getValue());
2112
2113
					@SuppressWarnings("unused")
2114
					SystemMetadata extraSysMeta = SystemMetadataFactory.createSystemMetadata(newLocalId, true, false);
2115 8591 leinfelder
					// should be done generating the ORE here, and the same permissions were used from the metadata object
2116 8362 leinfelder
2117
				} catch (Exception e) {
2118
					// oops, guess there was a problem - no package for you
2119
					logMetacat.error("Could not generate new ORE for published object: " + newIdentifier.getValue(), e);
2120
				}
2121 7864 leinfelder
			}
2122
		} catch (McdbDocNotFoundException e) {
2123
			// report as service failure
2124
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2125
			sf.initCause(e);
2126
			throw sf;
2127
		} catch (UnsupportedEncodingException e) {
2128
			// report as service failure
2129
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2130
			sf.initCause(e);
2131
			throw sf;
2132
		} catch (OREException e) {
2133
			// report as service failure
2134
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2135
			sf.initCause(e);
2136
			throw sf;
2137
		} catch (URISyntaxException e) {
2138
			// report as service failure
2139
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2140
			sf.initCause(e);
2141
			throw sf;
2142
		} catch (OREParserException e) {
2143
			// report as service failure
2144
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2145
			sf.initCause(e);
2146
			throw sf;
2147
		} catch (ORESerialiserException e) {
2148
			// report as service failure
2149
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2150
			sf.initCause(e);
2151
			throw sf;
2152
		} catch (NoSuchAlgorithmException e) {
2153
			// report as service failure
2154
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2155
			sf.initCause(e);
2156
			throw sf;
2157 9024 tao
		} catch (SQLException e) {
2158
            // report as service failure
2159
            ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2160
            sf.initCause(e);
2161
            throw sf;
2162
        }
2163 7849 leinfelder
2164
		return newIdentifier;
2165
	}
2166 7850 leinfelder
2167
	/**
2168 9387 walker
	   * Update a science metadata document with its new Identifier
2169
	   *
2170
	   * @param session - the Session object containing the credentials for the Subject
2171
	   * @param object - the InputStream for the XML object to be edited
2172
	   * @param pid - the Identifier of the XML object to be updated
2173
	   * @param newPid = the new Identifier to give to the modified XML doc
2174
	   *
2175
	   * @return newObject - The InputStream for the modified XML object
2176
	   *
2177
	   * @throws ServiceFailure
2178
	   * @throws IOException
2179
	   * @throws UnsupportedEncodingException
2180
	   * @throws InvalidToken
2181
	   * @throws NotAuthorized
2182
	   * @throws NotFound
2183
	   * @throws NotImplemented
2184
	   */
2185 10321 tao
	  public InputStream editScienceMetadata(Session session, InputStream object, Identifier pid, Identifier newPid, boolean isEML, SystemMetadata newSysmeta)
2186 9387 walker
	  	throws ServiceFailure, IOException, UnsupportedEncodingException, InvalidToken, NotAuthorized, NotFound, NotImplemented {
2187
2188
		logMetacat.debug("D1NodeService.editScienceMetadata() called.");
2189
2190
		 InputStream newObject = null;
2191
2192
	    try{
2193
	    	//Get the root node of the XML document
2194
	    	byte[] xmlBytes  = IOUtils.toByteArray(object);
2195
	        String xmlStr = new String(xmlBytes, "UTF-8");
2196
2197
	    	Document doc = XMLUtilities.getXMLReaderAsDOMDocument(new StringReader(xmlStr));
2198
		    org.w3c.dom.Node docNode = doc.getDocumentElement();
2199
2200
	    	//For all EML formats
2201 10321 tao
	        if(isEML){
2202 9387 walker
	        	//Update or add the id attribute
2203
	    	    XMLUtilities.addAttributeNodeToDOMTree(docNode, XPATH_EML_ID, newPid.getValue());
2204
	        }
2205
2206
	        //The modified object InputStream
2207
		    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
2208
		    Source xmlSource = new DOMSource(docNode);
2209
		    Result outputTarget = new StreamResult(outputStream);
2210
		    TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
2211 10321 tao
		    byte[] output = outputStream.toByteArray();
2212
		    Checksum checksum = ChecksumUtil.checksum(output, newSysmeta.getChecksum().getAlgorithm());
2213
		    newObject = new ByteArrayInputStream(output);
2214
		    newSysmeta.setChecksum(checksum);
2215
		    logMetacat.debug("MNNodeService.editScienceMetadata - the new checksum is "+checksum.getValue() +" with algorithm "+checksum.getAlgorithm()+" for the new pid "+newPid.getValue()+" which is published from the pid "+pid.getValue());
2216 9387 walker
	    } catch(TransformerException e) {
2217
	    	throw new ServiceFailure("1030", "MNNodeService.editScienceMetadata(): " +
2218
	                "Could not update the ID in the XML document for " +
2219
	                "pid " + pid.getValue() +" : " + e.getMessage());
2220
	    } catch(IOException e){
2221
	    	throw new ServiceFailure("1030", "MNNodeService.editScienceMetadata(): " +
2222
	                "Could not update the ID in the XML document for " +
2223
	                "pid " + pid.getValue() +" : " + e.getMessage());
2224 10321 tao
	    } catch(NoSuchAlgorithmException e) {
2225
	        throw new ServiceFailure("1030", "MNNodeService.editScienceMetadata(): " +
2226
                    "Could not update the ID in the XML document for " +
2227
                    "pid " + pid.getValue() +" since the checksum can't be computed : " + e.getMessage());
2228 9387 walker
	    }
2229
2230
	    return newObject;
2231
	  }
2232
2233
	/**
2234 8190 leinfelder
	 * Determines if we already have registered an ORE map for this package
2235
	 * NOTE: uses a solr query to locate OREs for the object
2236
	 * @param guid of the EML/packaging object
2237
	 * @return list of resource map identifiers for the given pid
2238
	 */
2239 8200 leinfelder
	public List<Identifier> lookupOreFor(Identifier guid, boolean includeObsolete) {
2240 8190 leinfelder
		// Search for the ORE if we can find it
2241
		String pid = guid.getValue();
2242
		List<Identifier> retList = null;
2243
		try {
2244 8745 leinfelder
			String query = "fl=id,resourceMap&wt=xml&q=-obsoletedBy:[* TO *]+resourceMap:[* TO *]+id:\"" + pid + "\"";
2245 8200 leinfelder
			if (includeObsolete) {
2246 8745 leinfelder
				query = "fl=id,resourceMap&wt=xml&q=resourceMap:[* TO *]+id:\"" + pid + "\"";
2247 8200 leinfelder
			}
2248
2249 8810 leinfelder
			InputStream results = this.query(null, "solr", query);
2250 8190 leinfelder
			org.w3c.dom.Node rootNode = XMLUtilities.getXMLReaderAsDOMTreeRootNode(new InputStreamReader(results, "UTF-8"));
2251
			//String resultString = XMLUtilities.getDOMTreeAsString(rootNode);
2252
			org.w3c.dom.NodeList nodeList = XMLUtilities.getNodeListWithXPath(rootNode, "//arr[@name=\"resourceMap\"]/str");
2253
			if (nodeList != null && nodeList.getLength() > 0) {
2254
				retList = new ArrayList<Identifier>();
2255
				for (int i = 0; i < nodeList.getLength(); i++) {
2256
					String found = nodeList.item(i).getFirstChild().getNodeValue();
2257
					Identifier oreId = new Identifier();
2258
					oreId.setValue(found);
2259
					retList.add(oreId);
2260
				}
2261
			}
2262
		} catch (Exception e) {
2263
			logMetacat.error("Error checking for resourceMap[s] on pid " + pid + ". " + e.getMessage(), e);
2264
		}
2265
2266
		return retList;
2267
	}
2268
2269 8810 leinfelder
2270
	@Override
2271
	public InputStream getPackage(Session session, ObjectFormatIdentifier formatId,
2272
			Identifier pid) throws InvalidToken, ServiceFailure,
2273
			NotAuthorized, InvalidRequest, NotImplemented, NotFound {
2274 9290 tao
	    if(formatId == null) {
2275
	        throw new InvalidRequest("2873", "The format type can't be null in the getpackage method.");
2276 9305 tao
	    } else if(!formatId.getValue().equals("application/bagit-097")) {
2277 9290 tao
	        throw new NotImplemented("", "The format "+formatId.getValue()+" is not supported in the getpackage method");
2278
	    }
2279 9092 tao
	    String serviceFailureCode = "2871";
2280
	    Identifier sid = getPIDForSID(pid, serviceFailureCode);
2281
	    if(sid != null) {
2282
	        pid = sid;
2283
	    }
2284 7850 leinfelder
		InputStream bagInputStream = null;
2285
		BagFactory bagFactory = new BagFactory();
2286
		Bag bag = bagFactory.createBag();
2287
2288
		// track the temp files we use so we can delete them when finished
2289
		List<File> tempFiles = new ArrayList<File>();
2290
2291
		// the pids to include in the package
2292
		List<Identifier> packagePids = new ArrayList<Identifier>();
2293
2294 7855 leinfelder
		// catch non-D1 service errors and throw as ServiceFailures
2295
		try {
2296 8437 walker
			//Create a map of dataone ids and file names
2297
			Map<Identifier, String> fileNames = new HashMap<Identifier, String>();
2298 7855 leinfelder
2299 8800 leinfelder
			// track the pid-to-file mapping
2300
			StringBuffer pidMapping = new StringBuffer();
2301
2302 7855 leinfelder
			// find the package contents
2303
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2304 8025 leinfelder
			if (ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
2305 8437 walker
				//Get the resource map as a map of Identifiers
2306 7855 leinfelder
				InputStream oreInputStream = this.get(session, pid);
2307
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
2308
				packagePids.addAll(resourceMapStructure.keySet());
2309 8437 walker
				//Loop through each object in this resource map
2310 7855 leinfelder
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
2311 8437 walker
					//Loop through each metadata object in this entry
2312
					Set<Identifier> metadataIdentifiers = entries.keySet();
2313
					for(Identifier metadataID: metadataIdentifiers){
2314
						try{
2315
							//Get the system metadata for this metadata object
2316
							SystemMetadata metadataSysMeta = this.getSystemMetadata(session, metadataID);
2317
2318 8800 leinfelder
							// include user-friendly metadata
2319
							if (ObjectFormatCache.getInstance().getFormat(metadataSysMeta.getFormatId()).getFormatType().equals("METADATA")) {
2320
								InputStream metadataStream = this.get(session, metadataID);
2321
2322
								try {
2323
									// transform
2324
						            String format = "default";
2325
2326
									DBTransform transformer = new DBTransform();
2327
						            String documentContent = IOUtils.toString(metadataStream, "UTF-8");
2328
						            String sourceType = metadataSysMeta.getFormatId().getValue();
2329
						            String targetType = "-//W3C//HTML//EN";
2330
						            ByteArrayOutputStream baos = new ByteArrayOutputStream();
2331
						            Writer writer = new OutputStreamWriter(baos , "UTF-8");
2332
						            // TODO: include more params?
2333
						            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2334
						            String localId = null;
2335
									try {
2336
										localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2337
									} catch (McdbDocNotFoundException e) {
2338
										throw new NotFound("1020", e.getMessage());
2339
									}
2340
									params.put("qformat", new String[] {format});
2341
						            params.put("docid", new String[] {localId});
2342
						            params.put("pid", new String[] {pid.getValue()});
2343
						            params.put("displaymodule", new String[] {"printall"});
2344
2345
						            transformer.transformXMLDocument(
2346
						                    documentContent ,
2347
						                    sourceType,
2348
						                    targetType ,
2349
						                    format,
2350
						                    writer,
2351
						                    params,
2352
						                    null //sessionid
2353
						                    );
2354
2355
						            // finally, get the HTML back
2356
						            ContentTypeByteArrayInputStream resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2357
2358
						            // write to temp file with correct css path
2359
						            File tmpDir = File.createTempFile("package_", "_dir");
2360
						            tmpDir.delete();
2361
						            tmpDir.mkdir();
2362
						            File htmlFile = File.createTempFile("metadata", ".html", tmpDir);
2363
						            File cssDir = new File(tmpDir, format);
2364
						            cssDir.mkdir();
2365
						            File cssFile = new File(tmpDir, format + "/" + format + ".css");
2366
						            String pdfFileName = metadataID.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-METADATA.pdf";
2367
						            File pdfFile = new File(tmpDir, pdfFileName);
2368
						            //File pdfFile = File.createTempFile("metadata", ".pdf", tmpDir);
2369
2370
						            // put the CSS file in place for the html to find it
2371
						            String originalCssPath = SystemUtil.getContextDir() + "/style/skins/" + format + "/" + format + ".css";
2372
						            IOUtils.copy(new FileInputStream(originalCssPath), new FileOutputStream(cssFile));
2373
2374
						            // write the HTML file
2375
						            IOUtils.copy(resultInputStream, new FileOutputStream(htmlFile));
2376
2377
						            // convert to PDF
2378
						            HtmlToPdf.export(htmlFile.getAbsolutePath(), pdfFile.getAbsolutePath());
2379
2380
						            //add to the package
2381
						            bag.addFileToPayload(pdfFile);
2382
									pidMapping.append(metadataID.getValue() + " (pdf)" +  "\t" + "data/" + pdfFile.getName() + "\n");
2383
2384
						            // mark for clean up after we are done
2385
									htmlFile.delete();
2386
									cssFile.delete();
2387
									cssDir.delete();
2388
						            tempFiles.add(tmpDir);
2389
									tempFiles.add(pdfFile); // delete this first later on
2390
2391
								} catch (Exception e) {
2392
									logMetacat.warn("Could not transform metadata", e);
2393
								}
2394
							}
2395
2396
2397 8437 walker
							//If this is in eml format, extract the filename and GUID from each entity in its package
2398
							if (metadataSysMeta.getFormatId().getValue().startsWith("eml://")) {
2399
								//Get the package
2400
								DataPackageParserInterface parser = new Eml200DataPackageParser();
2401
								InputStream emlStream = this.get(session, metadataID);
2402
								parser.parse(emlStream);
2403
								DataPackage dataPackage = parser.getDataPackage();
2404
2405
								//Get all the entities in this package and loop through each to extract its ID and file name
2406
								Entity[] entities = dataPackage.getEntityList();
2407
								for(Entity entity: entities){
2408
									try{
2409
										//Get the file name from the metadata
2410
										String fileNameFromMetadata = entity.getName();
2411
2412
										//Get the ecogrid URL from the metadata
2413
										String ecogridIdentifier = entity.getEntityIdentifier();
2414
										//Parse the ecogrid URL to get the local id
2415
										String idFromMetadata = DocumentUtil.getAccessionNumberFromEcogridIdentifier(ecogridIdentifier);
2416
2417
										//Get the docid and rev pair
2418
										String docid = DocumentUtil.getDocIdFromString(idFromMetadata);
2419
										String rev = DocumentUtil.getRevisionStringFromString(idFromMetadata);
2420
2421
										//Get the GUID
2422
										String guid = IdentifierManager.getInstance().getGUID(docid, Integer.valueOf(rev));
2423
										Identifier dataIdentifier = new Identifier();
2424
										dataIdentifier.setValue(guid);
2425
2426
										//Add the GUID to our GUID & file name map
2427
										fileNames.put(dataIdentifier, fileNameFromMetadata);
2428
									}
2429
									catch(Exception e){
2430
										//Prevent just one entity error
2431
										e.printStackTrace();
2432
										logMetacat.debug(e.getMessage(), e);
2433
									}
2434
								}
2435
							}
2436
						}
2437
						catch(Exception e){
2438
							//Catch errors that would prevent package download
2439
							logMetacat.debug(e.toString());
2440
						}
2441
					}
2442 7855 leinfelder
					packagePids.addAll(entries.keySet());
2443
					for (List<Identifier> dataPids: entries.values()) {
2444
						packagePids.addAll(dataPids);
2445
					}
2446 7850 leinfelder
				}
2447 7855 leinfelder
			} else {
2448
				// just the lone pid in this package
2449 9493 tao
				//packagePids.add(pid);
2450
			    //throw an invalid request exception
2451 9494 tao
			    throw new InvalidRequest("2873", "The given pid "+pid.getValue()+" is not a package id (resource map id). Please use a package id instead.");
2452 7850 leinfelder
			}
2453 8436 walker
2454 8437 walker
			//Create a temp file, then delete it and make a directory with that name
2455
			File tempDir = File.createTempFile("temp", Long.toString(System.nanoTime()));
2456
			tempDir.delete();
2457
			tempDir = new File(tempDir.getPath() + "_dir");
2458
			tempDir.mkdir();
2459 8436 walker
			tempFiles.add(tempDir);
2460 8800 leinfelder
			File pidMappingFile = new File(tempDir, "pid-mapping.txt");
2461 8436 walker
2462 7855 leinfelder
			// loop through the package contents
2463
			for (Identifier entryPid: packagePids) {
2464 8436 walker
				//Get the system metadata for each item
2465 8437 walker
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);
2466 8436 walker
2467
				String objectFormatType = ObjectFormatCache.getInstance().getFormat(entrySysMeta.getFormatId()).getFormatType();
2468 8437 walker
				String fileName = null;
2469 8436 walker
2470 8437 walker
				//TODO: Be more specific of what characters to replace. Make sure periods arent replaced for the filename from metadata
2471
				//Our default file name is just the ID + format type (e.g. walker.1.1-DATA)
2472
				fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + objectFormatType;
2473
2474 9542 leinfelder
				if (fileNames.containsKey(entryPid)){
2475 8437 walker
					//Let's use the file name and extension from the metadata is we have it
2476
					fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + fileNames.get(entryPid).replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
2477
				}
2478
2479 9541 leinfelder
				// ensure there is a file extension for the object
2480
				String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
2481
				fileName += extension;
2482
2483 9542 leinfelder
				// if SM has the file name, ignore everything else and use that
2484
				if (entrySysMeta.getFileName() != null) {
2485
					fileName = entrySysMeta.getFileName().replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
2486
				}
2487
2488 8436 walker
		        //Create a new file for this item and add to the list
2489 8437 walker
				File tempFile = new File(tempDir, fileName);
2490 7855 leinfelder
				tempFiles.add(tempFile);
2491 8436 walker
2492
				InputStream entryInputStream = this.get(session, entryPid);
2493 7855 leinfelder
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
2494
				bag.addFileToPayload(tempFile);
2495
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
2496
			}
2497
2498
			//add the the pid to data file map
2499
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
2500
			bag.addFileAsTag(pidMappingFile);
2501 8026 leinfelder
			tempFiles.add(pidMappingFile);
2502
2503 7855 leinfelder
			bag = bag.makeComplete();
2504 8026 leinfelder
2505 8436 walker
			///Now create the zip file
2506 8437 walker
			//Use the pid as the file name prefix, replacing all non-word characters
2507
			String zipName = pid.getValue().replaceAll("\\W", "_");
2508 8160 leinfelder
2509 8436 walker
			File bagFile = new File(tempDir, zipName+".zip");
2510
2511 7855 leinfelder
			bag.setFile(bagFile);
2512 7860 leinfelder
			ZipWriter zipWriter = new ZipWriter(bagFactory);
2513 7855 leinfelder
			bag.write(zipWriter, bagFile);
2514
			bagFile = bag.getFile();
2515 8026 leinfelder
			// use custom FIS that will delete the file when closed
2516
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
2517
			// also mark for deletion on shutdown in case the stream is never closed
2518
			bagFile.deleteOnExit();
2519 8436 walker
			tempFiles.add(bagFile);
2520 7855 leinfelder
2521 8026 leinfelder
			// clean up other temp files
2522 8800 leinfelder
			for (int i=tempFiles.size()-1; i>=0; i--){
2523
				tempFiles.get(i).delete();
2524 7855 leinfelder
			}
2525 8436 walker
2526 7855 leinfelder
		} catch (IOException e) {
2527
			// report as service failure
2528 9538 tao
		    e.printStackTrace();
2529 7855 leinfelder
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2530
			sf.initCause(e);
2531
			throw sf;
2532
		} catch (OREException e) {
2533
			// report as service failure
2534 9538 tao
		    e.printStackTrace();
2535 7855 leinfelder
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2536
			sf.initCause(e);
2537
			throw sf;
2538
		} catch (URISyntaxException e) {
2539
			// report as service failure
2540 9538 tao
		    e.printStackTrace();
2541 7855 leinfelder
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2542
			sf.initCause(e);
2543
			throw sf;
2544
		} catch (OREParserException e) {
2545
			// report as service failure
2546 9538 tao
		    e.printStackTrace();
2547 7855 leinfelder
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2548
			sf.initCause(e);
2549
			throw sf;
2550 7850 leinfelder
		}
2551
2552
		return bagInputStream;
2553 8810 leinfelder
	}
2554 9353 tao
2555
	 /**
2556
	   * Archives an object, where the object is either a
2557
	   * data object or a science metadata object.
2558
	   *
2559
	   * @param session - the Session object containing the credentials for the Subject
2560
	   * @param pid - The object identifier to be archived
2561
	   *
2562
	   * @return pid - the identifier of the object used for the archiving
2563
	   *
2564
	   * @throws InvalidToken
2565
	   * @throws ServiceFailure
2566
	   * @throws NotAuthorized
2567
	   * @throws NotFound
2568
	   * @throws NotImplemented
2569
	   * @throws InvalidRequest
2570
	   */
2571
	  public Identifier archive(Session session, Identifier pid)
2572
	      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2573 9478 tao
	      if(isReadOnlyMode()) {
2574 9481 tao
	            throw new ServiceFailure("2912", ReadOnlyChecker.DATAONEERROR);
2575 9478 tao
	        }
2576 9353 tao
	      boolean allowed = false;
2577
	      // do we have a valid pid?
2578
	      if (pid == null || pid.getValue().trim().equals("")) {
2579
	          throw new ServiceFailure("1350", "The provided identifier was invalid.");
2580
	      }
2581
2582
	      String serviceFailureCode = "1350";
2583
	      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2584
	      if(sid != null) {
2585
	          pid = sid;
2586
	      }
2587
	      // does the subject have archive (a D1 CHANGE_PERMISSION level) privileges on the pid?
2588
	      try {
2589
	            allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
2590
	        } catch (InvalidRequest e) {
2591
	          throw new ServiceFailure("1350", e.getDescription());
2592
	        }
2593
2594
	      if (allowed) {
2595
	         try {
2596
	             HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
2597
	             logMetacat.debug("MNodeService.archive - lock the identifier "+pid.getValue()+" in the system metadata map.");
2598
	             SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
2599 9354 tao
	             boolean needModifyDate = true;
2600 9373 tao
	             boolean logArchive = true;
2601
	             super.archiveObject(logArchive, session, pid, sysmeta, needModifyDate);
2602 9353 tao
	         } finally {
2603
	             HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
2604
	             logMetacat.debug("MNodeService.archive - unlock the identifier "+pid.getValue()+" in the system metadata map.");
2605
	         }
2606
2607
2608
	      } else {
2609
	          throw new NotAuthorized("1320", "The provided identity does not have " + "permission to archive the object on the Node.");
2610
	      }
2611
2612
	      return pid;
2613
	  }
2614 9190 tao
2615
	/**
2616
	 * Update the system metadata of the specified pid.
2617
	 */
2618 9177 tao
	@Override
2619
	public boolean updateSystemMetadata(Session session, Identifier pid,
2620
            SystemMetadata sysmeta) throws NotImplemented, NotAuthorized,
2621
            ServiceFailure, InvalidRequest, InvalidSystemMetadata, InvalidToken {
2622 9478 tao
2623
	  if(isReadOnlyMode()) {
2624 9481 tao
            throw new ServiceFailure("4868",  ReadOnlyChecker.DATAONEERROR);
2625 9478 tao
      }
2626 9177 tao
	 if(sysmeta == null) {
2627 9478 tao
	     throw  new InvalidRequest("4869", "The system metadata object should NOT be null in the updateSystemMetadata request.");
2628 9177 tao
	 }
2629 9190 tao
	 if(pid == null || pid.getValue() == null) {
2630 9478 tao
         throw new InvalidRequest("4869", "Please specify the id in the updateSystemMetadata request ") ;
2631 9190 tao
     }
2632
2633
     if (session == null) {
2634
         //TODO: many of the thrown exceptions do not use the correct error codes
2635
         //check these against the docs and correct them
2636
         throw new NotAuthorized("4861", "No Session - could not authorize for updating system metadata." +
2637
                 "  If you are not logged in, please do so and retry the request.");
2638
     } else {
2639
         try {
2640
             //Following session can do the change:
2641 9263 tao
           //- Authoritative Member Node (we can use isNodeAdmin since we checked isAuthoritativeNode )
2642 9190 tao
             //- Owner of object (coved by the userHasPermission method)
2643
             //- user subjects with the change permission
2644
             //Note: Coordinating Node can not because MN is authoritative
2645 9263 tao
             /*if(!isAuthoritativeNode(pid)) {
2646
                throw  new InvalidRequest("4863", "Client can only call updateSystemMetadata request on the authoritative memember node.");
2647
             }
2648 9190 tao
             if(!isNodeAdmin(session) && !userHasPermission(session, pid, Permission.CHANGE_PERMISSION)) {
2649
                 throw new NotAuthorized("4861", "The client -"+ session.getSubject().getValue()+ "is not authorized for updating the system metadata of the object "+pid.getValue());
2650 9263 tao
             }*/
2651
             if(!allowUpdating(session, pid, Permission.CHANGE_PERMISSION)) {
2652
                 throw new NotAuthorized("4861", "The client -"+ session.getSubject().getValue()+ "is not authorized for updating the system metadata of the object "+pid.getValue());
2653 9190 tao
             }
2654
         } catch (NotFound e) {
2655 9478 tao
             throw new InvalidRequest("4869", "Can't determine if the client has the permission to update the system metacat of the object with id "+pid.getValue()+" since "+e.getDescription());
2656 9997 tao
         } catch(ServiceFailure e) {
2657
             throw new ServiceFailure("4868", "Can't determine if the client has the permission to update the system metacat of the object with id "+pid.getValue()+" since "+e.getDescription());
2658
         } catch(NotAuthorized e) {
2659
             throw new NotAuthorized("4861", "Can't determine if the client has the permission to update the system metacat of the object with id "+pid.getValue()+" since "+e.getDescription());
2660
         } catch(NotImplemented e) {
2661
             throw new NotImplemented("4866","Can't determine if the client has the permission to update the system metacat of the object with id "+pid.getValue()+" since "+e.getDescription());
2662
         } catch(InvalidRequest e) {
2663
             throw new InvalidRequest("4869", "Can't determine if the client has the permission to update the system metacat of the object with id "+pid.getValue()+" since "+e.getDescription());
2664
         } catch(InvalidToken e) {
2665
             throw new InvalidToken("4957", "Can't determine if the client has the permission to update the system metacat of the object with id "+pid.getValue()+" since "+e.getDescription());
2666 9190 tao
         }
2667
2668
     }
2669 9335 tao
      //update the system metadata locally
2670
      boolean success = false;
2671
      try {
2672
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
2673
          SystemMetadata currentSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
2674
          if(currentSysmeta == null) {
2675 9478 tao
              throw  new InvalidRequest("4869", "We can't find the current system metadata on the member node for the id "+pid.getValue());
2676 9335 tao
          }
2677
          Date currentModiDate = currentSysmeta.getDateSysMetadataModified();
2678
          Date commingModiDate = sysmeta.getDateSysMetadataModified();
2679
          if(commingModiDate == null) {
2680 9478 tao
              throw  new InvalidRequest("4869", "The system metadata modification date can't be null.");
2681 9335 tao
          }
2682
          if(currentModiDate != null && commingModiDate.getTime() != currentModiDate.getTime()) {
2683 9478 tao
              throw new InvalidRequest("4869", "Your system metadata modification date is "+commingModiDate.toString()+
2684 9335 tao
                      ". It doesn't match our current system metadata modification date in the member node - "+currentModiDate.toString()+
2685
                      ". Please check if you have got the newest version of the system metadata before the modification.");
2686
          }
2687
          boolean needUpdateModificationDate = true;
2688 9354 tao
          boolean fromCN = false;
2689
          success = updateSystemMetadata(session, pid, sysmeta, needUpdateModificationDate, currentSysmeta, fromCN);
2690 9335 tao
      } finally {
2691
          HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
2692
      }
2693 9177 tao
2694
      if(success) {
2695 10102 tao
          this.cn = D1Client.getCN();
2696 9190 tao
          //TODO
2697 9177 tao
          //notify the cns the synchornize the new system metadata.
2698 10102 tao
          // run it in a thread to avoid connection timeout
2699
          Runnable runner = new Runnable() {
2700
              private CNode cNode = null;
2701
              private SystemMetadata sys = null;
2702
              private Identifier id = null;
2703
              @Override
2704
              public void run() {
2705
                  try {
2706
                      if(this.cNode == null)  {
2707
                          logMetacat.warn("MNodeService.updateSystemMetadata - can't get the instance of the CN. So can't call cn.synchronize to update the system metadata in CN.");
2708
                      } else if(id != null) {
2709
                          logMetacat.info("MNodeService.updateSystemMetadata - calling cn.synchornized in another thread for pid "+id.getValue());
2710
                          this.cNode.synchronize(null, id);
2711
                      } else {
2712 10104 tao
                          logMetacat.warn("MNodeService.updateSystemMetadata - the pid is null. So can't call cn.synchronize to update the system metadata in CN.");
2713 10102 tao
                      }
2714
                  } catch (BaseException e) {
2715
                      e.printStackTrace();
2716
                      logMetacat.error("It is a DataONEBaseException and its detail code is "+e.getDetail_code() +" and its code is "+e.getCode());
2717
                      logMetacat.error("Can't update the systemmetadata of pid "+id.getValue()+" in CNs through cn.synchronize method since "+e.getMessage(), e);
2718
                  } catch (Exception e) {
2719
                      e.printStackTrace();
2720
                      logMetacat.error("Can't update the systemmetadata of pid "+id.getValue()+" in CNs through cn.synchronize method since "+e.getMessage(), e);
2721
                  }
2722
2723
                  // attempt to re-register the identifier (it checks if it is a doi)
2724
                  try {
2725
                      logMetacat.info("MNodeSerice.updateSystemMetadata - register doi if the pid "+sys.getIdentifier().getValue()+" is a doi");
2726
                      DOIService.getInstance().registerDOI(sys);
2727
                  } catch (Exception e) {
2728
                    logMetacat.warn("Could not [re]register DOI: " + e.getMessage(), e);
2729
                  }
2730 9256 tao
              }
2731 10102 tao
              private Runnable init(CNode cn, SystemMetadata sys, Identifier id){
2732
                  this.cNode = cn;
2733
                  this.sys = sys;
2734
                  this.id = id;
2735
                  return this;
2736
2737
              }
2738
          }.init(cn, sysmeta, pid);
2739
          // submit the task, and that's it
2740 10104 tao
          if(executor != null) {
2741
              executor.submit(runner);
2742 10102 tao
          } else {
2743 10104 tao
              logMetacat.warn("MNodeSerivce.updateSystemMetadata - since the executor service for submitting the call of cn.synchronize() is null, the system metadata change of the id "+pid.getValue()+" can't go to cn through cn.synchronize.");
2744 9246 tao
          }
2745 9177 tao
      }
2746
      return success;
2747
    }
2748 9190 tao
2749
	/*
2750
     * Determine if the current node is the authoritative node for the given pid.
2751
     */
2752 9374 tao
    protected boolean isAuthoritativeNode(Identifier pid) throws InvalidRequest {
2753 9190 tao
        boolean isAuthoritativeNode = false;
2754
        if(pid != null && pid.getValue() != null) {
2755
            SystemMetadata sys = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
2756
            if(sys != null) {
2757
                NodeReference node = sys.getAuthoritativeMemberNode();
2758
                if(node != null) {
2759
                    String nodeValue = node.getValue();
2760
                    logMetacat.debug("The authoritative node for id "+pid.getValue()+" is "+nodeValue);
2761
                    //System.out.println("The authoritative node for id "+pid.getValue()+" is "+nodeValue);
2762
                    String currentNodeId = Settings.getConfiguration().getString("dataone.nodeId");
2763
                    logMetacat.debug("The node id in metacat.properties is "+currentNodeId);
2764
                    //System.out.println("The node id in metacat.properties is "+currentNodeId);
2765
                    if(currentNodeId != null && !currentNodeId.trim().equals("") && currentNodeId.equals(nodeValue)) {
2766 10021 tao
                        logMetacat.debug("They are matching, so the authoritative mn of the object "+pid.getValue()+" is the current node");
2767 9190 tao
                        //System.out.println("They are matching");
2768
                        isAuthoritativeNode = true;
2769
                    }
2770 9374 tao
                } else {
2771
                    throw new InvalidRequest("4869", "Coudn't find the authoritative member node in the system metadata associated with the pid "+pid.getValue());
2772 9190 tao
                }
2773 9374 tao
            } else {
2774
                throw new InvalidRequest("4869", "Coudn't find the system metadata associated with the pid "+pid.getValue());
2775 9190 tao
            }
2776 9374 tao
        } else {
2777
            throw new InvalidRequest("4869", "The request pid is null");
2778 9190 tao
        }
2779
        return isAuthoritativeNode;
2780
    }
2781 6795 cjones
2782 9263 tao
    /*
2783
     * Rules are:
2784
     * 1. If the session has an cn object, it is allowed.
2785
     * 2. If it is not a cn object, the client should have approperate permission and it should also happen on the authorative node.
2786 9700 cjones
     * 3. If it's the authoritative node, the MN Admin Subject is allowed.
2787 9263 tao
     */
2788 9997 tao
    private boolean allowUpdating(Session session, Identifier pid, Permission permission) throws NotAuthorized, NotFound, InvalidRequest, ServiceFailure, NotImplemented, InvalidToken {
2789 9263 tao
        boolean allow = false;
2790 9781 cjones
2791 9700 cjones
        if( isCNAdmin (session) ) {
2792 9263 tao
            allow = true;
2793 9700 cjones
2794 9263 tao
        } else {
2795 9700 cjones
            if( isAuthoritativeNode(pid) ) {
2796
2797 9781 cjones
            	// Check for admin authorization
2798 9700 cjones
            	try {
2799 9781 cjones
					allow = isNodeAdmin(session);
2800 9700 cjones
2801
				} catch (NotImplemented e) {
2802
					logMetacat.debug("Failed to authorize the Member Node Admin Subject: " + e.getMessage());
2803
2804
				} catch (ServiceFailure e) {
2805
					logMetacat.debug("Failed to authorize the Member Node Admin Subject: " + e.getMessage());
2806
2807
				}
2808
2809 9781 cjones
            	// Check for user authorization
2810
            	if ( !allow ) {
2811
                    allow = userHasPermission(session, pid, permission);
2812 9700 cjones
2813 9781 cjones
            	}
2814 9700 cjones
2815 9263 tao
            } else {
2816 10021 tao
                throw new NotAuthorized("4861", "Client can only call the request on the authoritative memember node of the object "+pid.getValue());
2817 9700 cjones
2818 9263 tao
            }
2819
        }
2820
        return allow;
2821
2822
    }
2823
2824 9478 tao
    /**
2825
     * Check if the metacat is in the read-only mode.
2826
     * @return true if it is; otherwise false.
2827
     */
2828
    protected boolean isReadOnlyMode() {
2829
        boolean readOnly = false;
2830
        ReadOnlyChecker checker = new ReadOnlyChecker();
2831
        readOnly = checker.isReadOnly();
2832
        return readOnly;
2833
    }
2834
2835 6179 cjones
}