Project

General

Profile

1
/**
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
import java.io.ByteArrayInputStream;
27
import java.io.ByteArrayOutputStream;
28
import java.io.File;
29
import java.io.FileInputStream;
30
import java.io.FileOutputStream;
31
import java.io.IOException;
32
import java.io.InputStream;
33
import java.io.InputStreamReader;
34
import java.io.OutputStreamWriter;
35
import java.io.StringReader;
36
import java.io.UnsupportedEncodingException;
37
import java.io.Writer;
38
import java.math.BigInteger;
39
import java.net.URISyntaxException;
40
import java.nio.charset.Charset;
41
import java.security.NoSuchAlgorithmException;
42
import java.sql.SQLException;
43
import java.util.ArrayList;
44
import java.util.Calendar;
45
import java.util.Date;
46
import java.util.HashMap;
47
import java.util.HashSet;
48
import java.util.Hashtable;
49
import java.util.List;
50
import java.util.Map;
51
import java.util.Set;
52
import java.util.Timer;
53
import java.util.UUID;
54
import java.util.Vector;
55

    
56
import javax.servlet.http.HttpServletRequest;
57
import javax.xml.transform.Result;
58
import javax.xml.transform.Source;
59
import javax.xml.transform.TransformerException;
60
import javax.xml.transform.TransformerFactory;
61
import javax.xml.transform.dom.DOMSource;
62
import javax.xml.transform.stream.StreamResult;
63

    
64
import org.apache.commons.io.IOUtils;
65
import org.apache.log4j.Logger;
66
import org.dataone.client.v2.CNode;
67
import org.dataone.client.v2.itk.D1Client;
68
import org.dataone.client.v2.MNode;
69
import org.dataone.client.v2.formats.ObjectFormatCache;
70
import org.dataone.client.auth.CertificateManager;
71
import org.dataone.client.v2.formats.ObjectFormatInfo;
72
import org.dataone.configuration.Settings;
73
import org.dataone.ore.ResourceMapFactory;
74
import org.dataone.service.exceptions.BaseException;
75
import org.dataone.service.exceptions.IdentifierNotUnique;
76
import org.dataone.service.exceptions.InsufficientResources;
77
import org.dataone.service.exceptions.InvalidRequest;
78
import org.dataone.service.exceptions.InvalidSystemMetadata;
79
import org.dataone.service.exceptions.InvalidToken;
80
import org.dataone.service.exceptions.NotAuthorized;
81
import org.dataone.service.exceptions.NotFound;
82
import org.dataone.service.exceptions.NotImplemented;
83
import org.dataone.service.exceptions.ServiceFailure;
84
import org.dataone.service.exceptions.SynchronizationFailed;
85
import org.dataone.service.exceptions.UnsupportedType;
86
import org.dataone.service.mn.tier1.v2.MNCore;
87
import org.dataone.service.mn.tier1.v2.MNRead;
88
import org.dataone.service.mn.tier2.v2.MNAuthorization;
89
import org.dataone.service.mn.tier3.v2.MNStorage;
90
import org.dataone.service.mn.tier4.v2.MNReplication;
91
import org.dataone.service.mn.v2.MNPackage;
92
import org.dataone.service.mn.v2.MNQuery;
93
import org.dataone.service.mn.v2.MNView;
94
import org.dataone.service.types.v1.AccessRule;
95
import org.dataone.service.types.v1.Checksum;
96
import org.dataone.service.types.v1.DescribeResponse;
97
import org.dataone.service.types.v1.Event;
98
import org.dataone.service.types.v1.Identifier;
99
import org.dataone.service.types.v2.Log;
100
import org.dataone.service.types.v2.LogEntry;
101
import org.dataone.service.types.v2.OptionList;
102
import org.dataone.service.types.v1.MonitorInfo;
103
import org.dataone.service.types.v1.MonitorList;
104
import org.dataone.service.types.v2.Node;
105
import org.dataone.service.types.v2.NodeList;
106
import org.dataone.service.types.v1.NodeReference;
107
import org.dataone.service.types.v1.NodeState;
108
import org.dataone.service.types.v1.NodeType;
109
import org.dataone.service.types.v2.ObjectFormat;
110
import org.dataone.service.types.v1.Group;
111
import org.dataone.service.types.v1.ObjectFormatIdentifier;
112
import org.dataone.service.types.v1.ObjectList;
113
import org.dataone.service.types.v1.Permission;
114
import org.dataone.service.types.v1.Ping;
115
import org.dataone.service.types.v1.Replica;
116
import org.dataone.service.types.v1.ReplicationStatus;
117
import org.dataone.service.types.v1.Schedule;
118
import org.dataone.service.types.v1.Service;
119
import org.dataone.service.types.v1.Services;
120
import org.dataone.service.types.v1.Session;
121
import org.dataone.service.types.v1.Subject;
122
import org.dataone.service.types.v1.Synchronization;
123
import org.dataone.service.types.v2.SystemMetadata;
124
import org.dataone.service.types.v1.util.AuthUtils;
125
import org.dataone.service.types.v1.util.ChecksumUtil;
126
import org.dataone.service.types.v1_1.QueryEngineDescription;
127
import org.dataone.service.types.v1_1.QueryEngineList;
128
import org.dataone.service.types.v1_1.QueryField;
129
import org.dataone.service.util.Constants;
130
import org.dataone.service.util.TypeMarshaller;
131
import org.dspace.foresite.OREException;
132
import org.dspace.foresite.OREParserException;
133
import org.dspace.foresite.ORESerialiserException;
134
import org.dspace.foresite.ResourceMap;
135
import org.ecoinformatics.datamanager.parser.DataPackage;
136
import org.ecoinformatics.datamanager.parser.Entity;
137
import org.ecoinformatics.datamanager.parser.generic.DataPackageParserInterface;
138
import org.ecoinformatics.datamanager.parser.generic.Eml200DataPackageParser;
139
import org.w3c.dom.Document;
140

    
141
import edu.ucsb.nceas.ezid.EZIDException;
142
import edu.ucsb.nceas.metacat.DBQuery;
143
import edu.ucsb.nceas.metacat.DBTransform;
144
import edu.ucsb.nceas.metacat.EventLog;
145
import edu.ucsb.nceas.metacat.IdentifierManager;
146
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
147
import edu.ucsb.nceas.metacat.MetaCatServlet;
148
import edu.ucsb.nceas.metacat.MetacatHandler;
149
import edu.ucsb.nceas.metacat.ReadOnlyChecker;
150
import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines;
151
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
152
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
153
import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler;
154
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
155
import edu.ucsb.nceas.metacat.properties.PropertyService;
156
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
157
import edu.ucsb.nceas.metacat.util.DeleteOnCloseFileInputStream;
158
import edu.ucsb.nceas.metacat.util.DocumentUtil;
159
import edu.ucsb.nceas.metacat.util.SkinUtil;
160
import edu.ucsb.nceas.metacat.util.SystemUtil;
161
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
162
import edu.ucsb.nceas.utilities.XMLUtilities;
163
import edu.ucsb.nceas.utilities.export.HtmlToPdf;
164
import gov.loc.repository.bagit.Bag;
165
import gov.loc.repository.bagit.BagFactory;
166
import gov.loc.repository.bagit.writer.impl.ZipWriter;
167

    
168
/**
169
 * Represents Metacat's implementation of the DataONE Member Node 
170
 * service API. Methods implement the various MN* interfaces, and methods common
171
 * to both Member Node and Coordinating Node interfaces are found in the
172
 * D1NodeService base class.
173
 * 
174
 * Implements:
175
 * MNCore.ping()
176
 * MNCore.getLogRecords()
177
 * MNCore.getObjectStatistics()
178
 * MNCore.getOperationStatistics()
179
 * MNCore.getStatus()
180
 * MNCore.getCapabilities()
181
 * MNRead.get()
182
 * MNRead.getSystemMetadata()
183
 * MNRead.describe()
184
 * MNRead.getChecksum()
185
 * MNRead.listObjects()
186
 * MNRead.synchronizationFailed()
187
 * MNAuthorization.isAuthorized()
188
 * MNAuthorization.setAccessPolicy()
189
 * MNStorage.create()
190
 * MNStorage.update()
191
 * MNStorage.delete()
192
 * MNStorage.updateSystemMetadata()
193
 * MNReplication.replicate()
194
 * 
195
 */
196
public class MNodeService extends D1NodeService 
197
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage, MNQuery, MNView, MNPackage {
198

    
199
    //private static final String PATHQUERY = "pathquery";
200
	public static final String UUID_SCHEME = "UUID";
201
	public static final String DOI_SCHEME = "DOI";
202
	private static final String UUID_PREFIX = "urn:uuid:";
203
	
204
	private static String XPATH_EML_ID = "/eml:eml/@packageId";
205

    
206
	/* the logger instance */
207
    private Logger logMetacat = null;
208
    
209
    /* A reference to a remote Memeber Node */
210
    //private MNode mn;
211
    
212
    /* A reference to a Coordinating Node */
213
    private CNode cn;
214

    
215

    
216
    /**
217
     * Singleton accessor to get an instance of MNodeService.
218
     * 
219
     * @return instance - the instance of MNodeService
220
     */
221
    public static MNodeService getInstance(HttpServletRequest request) {
222
        return new MNodeService(request);
223
    }
224

    
225
    /**
226
     * Constructor, private for singleton access
227
     */
228
    private MNodeService(HttpServletRequest request) {
229
        super(request);
230
        logMetacat = Logger.getLogger(MNodeService.class);
231
        
232
        // set the Member Node certificate file location
233
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
234
    }
235

    
236
    /**
237
     * Deletes an object from the Member Node, where the object is either a 
238
     * data object or a science metadata object.
239
     * 
240
     * @param session - the Session object containing the credentials for the Subject
241
     * @param pid - The object identifier to be deleted
242
     * 
243
     * @return pid - the identifier of the object used for the deletion
244
     * 
245
     * @throws InvalidToken
246
     * @throws ServiceFailure
247
     * @throws NotAuthorized
248
     * @throws NotFound
249
     * @throws NotImplemented
250
     * @throws InvalidRequest
251
     */
252
    @Override
253
    public Identifier delete(Session session, Identifier pid) 
254
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
255

    
256
        if(isReadOnlyMode()) {
257
            throw new ServiceFailure("2902", ReadOnlyChecker.DATAONEERROR);
258
        }
259
    	// only admin of  the MN or the CN is allowed a full delete
260
        boolean allowed = false;
261
        allowed = isAdminAuthorized(session);
262
        
263
        String serviceFailureCode = "2902";
264
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
265
        if(sid != null) {
266
            pid = sid;
267
        }
268
        
269
        //check if it is the authoritative member node
270
        if(!allowed) {
271
            allowed = isAuthoritativeMNodeAdmin(session, pid);
272
        }
273
        
274
        if (!allowed) { 
275
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
276
        }
277
    	
278
    	// defer to superclass implementation
279
        return super.delete(session, pid);
280
    }
281

    
282
    /**
283
     * Updates an existing object by creating a new object identified by 
284
     * newPid on the Member Node which explicitly obsoletes the object 
285
     * identified by pid through appropriate changes to the SystemMetadata 
286
     * of pid and newPid
287
     * 
288
     * @param session - the Session object containing the credentials for the Subject
289
     * @param pid - The identifier of the object to be updated
290
     * @param object - the new object bytes
291
     * @param sysmeta - the new system metadata describing the object
292
     * 
293
     * @return newPid - the identifier of the new object
294
     * 
295
     * @throws InvalidToken
296
     * @throws ServiceFailure
297
     * @throws NotAuthorized
298
     * @throws NotFound
299
     * @throws NotImplemented
300
     * @throws IdentifierNotUnique
301
     * @throws UnsupportedType
302
     * @throws InsufficientResources
303
     * @throws InvalidSystemMetadata
304
     * @throws InvalidRequest
305
     */
306
    @Override
307
    public Identifier update(Session session, Identifier pid, InputStream object, 
308
        Identifier newPid, SystemMetadata sysmeta) 
309
        throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
310
        UnsupportedType, InsufficientResources, NotFound, 
311
        InvalidSystemMetadata, NotImplemented, InvalidRequest {
312
        
313
        if(isReadOnlyMode()) {
314
            throw new ServiceFailure("1310", ReadOnlyChecker.DATAONEERROR);
315
        }
316

    
317
        //transform a sid to a pid if it is applicable
318
        String serviceFailureCode = "1310";
319
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
320
        if(sid != null) {
321
            pid = sid;
322
        }
323
        
324
        String localId = null;
325
        boolean allowed = false;
326
        boolean isScienceMetadata = false;
327
        
328
        if (session == null) {
329
        	throw new InvalidToken("1210", "No session has been provided");
330
        }
331
        Subject subject = session.getSubject();
332

    
333
        // verify the pid is valid format
334
        if (!isValidIdentifier(pid)) {
335
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
336
        }
337
        
338
        // verify the new pid is valid format
339
        if (!isValidIdentifier(newPid)) {
340
            throw new InvalidRequest("1202", "The provided identifier is invalid.");
341
        }
342
        
343
        // make sure that the newPid doesn't exists
344
        boolean idExists = true;
345
        try {
346
            idExists = IdentifierManager.getInstance().identifierExists(newPid.getValue());
347
        } catch (SQLException e) {
348
            throw new ServiceFailure("1310", 
349
                                    "The requested identifier " + newPid.getValue() +
350
                                    " couldn't be determined if it is unique since : "+e.getMessage());
351
        }
352
        if (idExists) {
353
                throw new IdentifierNotUnique("1220", 
354
                          "The requested identifier " + newPid.getValue() +
355
                          " is already used by another object and" +
356
                          "therefore can not be used for this object. Clients should choose" +
357
                          "a new identifier that is unique and retry the operation or " +
358
                          "use CN.reserveIdentifier() to reserve one.");
359
            
360
        }
361
        
362
       
363

    
364
        // check for the existing identifier
365
        try {
366
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
367
            
368
        } catch (McdbDocNotFoundException e) {
369
            throw new InvalidRequest("1202", "The object with the provided " + 
370
                "identifier was not found.");
371
            
372
        } catch (SQLException ee) {
373
            throw new ServiceFailure("1310", "The object with the provided " + 
374
                    "identifier "+pid.getValue()+" can't be identified since - "+ee.getMessage());
375
        }
376
        
377
        // set the originating node
378
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
379
        sysmeta.setOriginMemberNode(originMemberNode);
380
        
381
        // set the submitter to match the certificate
382
        sysmeta.setSubmitter(subject);
383
        // set the dates
384
        Date now = Calendar.getInstance().getTime();
385
        sysmeta.setDateSysMetadataModified(now);
386
        sysmeta.setDateUploaded(now);
387
        
388
        // make sure serial version is set to something
389
        BigInteger serialVersion = sysmeta.getSerialVersion();
390
        if (serialVersion == null) {
391
        	sysmeta.setSerialVersion(BigInteger.ZERO);
392
        }
393

    
394
        // does the subject have WRITE ( == update) priveleges on the pid?
395
        //allowed = isAuthorized(session, pid, Permission.WRITE);
396
        //CN having the permission is allowed; user with the write permission and calling on the authoritative node is allowed.
397
        allowed = allowUpdating(session, pid, Permission.WRITE);
398
        if (allowed) {
399
        	
400
        	// check quality of SM
401
        	if (sysmeta.getObsoletedBy() != null) {
402
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
403
        	}
404
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
405
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
406
        	}
407

    
408
            // get the existing system metadata for the object
409
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
410
            //System.out.println("the archive is "+existingSysMeta.getArchived());
411
            //Base on documentation, we can't update an archived object:
412
            //The update operation MUST fail with Exceptions.InvalidRequest on objects that have the Types.SystemMetadata.archived property set to true.
413
            if(existingSysMeta.getArchived() != null && existingSysMeta.getArchived()) {
414
                throw new InvalidRequest("1202","An archived object"+pid.getValue()+" can't be updated");
415
            }
416

    
417
            // check for previous update
418
            // see: https://redmine.dataone.org/issues/3336
419
            Identifier existingObsoletedBy = existingSysMeta.getObsoletedBy();
420
            if (existingObsoletedBy != null) {
421
            	throw new InvalidRequest("1202", 
422
            			"The previous identifier has already been made obsolete by: " + existingObsoletedBy.getValue());
423
            }
424
            //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.
425
            Identifier sidInSys = sysmeta.getSeriesId();
426
            if(sidInSys != null) {
427
                if (!isValidIdentifier(sidInSys)) {
428
                    throw new InvalidSystemMetadata("1300", "The provided series id in the system metadata is invalid.");
429
                }
430
                Identifier previousSid = existingSysMeta.getSeriesId();
431
                if(previousSid != null) {
432
                    // there is a previous sid, if the new sid doesn't match it, the new sid should be non-existing.
433
                    if(!sidInSys.getValue().equals(previousSid.getValue())) {
434
                        try {
435
                            idExists = IdentifierManager.getInstance().identifierExists(sidInSys.getValue());
436
                        } catch (SQLException e) {
437
                            throw new ServiceFailure("1310", 
438
                                                    "The requested identifier " + sidInSys.getValue() +
439
                                                    " couldn't be determined if it is unique since : "+e.getMessage());
440
                        }
441
                        if(idExists) {
442
                            throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata doesn't match the previous series id "
443
                                                            +previousSid.getValue()+", so it should NOT exist. However, it was used by another object.");
444
                        }
445
                    }
446
                } else {
447
                    // there is no previous sid, the new sid should be non-existing.
448
                    try {
449
                        idExists = IdentifierManager.getInstance().identifierExists(sidInSys.getValue());
450
                    } catch (SQLException e) {
451
                        throw new ServiceFailure("1310", 
452
                                                "The requested identifier " + sidInSys.getValue() +
453
                                                " couldn't be determined if it is unique since : "+e.getMessage());
454
                    }
455
                    if(idExists) {
456
                        throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata should NOT exist since the previous series id is null."
457
                                                        +"However, it was used by another object.");
458
                    }
459
                }
460
                //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)
461
                if(sidInSys.getValue().equals(newPid.getValue())) {
462
                    throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata shouldn't have the same value of the pid.");
463
                }
464
            }
465

    
466
            isScienceMetadata = isScienceMetadata(sysmeta);
467

    
468
            // do we have XML metadata or a data object?
469
            if (isScienceMetadata) {
470

    
471
                // update the science metadata XML document
472
                // TODO: handle non-XML metadata/data documents (like netCDF)
473
                // TODO: don't put objects into memory using stream to string
474
                //String objectAsXML = "";
475
                try {
476
                    //objectAsXML = IOUtils.toString(object, "UTF-8");
477
                	String formatId = null;
478
                	if(sysmeta.getFormatId() != null) {
479
                	    formatId = sysmeta.getFormatId().getValue();
480
                	}
481
                    localId = insertOrUpdateDocument(object, "UTF-8", pid, session, "update", formatId);
482
                    
483
                    // register the newPid and the generated localId
484
                    if (newPid != null) {
485
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
486
                    }
487

    
488
                } catch (IOException e) {
489
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
490
                    logMetacat.info(msg);
491
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
492

    
493
                }
494

    
495
            } else {
496

    
497
                // update the data object
498
                localId = insertDataObject(object, newPid, session);
499

    
500
            }
501
            
502
            // add the newPid to the obsoletedBy list for the existing sysmeta
503
            existingSysMeta.setObsoletedBy(newPid);
504
            //increase version
505
            BigInteger current = existingSysMeta.getSerialVersion();
506
            //System.out.println("the current version is "+current);
507
            current = current.add(BigInteger.ONE);
508
            //System.out.println("the new current version is "+current);
509
            existingSysMeta.setSerialVersion(current);
510
            // then update the existing system metadata
511
            updateSystemMetadata(existingSysMeta);
512

    
513
            // prep the new system metadata, add pid to the affected lists
514
            sysmeta.setObsoletes(pid);
515
            //sysmeta.addDerivedFrom(pid);
516

    
517
            // and insert the new system metadata
518
            insertSystemMetadata(sysmeta);
519

    
520
            // log the update event
521
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
522
            
523
            // attempt to register the identifier - it checks if it is a doi
524
            try {
525
    			DOIService.getInstance().registerDOI(sysmeta);
526
    		} catch (Exception e) {
527
                throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
528
    		}
529

    
530
        } else {
531
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
532
                    + " on the Member Node.");
533
        }
534

    
535
        return newPid;
536
    }
537

    
538
    public Identifier create(Session session, Identifier pid, InputStream object, SystemMetadata sysmeta) throws InvalidToken, ServiceFailure, NotAuthorized,
539
            IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, InvalidRequest {
540

    
541
        if(isReadOnlyMode()) {
542
            throw new ServiceFailure("1190", ReadOnlyChecker.DATAONEERROR);
543
        }
544
        // check for null session
545
        if (session == null) {
546
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
547
        }
548
        // verify the pid is valid format
549
        if (!isValidIdentifier(pid)) {
550
            throw new InvalidRequest("1102", "The provided identifier is invalid.");
551
        }
552
        // set the submitter to match the certificate
553
        sysmeta.setSubmitter(session.getSubject());
554
        // set the originating node
555
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
556
        sysmeta.setOriginMemberNode(originMemberNode);
557
        
558
        // if no authoritative MN, set it to the same
559
        if (sysmeta.getAuthoritativeMemberNode() == null) {
560
        	sysmeta.setAuthoritativeMemberNode(originMemberNode);
561
        }
562
        
563
        sysmeta.setArchived(false);
564

    
565
        // set the dates
566
        Date now = Calendar.getInstance().getTime();
567
        sysmeta.setDateSysMetadataModified(now);
568
        sysmeta.setDateUploaded(now);
569
        
570
        // set the serial version
571
        sysmeta.setSerialVersion(BigInteger.ZERO);
572

    
573
        // check that we are not attempting to subvert versioning
574
        if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
575
            throw new InvalidSystemMetadata("1180", 
576
              "The supplied system metadata is invalid. " +
577
              "The obsoletes field cannot have a value when creating entries.");
578
        }
579
        
580
        if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
581
            throw new InvalidSystemMetadata("1180", 
582
              "The supplied system metadata is invalid. " +
583
              "The obsoletedBy field cannot have a value when creating entries.");
584
        }
585
        
586
        // verify the sid in the system metadata
587
        Identifier sid = sysmeta.getSeriesId();
588
        boolean idExists = false;
589
        if(sid != null) {
590
            if (!isValidIdentifier(sid)) {
591
                throw new InvalidSystemMetadata("1180", "The provided series id is invalid.");
592
            }
593
            try {
594
                idExists = IdentifierManager.getInstance().identifierExists(sid.getValue());
595
            } catch (SQLException e) {
596
                throw new ServiceFailure("1190", 
597
                                        "The series identifier " + sid.getValue() +
598
                                        " in the system metadata couldn't be determined if it is unique since : "+e.getMessage());
599
            }
600
            if (idExists) {
601
                    throw new InvalidSystemMetadata("1180", 
602
                              "The series identifier " + sid.getValue() +
603
                              " is already used by another object and" +
604
                              "therefore can not be used for this object. Clients should choose" +
605
                              "a new identifier that is unique and retry the operation or " +
606
                              "use CN.reserveIdentifier() to reserve one.");
607
                
608
            }
609
            //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 )
610
            if(sid.getValue().equals(pid.getValue())) {
611
                throw new InvalidSystemMetadata("1180", "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
612
            }
613
        }
614

    
615
        // call the shared impl
616
        Identifier resultPid = super.create(session, pid, object, sysmeta);
617
        
618
        // attempt to register the identifier - it checks if it is a doi
619
        try {
620
			DOIService.getInstance().registerDOI(sysmeta);
621
		} catch (Exception e) {
622
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
623
			sf.initCause(e);
624
            throw sf;
625
		}
626
        
627
        // return 
628
		return resultPid ;
629
    }
630

    
631
    /**
632
     * Called by a Coordinating Node to request that the Member Node create a 
633
     * copy of the specified object by retrieving it from another Member 
634
     * Node and storing it locally so that it can be made accessible to 
635
     * the DataONE system.
636
     * 
637
     * @param session - the Session object containing the credentials for the Subject
638
     * @param sysmeta - Copy of the CN held system metadata for the object
639
     * @param sourceNode - A reference to node from which the content should be 
640
     *                     retrieved. The reference should be resolved by 
641
     *                     checking the CN node registry.
642
     * 
643
     * @return true if the replication succeeds
644
     * 
645
     * @throws ServiceFailure
646
     * @throws NotAuthorized
647
     * @throws NotImplemented
648
     * @throws UnsupportedType
649
     * @throws InsufficientResources
650
     * @throws InvalidRequest
651
     */
652
    @Override
653
    public boolean replicate(Session session, SystemMetadata sysmeta,
654
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
655
            NotAuthorized, InvalidRequest, InsufficientResources,
656
            UnsupportedType {
657
        /*if(isReadOnlyMode()) {
658
            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.");
659
        }*/
660

    
661
        if (session != null && sysmeta != null && sourceNode != null) {
662
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
663
                            "\tSession.Subject      = "                           +
664
                            session.getSubject().getValue() + "\n"                +
665
                            "\tidentifier           = "                           + 
666
                            sysmeta.getIdentifier().getValue()                    +
667
                            "\n" + "\tSource NodeReference ="                     +
668
                            sourceNode.getValue());
669
        }
670
        boolean result = false;
671
        String nodeIdStr = null;
672
        NodeReference nodeId = null;
673

    
674
        // get the referenced object
675
        Identifier pid = sysmeta.getIdentifier();
676
        // verify the pid is valid format
677
        if (!isValidIdentifier(pid)) {
678
            throw new InvalidRequest("2153", "The provided identifier in the system metadata is invalid.");
679
        }
680

    
681
        // get from the membernode
682
        // TODO: switch credentials for the server retrieval?
683
        this.cn = D1Client.getCN();
684
        InputStream object = null;
685
        Session thisNodeSession = null;
686
        SystemMetadata localSystemMetadata = null;
687
        BaseException failure = null;
688
        String localId = null;
689
        
690
        // TODO: check credentials
691
        // cannot be called by public
692
        if (session == null || session.getSubject() == null) {
693
            String msg = "No session was provided to replicate identifier " +
694
            sysmeta.getIdentifier().getValue();
695
            logMetacat.error(msg);
696
            throw new NotAuthorized("2152", msg);
697
            
698
        }
699

    
700

    
701
        // get the local node id
702
        try {
703
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
704
            nodeId = new NodeReference();
705
            nodeId.setValue(nodeIdStr);
706

    
707
        } catch (PropertyNotFoundException e1) {
708
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
709
            failure = new ServiceFailure("2151", msg);
710
            //setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
711
            logMetacat.error(msg);
712
            //return true;
713
            throw new ServiceFailure("2151", msg);
714

    
715
        }
716
        
717
        try {
718
            try {
719
                // do we already have a replica?
720
                try {
721
                    localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
722
                    // if we have a local id, get the local object
723
                    try {
724
                        object = MetacatHandler.read(localId);
725
                    } catch (Exception e) {
726
                        // NOTE: we may already know about this ID because it could be a data file described by a metadata file
727
                        // https://redmine.dataone.org/issues/2572
728
                        // TODO: fix this so that we don't prevent ourselves from getting replicas
729
                        
730
                        // let the CN know that the replication failed
731
                        logMetacat.warn("Object content not found on this node despite having localId: " + localId);
732
                        String msg = "Can't read the object bytes properly, replica is invalid.";
733
                        ServiceFailure serviceFailure = new ServiceFailure("2151", msg);
734
                        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, serviceFailure);
735
                        logMetacat.warn(msg);
736
                        throw serviceFailure;
737
                        
738
                    }
739

    
740
                } catch (McdbDocNotFoundException e) {
741
                    logMetacat.info("No replica found. Continuing.");
742
                    
743
                } catch (SQLException ee) {
744
                    throw new ServiceFailure("2151", "Couldn't identify the local id of the object with the specified identifier "
745
                                            +pid.getValue()+" since - "+ee.getMessage());
746
                }
747
                
748
                // no local replica, get a replica
749
                if ( object == null ) {
750
                    /*boolean success = true;
751
                    try {
752
                        //use the v2 ping api to connect the source node
753
                        mn.ping();
754
                    } catch (Exception e) {
755
                        success = false;
756
                    }*/
757
                    D1NodeVersionChecker checker = new D1NodeVersionChecker(sourceNode);
758
                    String nodeVersion = checker.getVersion("MNRead");
759
                    if(nodeVersion != null && nodeVersion.equals(D1NodeVersionChecker.V1)) {
760
                        //The source node is a v1 node, we use the v1 api
761
                        org.dataone.client.v1.MNode mNodeV1 =  org.dataone.client.v1.itk.D1Client.getMN(sourceNode);
762
                        object = mNodeV1.getReplica(thisNodeSession, pid);
763
                    } else if (nodeVersion != null && nodeVersion.equals(D1NodeVersionChecker.V2)){
764
                     // session should be null to use the default certificate
765
                        // location set in the Certificate manager
766
                        MNode mn = D1Client.getMN(sourceNode);
767
                        object = mn.getReplica(thisNodeSession, pid);
768
                    } else {
769
                        throw new ServiceFailure("2151", "The version of MNRead service is "+nodeVersion+" in the source node "+sourceNode.getValue()+" and it is supported. Please check the information in the cn");
770
                    }
771
                    
772
                    logMetacat.info("MNodeService.getReplica() called for identifier "
773
                                    + pid.getValue());
774

    
775
                }
776

    
777
            } catch (InvalidToken e) {            
778
                String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
779
                failure = new ServiceFailure("2151", msg);
780
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
781
                logMetacat.error(msg);
782
                throw new ServiceFailure("2151", msg);
783

    
784
            } catch (NotFound e) {
785
                String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
786
                failure = new ServiceFailure("2151", msg);
787
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
788
                logMetacat.error(msg);
789
                throw new ServiceFailure("2151", msg);
790

    
791
            } catch (NotAuthorized e) {
792
                String msg = "Could not retrieve object to replicate (NotAuthorized): "+ e.getMessage();
793
                failure = new ServiceFailure("2151", msg);
794
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
795
                logMetacat.error(msg);
796
                throw new ServiceFailure("2151", msg);
797
            } catch (NotImplemented e) {
798
                String msg = "Could not retrieve object to replicate (mn.getReplica NotImplemented): "+ e.getMessage();
799
                failure = new ServiceFailure("2151", msg);
800
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
801
                logMetacat.error(msg);
802
                throw new ServiceFailure("2151", msg);
803
            } catch (ServiceFailure e) {
804
                String msg = "Could not retrieve object to replicate (ServiceFailure): "+ e.getMessage();
805
                failure = new ServiceFailure("2151", msg);
806
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
807
                logMetacat.error(msg);
808
                throw new ServiceFailure("2151", msg);
809
            } catch (InsufficientResources e) {
810
                String msg = "Could not retrieve object to replicate (InsufficientResources): "+ e.getMessage();
811
                failure = new ServiceFailure("2151", msg);
812
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
813
                logMetacat.error(msg);
814
                throw new ServiceFailure("2151", msg);
815
            }
816

    
817
            // verify checksum on the object, if supported
818
            if (object.markSupported()) {
819
                Checksum givenChecksum = sysmeta.getChecksum();
820
                Checksum computedChecksum = null;
821
                try {
822
                    computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
823
                    object.reset();
824

    
825
                } catch (Exception e) {
826
                    String msg = "Error computing checksum on replica: " + e.getMessage();
827
                    logMetacat.error(msg);
828
                    ServiceFailure sf = new ServiceFailure("2151", msg);
829
                    sf.initCause(e);
830
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, sf);
831
                    throw sf;
832
                }
833
                if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
834
                    logMetacat.error("Given    checksum for " + pid.getValue() + 
835
                        "is " + givenChecksum.getValue());
836
                    logMetacat.error("Computed checksum for " + pid.getValue() + 
837
                        "is " + computedChecksum.getValue());
838
                    String msg = "Computed checksum does not match declared checksum";
839
                    failure = new ServiceFailure("2151", msg);
840
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
841
                    throw new ServiceFailure("2151", msg);
842
                }
843
            }
844

    
845
            // add it to local store
846
            Identifier retPid;
847
            try {
848
                // skip the MN.create -- this mutates the system metadata and we don't want it to
849
                if ( localId == null ) {
850
                    // TODO: this will fail if we already "know" about the identifier
851
                    // FIXME: see https://redmine.dataone.org/issues/2572
852
                    retPid = super.create(session, pid, object, sysmeta);
853
                    result = (retPid.getValue().equals(pid.getValue()));
854
                }
855
                
856
            } catch (Exception e) {
857
                String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
858
                failure = new ServiceFailure("2151", msg);
859
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
860
                logMetacat.error(msg);
861
                throw new ServiceFailure("2151", msg);
862
                
863
            }
864
        } finally {
865
            IOUtils.closeQuietly(object);
866
        }
867
        
868

    
869
        // finish by setting the replication status
870
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
871
        return result;
872

    
873
    }
874
    
875
    /*
876
     * If the given node supports v2 replication.
877
     */
878
    private boolean supportV2Replication(Node node) throws InvalidRequest {
879
        return supportVersionReplication(node, "v2");
880
    }
881
    
882
    /*
883
     * If the given node support the the given version replication. Return true if it does.
884
     */
885
    private boolean supportVersionReplication(Node node, String version) throws InvalidRequest{
886
        boolean support = false;
887
        if(node == null) {
888
            throw new InvalidRequest("2153", "There is no capacity information about the node in the replicate.");
889
        } else {
890
            Services services = node.getServices();
891
            if(services == null) {
892
                throw new InvalidRequest("2153", "Can't get replica from a node which doesn't have the replicate service.");
893
            } else {
894
               List<Service> list = services.getServiceList();
895
               if(list == null) {
896
                   throw new InvalidRequest("2153", "Can't get replica from a node which doesn't have the replicate service.");
897
               } else {
898
                   for(Service service : list) {
899
                       if(service != null && service.getName() != null && service.getName().equals("MNReplication") && 
900
                               service.getVersion() != null && service.getVersion().equalsIgnoreCase(version) && service.getAvailable() == true ) {
901
                           support = true;
902
                           
903
                       }
904
                   }
905
               }
906
            }
907
        }
908
        return support;
909
    }
910

    
911
    /**
912
     * Return the object identified by the given object identifier
913
     * 
914
     * @param session - the Session object containing the credentials for the Subject
915
     * @param pid - the object identifier for the given object
916
     * 
917
     * @return inputStream - the input stream of the given object
918
     * 
919
     * @throws InvalidToken
920
     * @throws ServiceFailure
921
     * @throws NotAuthorized
922
     * @throws InvalidRequest
923
     * @throws NotImplemented
924
     */
925
    @Override
926
    public InputStream get(Session session, Identifier pid) 
927
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
928

    
929
        return super.get(session, pid);
930

    
931
    }
932

    
933
    /**
934
     * Returns a Checksum for the specified object using an accepted hashing algorithm
935
     * 
936
     * @param session - the Session object containing the credentials for the Subject
937
     * @param pid - the object identifier for the given object
938
     * @param algorithm -  the name of an algorithm that will be used to compute 
939
     *                     a checksum of the bytes of the object
940
     * 
941
     * @return checksum - the checksum of the given object
942
     * 
943
     * @throws InvalidToken
944
     * @throws ServiceFailure
945
     * @throws NotAuthorized
946
     * @throws NotFound
947
     * @throws InvalidRequest
948
     * @throws NotImplemented
949
     */
950
    @Override
951
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
952
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
953
        InvalidRequest, NotImplemented {
954

    
955
        Checksum checksum = null;
956
        String serviceFailure = "1410";
957
        String notFound = "1420";
958
        //Checkum only handles the pid, not sid
959
        checkV1SystemMetaPidExist(pid, serviceFailure, "The checksum for the object specified by "+pid.getValue()+" couldn't be returned ",  notFound, 
960
                "The object specified by "+pid.getValue()+" does not exist at this node.");
961
        InputStream inputStream = get(session, pid);
962

    
963
        try {
964
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
965

    
966
        } catch (NoSuchAlgorithmException e) {
967
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
968
                    + e.getMessage());
969
        } catch (IOException e) {
970
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
971
                    + e.getMessage());
972
        }
973

    
974
        if (checksum == null) {
975
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
976
        }
977

    
978
        return checksum;
979
    }
980

    
981
    /**
982
     * Return the system metadata for a given object
983
     * 
984
     * @param session - the Session object containing the credentials for the Subject
985
     * @param pid - the object identifier for the given object
986
     * 
987
     * @return inputStream - the input stream of the given system metadata object
988
     * 
989
     * @throws InvalidToken
990
     * @throws ServiceFailure
991
     * @throws NotAuthorized
992
     * @throws NotFound
993
     * @throws InvalidRequest
994
     * @throws NotImplemented
995
     */
996
    @Override
997
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) 
998
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
999
        NotImplemented {
1000

    
1001
        return super.getSystemMetadata(session, pid);
1002
    }
1003

    
1004
    /**
1005
     * Retrieve the list of objects present on the MN that match the calling parameters
1006
     * 
1007
     * @param session - the Session object containing the credentials for the Subject
1008
     * @param startTime - Specifies the beginning of the time range from which 
1009
     *                    to return object (>=)
1010
     * @param endTime - Specifies the beginning of the time range from which 
1011
     *                  to return object (>=)
1012
     * @param objectFormat - Restrict results to the specified object format
1013
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
1014
     * @param start - The zero-based index of the first value, relative to the 
1015
     *                first record of the resultset that matches the parameters.
1016
     * @param count - The maximum number of entries that should be returned in 
1017
     *                the response. The Member Node may return less entries 
1018
     *                than specified in this value.
1019
     * 
1020
     * @return objectList - the list of objects matching the criteria
1021
     * 
1022
     * @throws InvalidToken
1023
     * @throws ServiceFailure
1024
     * @throws NotAuthorized
1025
     * @throws InvalidRequest
1026
     * @throws NotImplemented
1027
     */
1028
    @Override
1029
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Identifier identifier, Boolean replicaStatus, Integer start,
1030
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
1031
        NodeReference nodeId = null;
1032
        if(!replicaStatus) {
1033
            //not include those objects whose authoritative node is not this mn
1034
            nodeId = new NodeReference();
1035
            try {
1036
                String currentNodeId = PropertyService.getInstance().getProperty("dataone.nodeId"); // return only pids for which this mn
1037
                nodeId.setValue(currentNodeId);
1038
            } catch(Exception e) {
1039
                throw new ServiceFailure("1580", e.getMessage());
1040
            }
1041
        }
1042
        return super.listObjects(session, startTime, endTime, objectFormatId, identifier, nodeId, start, count);
1043
    }
1044

    
1045
    /**
1046
     * Return a description of the node's capabilities and services.
1047
     * 
1048
     * @return node - the technical capabilities of the Member Node
1049
     * 
1050
     * @throws ServiceFailure
1051
     * @throws NotAuthorized
1052
     * @throws InvalidRequest
1053
     * @throws NotImplemented
1054
     */
1055
    @Override
1056
    public Node getCapabilities() 
1057
        throws NotImplemented, ServiceFailure {
1058

    
1059
        String nodeName = null;
1060
        String nodeId = null;
1061
        String subject = null;
1062
        String contactSubject = null;
1063
        String nodeDesc = null;
1064
        String nodeTypeString = null;
1065
        NodeType nodeType = null;
1066
        List<String> mnCoreServiceVersions = null;
1067
        List<String> mnReadServiceVersions = null;
1068
        List<String> mnAuthorizationServiceVersions = null;
1069
        List<String> mnStorageServiceVersions = null;
1070
        List<String> mnReplicationServiceVersions = null;
1071

    
1072
        boolean nodeSynchronize = false;
1073
        boolean nodeReplicate = false;
1074
        List<String> mnCoreServiceAvailables = null;
1075
        List<String> mnReadServiceAvailables = null;
1076
        List<String> mnAuthorizationServiceAvailables = null;
1077
        List<String> mnStorageServiceAvailables = null;
1078
        List<String> mnReplicationServiceAvailables = null;
1079

    
1080
        try {
1081
            // get the properties of the node based on configuration information
1082
            nodeName = Settings.getConfiguration().getString("dataone.nodeName");
1083
            nodeId = Settings.getConfiguration().getString("dataone.nodeId");
1084
            subject = Settings.getConfiguration().getString("dataone.subject");
1085
            contactSubject = Settings.getConfiguration().getString("dataone.contactSubject");
1086
            nodeDesc = Settings.getConfiguration().getString("dataone.nodeDescription");
1087
            nodeTypeString = Settings.getConfiguration().getString("dataone.nodeType");
1088
            nodeType = NodeType.convert(nodeTypeString);
1089
            nodeSynchronize = new Boolean(Settings.getConfiguration().getString("dataone.nodeSynchronize")).booleanValue();
1090
            nodeReplicate = new Boolean(Settings.getConfiguration().getString("dataone.nodeReplicate")).booleanValue();
1091

    
1092
            // Set the properties of the node based on configuration information and
1093
            // calls to current status methods
1094
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
1095
            Node node = new Node();
1096
            node.setBaseURL(serviceName + "/" + nodeTypeString);
1097
            node.setDescription(nodeDesc);
1098

    
1099
            // set the node's health information
1100
            node.setState(NodeState.UP);
1101
            
1102
            // set the ping response to the current value
1103
            Ping canPing = new Ping();
1104
            canPing.setSuccess(false);
1105
            try {
1106
            	Date pingDate = ping();
1107
                canPing.setSuccess(pingDate != null);
1108
            } catch (BaseException e) {
1109
                e.printStackTrace();
1110
                // guess it can't be pinged
1111
            }
1112
            
1113
            node.setPing(canPing);
1114

    
1115
            NodeReference identifier = new NodeReference();
1116
            identifier.setValue(nodeId);
1117
            node.setIdentifier(identifier);
1118
            Subject s = new Subject();
1119
            s.setValue(subject);
1120
            node.addSubject(s);
1121
            Subject contact = new Subject();
1122
            contact.setValue(contactSubject);
1123
            node.addContactSubject(contact);
1124
            node.setName(nodeName);
1125
            node.setReplicate(nodeReplicate);
1126
            node.setSynchronize(nodeSynchronize);
1127

    
1128
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
1129
            Services services = new Services();
1130

    
1131
            mnCoreServiceVersions = Settings.getConfiguration().getList("dataone.mnCore.serviceVersion");
1132
            mnCoreServiceAvailables = Settings.getConfiguration().getList("dataone.mnCore.serviceAvailable");
1133
            if(mnCoreServiceVersions != null && mnCoreServiceAvailables != null && mnCoreServiceVersions.size() == mnCoreServiceAvailables.size()) {
1134
                for(int i=0; i<mnCoreServiceVersions.size(); i++) {
1135
                    String version = mnCoreServiceVersions.get(i);
1136
                    boolean available = new Boolean(mnCoreServiceAvailables.get(i)).booleanValue();
1137
                    Service sMNCore = new Service();
1138
                    sMNCore.setName("MNCore");
1139
                    sMNCore.setVersion(version);
1140
                    sMNCore.setAvailable(available);
1141
                    services.addService(sMNCore);
1142
                }
1143
            }
1144
            
1145
            mnReadServiceVersions = Settings.getConfiguration().getList("dataone.mnRead.serviceVersion");
1146
            mnReadServiceAvailables = Settings.getConfiguration().getList("dataone.mnRead.serviceAvailable");
1147
            if(mnReadServiceVersions != null && mnReadServiceAvailables != null && mnReadServiceVersions.size()==mnReadServiceAvailables.size()) {
1148
                for(int i=0; i<mnReadServiceVersions.size(); i++) {
1149
                    String version = mnReadServiceVersions.get(i);
1150
                    boolean available = new Boolean(mnReadServiceAvailables.get(i)).booleanValue();
1151
                    Service sMNRead = new Service();
1152
                    sMNRead.setName("MNRead");
1153
                    sMNRead.setVersion(version);
1154
                    sMNRead.setAvailable(available);
1155
                    services.addService(sMNRead);
1156
                }
1157
            }
1158
           
1159
            mnAuthorizationServiceVersions = Settings.getConfiguration().getList("dataone.mnAuthorization.serviceVersion");
1160
            mnAuthorizationServiceAvailables = Settings.getConfiguration().getList("dataone.mnAuthorization.serviceAvailable");
1161
            if(mnAuthorizationServiceVersions != null && mnAuthorizationServiceAvailables != null && mnAuthorizationServiceVersions.size()==mnAuthorizationServiceAvailables.size()) {
1162
                for(int i=0; i<mnAuthorizationServiceVersions.size(); i++) {
1163
                    String version = mnAuthorizationServiceVersions.get(i);
1164
                    boolean available = new Boolean(mnAuthorizationServiceAvailables.get(i)).booleanValue();
1165
                    Service sMNAuthorization = new Service();
1166
                    sMNAuthorization.setName("MNAuthorization");
1167
                    sMNAuthorization.setVersion(version);
1168
                    sMNAuthorization.setAvailable(available);
1169
                    services.addService(sMNAuthorization);
1170
                }
1171
            }
1172
           
1173
            mnStorageServiceVersions = Settings.getConfiguration().getList("dataone.mnStorage.serviceVersion");
1174
            mnStorageServiceAvailables = Settings.getConfiguration().getList("dataone.mnStorage.serviceAvailable");
1175
            if(mnStorageServiceVersions != null && mnStorageServiceAvailables != null && mnStorageServiceVersions.size() == mnStorageServiceAvailables.size()) {
1176
                for(int i=0; i<mnStorageServiceVersions.size(); i++) {
1177
                    String version = mnStorageServiceVersions.get(i);
1178
                    boolean available = new Boolean(mnStorageServiceAvailables.get(i)).booleanValue();
1179
                    Service sMNStorage = new Service();
1180
                    sMNStorage.setName("MNStorage");
1181
                    sMNStorage.setVersion(version);
1182
                    sMNStorage.setAvailable(available);
1183
                    services.addService(sMNStorage);
1184
                }
1185
            }
1186
            
1187
            mnReplicationServiceVersions = Settings.getConfiguration().getList("dataone.mnReplication.serviceVersion");
1188
            mnReplicationServiceAvailables = Settings.getConfiguration().getList("dataone.mnReplication.serviceAvailable");
1189
            if(mnReplicationServiceVersions != null && mnReplicationServiceAvailables != null && mnReplicationServiceVersions.size() == mnReplicationServiceAvailables.size()) {
1190
                for (int i=0; i<mnReplicationServiceVersions.size(); i++) {
1191
                    String version = mnReplicationServiceVersions.get(i);
1192
                    boolean available = new Boolean(mnReplicationServiceAvailables.get(i)).booleanValue();
1193
                    Service sMNReplication = new Service();
1194
                    sMNReplication.setName("MNReplication");
1195
                    sMNReplication.setVersion(version);
1196
                    sMNReplication.setAvailable(available);
1197
                    services.addService(sMNReplication);
1198
                }
1199
            }
1200
            
1201
            node.setServices(services);
1202

    
1203
            // Set the schedule for synchronization
1204
            Synchronization synchronization = new Synchronization();
1205
            Schedule schedule = new Schedule();
1206
            Date now = new Date();
1207
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
1208
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
1209
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
1210
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
1211
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
1212
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
1213
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
1214
            synchronization.setSchedule(schedule);
1215
            synchronization.setLastHarvested(now);
1216
            synchronization.setLastCompleteHarvest(now);
1217
            node.setSynchronization(synchronization);
1218

    
1219
            node.setType(nodeType);
1220
            return node;
1221

    
1222
        } catch (PropertyNotFoundException pnfe) {
1223
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
1224
            logMetacat.error(msg);
1225
            throw new ServiceFailure("2162", msg);
1226
        }
1227
    }
1228

    
1229
    
1230

    
1231
    /**
1232
     * A callback method used by a CN to indicate to a MN that it cannot 
1233
     * complete synchronization of the science metadata identified by pid.  Log
1234
     * the event in the metacat event log.
1235
     * 
1236
     * @param session
1237
     * @param syncFailed
1238
     * 
1239
     * @throws ServiceFailure
1240
     * @throws NotAuthorized
1241
     * @throws NotImplemented
1242
     */
1243
    @Override
1244
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
1245
        throws NotImplemented, ServiceFailure, NotAuthorized {
1246

    
1247
        String localId;
1248
        Identifier pid;
1249
        if ( syncFailed.getPid() != null ) {
1250
            pid = new Identifier();
1251
            pid.setValue(syncFailed.getPid());
1252
            boolean allowed;
1253
            
1254
            //are we allowed? only CNs
1255
            try {
1256
                allowed = isAdminAuthorized(session);
1257
                if ( !allowed ){
1258
                    throw new NotAuthorized("2162", 
1259
                            "Not allowed to call synchronizationFailed() on this node.");
1260
                }
1261
            } catch (InvalidToken e) {
1262
                throw new NotAuthorized("2162", 
1263
                        "Not allowed to call synchronizationFailed() on this node.");
1264

    
1265
            }
1266
            
1267
        } else {
1268
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1269

    
1270
        }
1271
        
1272
        try {
1273
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1274
        } catch (McdbDocNotFoundException e) {
1275
            throw new ServiceFailure("2161", "The identifier specified by " + 
1276
                    syncFailed.getPid() + " was not found on this node.");
1277

    
1278
        } catch (SQLException e) {
1279
            throw new ServiceFailure("2161", "Couldn't identify the local id of the identifier specified by " + 
1280
                    syncFailed.getPid() + " since "+e.getMessage());
1281
        }
1282
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1283
        // method is changed to include the URL as a parameter
1284
        logMetacat.warn("Synchronization for the object identified by " + 
1285
                pid.getValue() + " failed from " + syncFailed.getNodeId() + 
1286
                " with message: " + syncFailed.getDescription() + 
1287
                ". Logging the event to the Metacat EventLog as a 'syncFailed' event.");
1288
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1289
        String principal = Constants.SUBJECT_PUBLIC;
1290
        if (session != null && session.getSubject() != null) {
1291
          principal = session.getSubject().getValue();
1292
        }
1293
        try {
1294
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1295
        } catch (Exception e) {
1296
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1297
        }
1298
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
1299
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1300
        return true;
1301

    
1302
    }
1303

    
1304
    /**
1305
     * Essentially a get() but with different logging behavior
1306
     */
1307
    @Override
1308
    public InputStream getReplica(Session session, Identifier pid) 
1309
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken, NotFound {
1310

    
1311
        logMetacat.info("MNodeService.getReplica() called.");
1312

    
1313
        // cannot be called by public
1314
        if (session == null) {
1315
        	throw new InvalidToken("2183", "No session was provided.");
1316
        }
1317
        
1318
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1319
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1320
             "\tIdentifier           = " + pid.getValue());
1321

    
1322
        InputStream inputStream = null; // bytes to be returned
1323
        handler = new MetacatHandler(new Timer());
1324
        boolean allowed = false;
1325
        String localId; // the metacat docid for the pid
1326

    
1327
        // get the local docid from Metacat
1328
        try {
1329
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1330
        } catch (McdbDocNotFoundException e) {
1331
            throw new NotFound("2185", "The object specified by " + 
1332
                    pid.getValue() + " does not exist at this node.");
1333
            
1334
        } catch (SQLException e) {
1335
            throw new ServiceFailure("2181", "The local id of the object specified by " + 
1336
                    pid.getValue() + " couldn't be identified since "+e.getMessage());
1337
        }
1338

    
1339
        Subject targetNodeSubject = session.getSubject();
1340

    
1341
        // check for authorization to replicate, null session to act as this source MN
1342
        try {
1343
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1344
        } catch (InvalidToken e1) {
1345
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1346
                + e1.getMessage());
1347
            
1348
        } catch (NotFound e1) {
1349
            throw new NotFound("2185", "Could not find the object "+pid.getValue()+" in this node - " 
1350
                    + e1.getMessage());
1351

    
1352
        } catch (InvalidRequest e1) {
1353
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1354
                    + e1.getMessage());
1355

    
1356
        }
1357

    
1358
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1359
            " for identifier " + pid.getValue());
1360

    
1361
        // if the person is authorized, perform the read
1362
        if (allowed) {
1363
            try {
1364
                inputStream = MetacatHandler.read(localId);
1365
            } catch (Exception e) {
1366
                throw new ServiceFailure("2181", "The object specified by " + 
1367
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1368
            }
1369
        } else {
1370
            throw new NotAuthorized("2182", "The pid "+pid.getValue()+" is not authorized to be read by the client.");
1371
        }
1372

    
1373
        // if we fail to set the input stream
1374
        if (inputStream == null) {
1375
            throw new ServiceFailure("2181", "The object specified by " + 
1376
                pid.getValue() + " can't be returned from the node.");
1377
        }
1378

    
1379
        // log the replica event
1380
        String principal = null;
1381
        if (session.getSubject() != null) {
1382
            principal = session.getSubject().getValue();
1383
        }
1384
        EventLog.getInstance().log(request.getRemoteAddr(), 
1385
            request.getHeader("User-Agent"), principal, localId, "replicate");
1386

    
1387
        return inputStream;
1388
    }
1389
    
1390
    /**
1391
     * A method to notify the Member Node that the authoritative copy of 
1392
     * system metadata on the Coordinating Nodes has changed.
1393
     *
1394
     * @param session   Session information that contains the identity of the 
1395
     *                  calling user as retrieved from the X.509 certificate 
1396
     *                  which must be traceable to the CILogon service.
1397
     * @param serialVersion   The serialVersion of the system metadata
1398
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1399
     * @throws NotImplemented
1400
     * @throws ServiceFailure
1401
     * @throws NotAuthorized
1402
     * @throws InvalidRequest
1403
     * @throws InvalidToken
1404
     */
1405
    public boolean systemMetadataChanged(Session session, Identifier pid,
1406
        long serialVersion, Date dateSysMetaLastModified) 
1407
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1408
        InvalidToken {
1409
        boolean needCheckAuthoriativeNode = true; 
1410
        return systemMetadataChanged(needCheckAuthoriativeNode, session, pid,serialVersion, dateSysMetaLastModified);
1411
    }
1412

    
1413
    /**
1414
     * A method to notify the Member Node that the authoritative copy of 
1415
     * system metadata on the Coordinating Nodes has changed.
1416
     * @param needCheckAuthoriativeNode  this is for the dataone version 2. In the
1417
     * version 2, there are two scenarios:
1418
     * 1. If the node is the authoritative node, it only accepts serial version and replica list.
1419
     * 2. If the node is a replica, it accepts everything.
1420
     * For the v1, api, the parameter should be false. 
1421
     * @param session   Session information that contains the identity of the 
1422
     *                  calling user as retrieved from the X.509 certificate 
1423
     *                  which must be traceable to the CILogon service.
1424
     * @param serialVersion   The serialVersion of the system metadata
1425
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1426
     * @throws NotImplemented
1427
     * @throws ServiceFailure
1428
     * @throws NotAuthorized
1429
     * @throws InvalidRequest
1430
     * @throws InvalidToken
1431
     */
1432
    public boolean systemMetadataChanged(boolean needCheckAuthoriativeNode, Session session, Identifier pid,
1433
        long serialVersion, Date dateSysMetaLastModified) 
1434
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1435
        InvalidToken {
1436
        
1437
        /*if(isReadOnlyMode()) {
1438
            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.");
1439
        }*/
1440
        // cannot be called by public
1441
        if (session == null) {
1442
        	throw new InvalidToken("1332", "No session was provided.");
1443
        }
1444

    
1445
        String serviceFailureCode = "1333";
1446
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
1447
        if(sid != null) {
1448
            pid = sid;
1449
        }
1450
        
1451
        SystemMetadata currentLocalSysMeta = null;
1452
        SystemMetadata newSysMeta = null;
1453
        CNode cn = D1Client.getCN();
1454
        NodeList nodeList = null;
1455
        Subject callingSubject = null;
1456
        boolean allowed = false;
1457
        
1458
        // are we allowed to call this?
1459
        callingSubject = session.getSubject();
1460
        nodeList = cn.listNodes();
1461
        
1462
        for(Node node : nodeList.getNodeList()) {
1463
            // must be a CN
1464
            if ( node.getType().equals(NodeType.CN)) {
1465
               List<Subject> subjectList = node.getSubjectList();
1466
               // the calling subject must be in the subject list
1467
               if ( subjectList.contains(callingSubject)) {
1468
                   allowed = true;
1469
                   
1470
               }
1471
               
1472
            }
1473
        }
1474
        
1475
        if (!allowed ) {
1476
            String msg = "The subject identified by " + callingSubject.getValue() +
1477
              " is not authorized to call this service.";
1478
            throw new NotAuthorized("1331", msg);
1479
            
1480
        }
1481
        try {
1482
            HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
1483
        
1484
            // compare what we have locally to what is sent in the change notification
1485
            try {
1486
                currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1487
                 
1488
            } catch (RuntimeException e) {
1489
                String msg = "SystemMetadata for pid " + pid.getValue() +
1490
                  " couldn't be updated because it couldn't be found locally: " +
1491
                  e.getMessage();
1492
                logMetacat.error(msg);
1493
                ServiceFailure sf = new ServiceFailure("1333", msg);
1494
                sf.initCause(e);
1495
                throw sf; 
1496
            }
1497
            
1498
            if(currentLocalSysMeta == null) {
1499
                throw new InvalidRequest("1334", "We can't find the system metadata in the node for the id "+pid.getValue());
1500
            }
1501
            if (currentLocalSysMeta.getSerialVersion().longValue() <= serialVersion ) {
1502
                try {
1503
                    newSysMeta = cn.getSystemMetadata(null, pid);
1504
                } catch (NotFound e) {
1505
                    // huh? you just said you had it
1506
                	String msg = "On updating the local copy of system metadata " + 
1507
                    "for pid " + pid.getValue() +", the CN reports it is not found." +
1508
                    " The error message was: " + e.getMessage();
1509
                    logMetacat.error(msg);
1510
                    //ServiceFailure sf = new ServiceFailure("1333", msg);
1511
                    InvalidRequest sf = new InvalidRequest("1334", msg);
1512
                    sf.initCause(e);
1513
                    throw sf;
1514
                }
1515
                
1516
                //check about the sid in the system metadata
1517
                Identifier newSID = newSysMeta.getSeriesId();
1518
                if(newSID != null) {
1519
                    if (!isValidIdentifier(newSID)) {
1520
                        throw new InvalidRequest("1334", "The series identifier in the new system metadata is invalid.");
1521
                    }
1522
                    Identifier currentSID = currentLocalSysMeta.getSeriesId();
1523
                    if( currentSID != null && currentSID.getValue() != null) {
1524
                        if(!newSID.getValue().equals(currentSID.getValue())) {
1525
                            //newSID doesn't match the currentSID. The newSID shouldn't be used.
1526
                            try {
1527
                                if(IdentifierManager.getInstance().identifierExists(newSID.getValue())) {
1528
                                    throw new InvalidRequest("1334", "The series identifier "+newSID.getValue()+" in the new system metadata has been used by another object.");
1529
                                }
1530
                            } catch (SQLException sql) {
1531
                                throw new ServiceFailure("1333", "Couldn't determine if the SID "+newSID.getValue()+" in the system metadata exists in the node since "+sql.getMessage());
1532
                            }
1533
                            
1534
                        }
1535
                    } else {
1536
                        //newSID shouldn't be used
1537
                        try {
1538
                            if(IdentifierManager.getInstance().identifierExists(newSID.getValue())) {
1539
                                throw new InvalidRequest("1334", "The series identifier "+newSID.getValue()+" in the new system metadata has been used by another object.");
1540
                            }
1541
                        } catch (SQLException sql) {
1542
                            throw new ServiceFailure("1333", "Couldn't determine if the SID "+newSID.getValue()+" in the system metadata exists in the node since "+sql.getMessage());
1543
                        }
1544
                    }
1545
                }
1546
                // update the local copy of system metadata for the pid
1547
                try {
1548
                    if(needCheckAuthoriativeNode) {
1549
                        //this is for the v2 api.
1550
                        if(isAuthoritativeNode(pid)) {
1551
                            //this is the authoritative node, so we only accept replica and serial version
1552
                            logMetacat.debug("MNodeService.systemMetadataChanged - this is the authoritative node for the pid "+pid.getValue());
1553
                            List<Replica> replicas = newSysMeta.getReplicaList();
1554
                            newSysMeta = currentLocalSysMeta;
1555
                            newSysMeta.setSerialVersion(new BigInteger((new Long(serialVersion)).toString()));
1556
                            newSysMeta.setReplicaList(replicas);
1557
                        } else {
1558
                            //we need to archive the object in the replica node
1559
                            logMetacat.debug("MNodeService.systemMetadataChanged - this is NOT the authoritative node for the pid "+pid.getValue());
1560
                            logMetacat.debug("MNodeService.systemMetadataChanged - the new value of archive is "+newSysMeta.getArchived()+" for the pid "+pid.getValue());
1561
                            logMetacat.debug("MNodeService.systemMetadataChanged - the local value of archive is "+currentLocalSysMeta.getArchived()+" for the pid "+pid.getValue());
1562
                            if (newSysMeta.getArchived() != null && newSysMeta.getArchived() == true  && 
1563
                                    ((currentLocalSysMeta.getArchived() != null && currentLocalSysMeta.getArchived() == false ) || currentLocalSysMeta.getArchived() == null)){
1564
                                logMetacat.debug("MNodeService.systemMetadataChanged - start to archive object "+pid.getValue());
1565
                                boolean logArchive = false;
1566
                                boolean needUpdateModificationDate = false;
1567
                                try {
1568
                                    archiveObject(logArchive, session, pid, newSysMeta, needUpdateModificationDate);
1569
                                } catch (NotFound e) {
1570
                                    throw new InvalidRequest("1334", "Can't find the pid "+pid.getValue()+" for archive.");
1571
                                }
1572
                                
1573
                            } else if((newSysMeta.getArchived() == null || newSysMeta.getArchived() == false) && (currentLocalSysMeta.getArchived() != null && currentLocalSysMeta.getArchived() == true )) {
1574
                                throw new InvalidRequest("1334", "The pid "+pid.getValue()+" has been archived and it can't be reset to false.");
1575
                            }
1576
                        }
1577
                    }
1578
                    HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1579
                    logMetacat.info("Updated local copy of system metadata for pid " +
1580
                        pid.getValue() + " after change notification from the CN.");
1581
                    
1582
                    // TODO: consider inspecting the change for archive
1583
                    // see: https://projects.ecoinformatics.org/ecoinfo/issues/6417
1584
    //                if (newSysMeta.getArchived() != null && newSysMeta.getArchived().booleanValue()) {
1585
    //                	try {
1586
    //						this.archive(session, newSysMeta.getIdentifier());
1587
    //					} catch (NotFound e) {
1588
    //						// do we care? nothing to do about it now
1589
    //						logMetacat.error(e.getMessage(), e);
1590
    //					}
1591
    //                }
1592
                    
1593
                } catch (RuntimeException e) {
1594
                    String msg = "SystemMetadata for pid " + pid.getValue() +
1595
                      " couldn't be updated: " +
1596
                      e.getMessage();
1597
                    logMetacat.error(msg);
1598
                    ServiceFailure sf = new ServiceFailure("1333", msg);
1599
                    sf.initCause(e);
1600
                    throw sf;
1601
                }
1602
                
1603
                try {
1604
                    String localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1605
                    EventLog.getInstance().log(request.getRemoteAddr(), 
1606
                            request.getHeader("User-Agent"), session.getSubject().getValue(), 
1607
                            localId, "updateSystemMetadata");
1608
                } catch (Exception e) {
1609
                    // do nothing, no localId to log with
1610
                    logMetacat.warn("MNodeService.systemMetadataChanged - Could not log 'updateSystemMetadata' event because no localId was found for pid: " + pid.getValue());
1611
                } 
1612
                
1613
               
1614
            }
1615
        } finally {
1616
            HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
1617
        }
1618
        
1619
        if (currentLocalSysMeta.getSerialVersion().longValue() <= serialVersion ) {
1620
            // attempt to re-register the identifier (it checks if it is a doi)
1621
            try {
1622
                DOIService.getInstance().registerDOI(newSysMeta);
1623
            } catch (Exception e) {
1624
                logMetacat.warn("Could not [re]register DOI: " + e.getMessage(), e);
1625
            }
1626
            
1627
            // submit for indexing
1628
            try {
1629
                MetacatSolrIndex.getInstance().submit(newSysMeta.getIdentifier(), newSysMeta, null, true);
1630
            } catch (Exception e) {
1631
                logMetacat.error("Could not submit changed systemMetadata for indexing, pid: " + newSysMeta.getIdentifier().getValue(), e);
1632
            }
1633
        }
1634
        
1635
        return true;
1636
        
1637
    }
1638
    
1639
    /*
1640
     * Set the replication status for the object on the Coordinating Node
1641
     * 
1642
     * @param session - the session for the this target node
1643
     * @param pid - the identifier of the object being updated
1644
     * @param nodeId - the identifier of this target node
1645
     * @param status - the replication status to set
1646
     * @param failure - the exception to include, if any
1647
     */
1648
    private void setReplicationStatus(Session session, Identifier pid, 
1649
        NodeReference nodeId, ReplicationStatus status, BaseException failure) 
1650
        throws ServiceFailure, NotImplemented, NotAuthorized, 
1651
        InvalidRequest {
1652
        
1653
        // call the CN as the MN to set the replication status
1654
        try {
1655
            this.cn = D1Client.getCN();
1656
            this.cn.setReplicationStatus(session, pid, nodeId,
1657
                    status, failure);
1658
            
1659
        } catch (InvalidToken e) {
1660
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (InvalidToken): " + e.getMessage();
1661
            logMetacat.error(msg);
1662
        	throw new ServiceFailure("2151",
1663
                    msg);
1664
            
1665
        } catch (NotFound e) {
1666
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (NotFound): " + e.getMessage();
1667
            logMetacat.error(msg);
1668
        	throw new ServiceFailure("2151",
1669
                    msg);
1670
            
1671
        }
1672
    }
1673
    
1674
    private SystemMetadata makePublicIfNot(SystemMetadata sysmeta, Identifier pid) throws ServiceFailure, InvalidToken, NotFound, NotImplemented, InvalidRequest {
1675
    	// check if it is publicly readable
1676
		boolean isPublic = false;
1677
		Subject publicSubject = new Subject();
1678
		publicSubject.setValue(Constants.SUBJECT_PUBLIC);
1679
		Session publicSession = new Session();
1680
		publicSession.setSubject(publicSubject);
1681
		AccessRule publicRule = new AccessRule();
1682
		publicRule.addPermission(Permission.READ);
1683
		publicRule.addSubject(publicSubject);
1684
		
1685
		// see if we need to add the rule
1686
		try {
1687
			isPublic = this.isAuthorized(publicSession, pid, Permission.READ);
1688
		} catch (NotAuthorized na) {
1689
			// well, certainly not authorized for public read!
1690
		}
1691
		if (!isPublic) {
1692
			sysmeta.getAccessPolicy().addAllow(publicRule);
1693
		}
1694
		
1695
		return sysmeta;
1696
    }
1697

    
1698
	@Override
1699
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1700
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1701
			InvalidRequest {
1702
		
1703
		// check for null session
1704
        if (session == null) {
1705
          throw new InvalidToken("2190", "Session is required to generate an Identifier at this Node.");
1706
        }
1707
		
1708
		Identifier identifier = new Identifier();
1709
		
1710
		// handle different schemes
1711
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1712
			// UUID
1713
			UUID uuid = UUID.randomUUID();
1714
            identifier.setValue(UUID_PREFIX + uuid.toString());
1715
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1716
			// generate a DOI
1717
			try {
1718
				identifier = DOIService.getInstance().generateDOI();
1719
			} catch (EZIDException e) {
1720
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1721
				sf.initCause(e);
1722
				throw sf;
1723
			}
1724
		} else {
1725
			// default if we don't know the scheme
1726
			if (fragment != null) {
1727
				// for now, just autogen with fragment
1728
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1729
				identifier.setValue(autogenId);			
1730
			} else {
1731
				// autogen with no fragment
1732
				String autogenId = DocumentUtil.generateDocumentId(0);
1733
				identifier.setValue(autogenId);
1734
			}
1735
		}
1736
		
1737
		// TODO: reserve the identifier with the CN. We can only do this when
1738
		// 1) the MN is part of a CN cluster
1739
		// 2) the request is from an authenticated user
1740
		
1741
		return identifier;
1742
	}
1743

    
1744
	
1745

    
1746
	@Override
1747
	public QueryEngineDescription getQueryEngineDescription(Session session, String engine)
1748
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1749
			NotFound {
1750
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1751
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1752
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1753
            }
1754
	        QueryEngineDescription qed = new QueryEngineDescription();
1755
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1756
	        qed.setQueryEngineVersion("1.0");
1757
	        qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1758
	        Vector<String> pathsForIndexing = null;
1759
	        try {
1760
	            pathsForIndexing = SystemUtil.getPathsForIndexing();
1761
	        } catch (MetacatUtilException e) {
1762
	            logMetacat.warn("Could not get index paths", e);
1763
	        }
1764
	        for (String fieldName: pathsForIndexing) {
1765
	            QueryField field = new QueryField();
1766
	            field.addDescription("Indexed field for path '" + fieldName + "'");
1767
	            field.setName(fieldName);
1768
	            field.setReturnable(true);
1769
	            field.setSearchable(true);
1770
	            field.setSortable(false);
1771
	            // TODO: determine type and multivaluedness
1772
	            field.setType(String.class.getName());
1773
	            //field.setMultivalued(true);
1774
	            qed.addQueryField(field);
1775
	        }
1776
	        return qed;
1777
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1778
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1779
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1780
            }
1781
	        try {
1782
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1783
	            return qed;
1784
	        } catch (Exception e) {
1785
	            e.printStackTrace();
1786
	            throw new ServiceFailure("Solr server error", e.getMessage());
1787
	        }
1788
	    } else {
1789
	        throw new NotFound("404", "The Metacat member node can't find the query engine - "+engine);
1790
	    }
1791
		
1792
	}
1793

    
1794
	@Override
1795
	public QueryEngineList listQueryEngines(Session session) throws InvalidToken,
1796
			ServiceFailure, NotAuthorized, NotImplemented {
1797
		QueryEngineList qel = new QueryEngineList();
1798
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1799
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1800
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1801
		for(String name : enables) {
1802
		    qel.addQueryEngine(name);
1803
		}
1804
		return qel;
1805
	}
1806

    
1807
	@Override
1808
	public InputStream query(Session session, String engine, String query) throws InvalidToken,
1809
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1810
			NotFound {
1811
	    String user = Constants.SUBJECT_PUBLIC;
1812
        String[] groups= null;
1813
        Set<Subject> subjects = null;
1814
        if (session != null) {
1815
            user = session.getSubject().getValue();
1816
            subjects = AuthUtils.authorizedClientSubjects(session);
1817
            if (subjects != null) {
1818
                List<String> groupList = new ArrayList<String>();
1819
                for (Subject subject: subjects) {
1820
                    groupList.add(subject.getValue());
1821
                }
1822
                groups = groupList.toArray(new String[0]);
1823
            }
1824
        } else {
1825
            //add the public user subject to the set 
1826
            Subject subject = new Subject();
1827
            subject.setValue(Constants.SUBJECT_PUBLIC);
1828
            subjects = new HashSet<Subject>();
1829
            subjects.add(subject);
1830
        }
1831
        //System.out.println("====== user is "+user);
1832
        //System.out.println("====== groups are "+groups);
1833
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1834
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1835
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1836
            }
1837
			try {
1838
				DBQuery queryobj = new DBQuery();
1839
				
1840
				String results = queryobj.performPathquery(query, user, groups);
1841
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1842
				ctbais.setContentType("text/xml");
1843
				return ctbais;
1844

    
1845
			} catch (Exception e) {
1846
				throw new ServiceFailure("Pathquery error", e.getMessage());
1847
			}
1848
			
1849
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1850
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1851
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1852
		    }
1853
		    logMetacat.info("The query is ==================================== \n"+query);
1854
		    try {
1855
		        
1856
                return MetacatSolrIndex.getInstance().query(query, subjects);
1857
            } catch (Exception e) {
1858
                // TODO Auto-generated catch block
1859
                throw new ServiceFailure("Solr server error", e.getMessage());
1860
            } 
1861
		}
1862
		return null;
1863
	}
1864
	
1865
	/**
1866
	 * Given an existing Science Metadata PID, this method mints a DOI
1867
	 * and updates the original object "publishing" the update with the DOI.
1868
	 * This includes updating the ORE map that describes the Science Metadata+data.
1869
	 * TODO: ensure all referenced objects allow public read
1870
	 * 
1871
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1872
	 * 
1873
	 * @param originalIdentifier
1874
	 * @param request
1875
	 * @throws InvalidRequest 
1876
	 * @throws NotImplemented 
1877
	 * @throws NotAuthorized 
1878
	 * @throws ServiceFailure 
1879
	 * @throws InvalidToken 
1880
	 * @throws NotFound
1881
	 * @throws InvalidSystemMetadata 
1882
	 * @throws InsufficientResources 
1883
	 * @throws UnsupportedType 
1884
	 * @throws IdentifierNotUnique 
1885
	 */
1886
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken, 
1887
	ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique, 
1888
	UnsupportedType, InsufficientResources, InvalidSystemMetadata, IOException {
1889
		
1890
	    String serviceFailureCode = "1030";
1891
	    Identifier sid = getPIDForSID(originalIdentifier, serviceFailureCode);
1892
	    if(sid != null) {
1893
	        originalIdentifier = sid;
1894
	    }
1895
		// get the original SM
1896
		SystemMetadata originalSystemMetadata = this.getSystemMetadata(session, originalIdentifier);
1897

    
1898
		// make copy of it using the marshaller to ensure DEEP copy
1899
		SystemMetadata sysmeta = null;
1900
		try {
1901
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
1902
			TypeMarshaller.marshalTypeToOutputStream(originalSystemMetadata, baos);
1903
			sysmeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1904
		} catch (Exception e) {
1905
			// report as service failure
1906
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1907
			sf.initCause(e);
1908
			throw sf;
1909
		}
1910

    
1911
		// mint a DOI for the new revision
1912
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1913
				
1914
		// set new metadata values
1915
		sysmeta.setIdentifier(newIdentifier);
1916
		sysmeta.setObsoletes(originalIdentifier);
1917
		sysmeta.setObsoletedBy(null);
1918
		
1919
		// ensure it is publicly readable
1920
		sysmeta = makePublicIfNot(sysmeta, originalIdentifier);
1921
		
1922
		//Get the bytes
1923
		InputStream inputStream = null;		
1924
		boolean isScienceMetadata = isScienceMetadata(sysmeta);
1925
		//If it's a science metadata doc, we want to update the packageId first
1926
		if(isScienceMetadata){
1927
			InputStream originalObject = this.get(session, originalIdentifier);
1928
			
1929
			//Edit the science metadata with the new package Id (EML)
1930
			inputStream = editScienceMetadata(session, originalObject, originalIdentifier, newIdentifier);
1931
		}
1932
		else{
1933
			inputStream = this.get(session, originalIdentifier);
1934
		}
1935
		
1936
		// update the object
1937
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
1938
		
1939
		// update ORE that references the scimeta
1940
		// first try the naive method, then check the SOLR index
1941
		try {
1942
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1943
			
1944
			Identifier potentialOreIdentifier = new Identifier();
1945
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1946
			
1947
			InputStream oreInputStream = null;
1948
			try {
1949
				oreInputStream = this.get(session, potentialOreIdentifier);
1950
			} catch (NotFound nf) {
1951
				// this is probably okay for many sci meta data docs
1952
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1953
				// try the SOLR index
1954
				List<Identifier> potentialOreIdentifiers = this.lookupOreFor(originalIdentifier, false);
1955
				if (potentialOreIdentifiers != null) {
1956
					potentialOreIdentifier = potentialOreIdentifiers.get(0);
1957
					try {
1958
						oreInputStream = this.get(session, potentialOreIdentifier);
1959
					} catch (NotFound nf2) {
1960
						// this is probably okay for many sci meta data docs
1961
						logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1962
					}
1963
				}
1964
			}
1965
			if (oreInputStream != null) {
1966
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1967
	
1968
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1969
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1970
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1971
					
1972
				// reconstruct the ORE with the new identifiers
1973
				sciMetaMap.remove(originalIdentifier);
1974
				sciMetaMap.put(newIdentifier, dataIdentifiers);
1975
				
1976
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1977
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1978
				
1979
				// get the original ORE SM and update the values
1980
				SystemMetadata originalOreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1981
				SystemMetadata oreSysMeta = new SystemMetadata();
1982
				try {
1983
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
1984
					TypeMarshaller.marshalTypeToOutputStream(originalOreSysMeta, baos);
1985
					oreSysMeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1986
				} catch (Exception e) {
1987
					// report as service failure
1988
					ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1989
					sf.initCause(e);
1990
					throw sf;
1991
				}
1992

    
1993
				oreSysMeta.setIdentifier(newOreIdentifier);
1994
				oreSysMeta.setObsoletes(potentialOreIdentifier);
1995
				oreSysMeta.setObsoletedBy(null);
1996
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
1997
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
1998
				
1999
				// ensure ORE is publicly readable
2000
				oreSysMeta = makePublicIfNot(oreSysMeta, potentialOreIdentifier);
2001
				
2002
				// ensure all data objects allow public read
2003
				List<String> pidsToSync = new ArrayList<String>();
2004
				for (Identifier dataId: dataIdentifiers) {
2005
					SystemMetadata dataSysMeta = this.getSystemMetadata(session, dataId);
2006
					dataSysMeta = makePublicIfNot(dataSysMeta, dataId);
2007
					this.updateSystemMetadata(dataSysMeta);
2008
					pidsToSync.add(dataId.getValue());
2009
				}
2010
				SyncAccessPolicy sap = new SyncAccessPolicy();
2011
				try {
2012
					sap.sync(pidsToSync);
2013
				} catch (Exception e) {
2014
					// ignore
2015
					logMetacat.warn("Error attempting to sync access for data objects when publishing package");
2016
				}
2017
				
2018
				// save the updated ORE
2019
				this.update(
2020
						session, 
2021
						potentialOreIdentifier, 
2022
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")), 
2023
						newOreIdentifier, 
2024
						oreSysMeta);
2025
				
2026
			} else {
2027
				// create a new ORE for them
2028
				// https://projects.ecoinformatics.org/ecoinfo/issues/6194
2029
				try {
2030
					// find the local id for the NEW package.
2031
					String newLocalId = IdentifierManager.getInstance().getLocalId(newIdentifier.getValue());
2032
	
2033
					@SuppressWarnings("unused")
2034
					SystemMetadata extraSysMeta = SystemMetadataFactory.createSystemMetadata(newLocalId, true, false);
2035
					// should be done generating the ORE here, and the same permissions were used from the metadata object
2036
					
2037
				} catch (Exception e) {
2038
					// oops, guess there was a problem - no package for you
2039
					logMetacat.error("Could not generate new ORE for published object: " + newIdentifier.getValue(), e);
2040
				}
2041
			}
2042
		} catch (McdbDocNotFoundException e) {
2043
			// report as service failure
2044
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2045
			sf.initCause(e);
2046
			throw sf;
2047
		} catch (UnsupportedEncodingException e) {
2048
			// report as service failure
2049
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2050
			sf.initCause(e);
2051
			throw sf;
2052
		} catch (OREException e) {
2053
			// report as service failure
2054
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2055
			sf.initCause(e);
2056
			throw sf;
2057
		} catch (URISyntaxException e) {
2058
			// report as service failure
2059
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2060
			sf.initCause(e);
2061
			throw sf;
2062
		} catch (OREParserException e) {
2063
			// report as service failure
2064
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2065
			sf.initCause(e);
2066
			throw sf;
2067
		} catch (ORESerialiserException e) {
2068
			// report as service failure
2069
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2070
			sf.initCause(e);
2071
			throw sf;
2072
		} catch (NoSuchAlgorithmException e) {
2073
			// report as service failure
2074
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2075
			sf.initCause(e);
2076
			throw sf;
2077
		} catch (SQLException e) {
2078
            // report as service failure
2079
            ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2080
            sf.initCause(e);
2081
            throw sf;
2082
        }
2083
		
2084
		return newIdentifier;
2085
	}
2086
	
2087
	/**
2088
	   * Update a science metadata document with its new Identifier  
2089
	   * 
2090
	   * @param session - the Session object containing the credentials for the Subject
2091
	   * @param object - the InputStream for the XML object to be edited
2092
	   * @param pid - the Identifier of the XML object to be updated
2093
	   * @param newPid = the new Identifier to give to the modified XML doc
2094
	   * 
2095
	   * @return newObject - The InputStream for the modified XML object
2096
	   * 
2097
	   * @throws ServiceFailure
2098
	   * @throws IOException
2099
	   * @throws UnsupportedEncodingException
2100
	   * @throws InvalidToken
2101
	   * @throws NotAuthorized
2102
	   * @throws NotFound
2103
	   * @throws NotImplemented
2104
	   */
2105
	  public InputStream editScienceMetadata(Session session, InputStream object, Identifier pid, Identifier newPid)
2106
	  	throws ServiceFailure, IOException, UnsupportedEncodingException, InvalidToken, NotAuthorized, NotFound, NotImplemented {
2107
	    
2108
		logMetacat.debug("D1NodeService.editScienceMetadata() called.");
2109
		
2110
		 InputStream newObject = null;
2111
		
2112
	    try{   	
2113
	    	//Get the root node of the XML document
2114
	    	byte[] xmlBytes  = IOUtils.toByteArray(object);
2115
	        String xmlStr = new String(xmlBytes, "UTF-8");
2116
	        
2117
	    	Document doc = XMLUtilities.getXMLReaderAsDOMDocument(new StringReader(xmlStr));
2118
		    org.w3c.dom.Node docNode = doc.getDocumentElement();
2119

    
2120
		    //Get the system metadata for this object
2121
		    SystemMetadata sysMeta = null;
2122
		    try{
2123
		    	sysMeta = getSystemMetadata(session, pid);
2124
		    } catch(NotAuthorized e){
2125
		    	throw new ServiceFailure("1030", "D1NodeService.editScienceMetadata(): " + 
2126
		    			"This session is not authorized to access the system metadata for " +
2127
		    			pid.getValue() + " : " + e.getMessage());
2128
		    } catch(NotFound e){
2129
		    	throw new ServiceFailure("1030", "D1NodeService.editScienceMetadata(): " + 
2130
		    			"Could not find the system metadata for " +
2131
		    			pid.getValue() + " : " + e.getMessage());
2132
		    }
2133
		    
2134
		    //Get the formatId
2135
	        ObjectFormatIdentifier objFormatId = sysMeta.getFormatId();
2136
	        String formatId = objFormatId.getValue();
2137
	        
2138
	    	//For all EML formats
2139
	        if(formatId.indexOf("eml") == 0){
2140
	        	//Update or add the id attribute
2141
	    	    XMLUtilities.addAttributeNodeToDOMTree(docNode, XPATH_EML_ID, newPid.getValue());
2142
	        }
2143

    
2144
	        //The modified object InputStream
2145
		    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
2146
		    Source xmlSource = new DOMSource(docNode);
2147
		    Result outputTarget = new StreamResult(outputStream);
2148
		    TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
2149
		    newObject = new ByteArrayInputStream(outputStream.toByteArray());
2150
		    
2151
	    } catch(TransformerException e) {
2152
	    	throw new ServiceFailure("1030", "MNNodeService.editScienceMetadata(): " +
2153
	                "Could not update the ID in the XML document for " +
2154
	                "pid " + pid.getValue() +" : " + e.getMessage());
2155
	    } catch(IOException e){
2156
	    	throw new ServiceFailure("1030", "MNNodeService.editScienceMetadata(): " +
2157
	                "Could not update the ID in the XML document for " +
2158
	                "pid " + pid.getValue() +" : " + e.getMessage());
2159
	    }
2160
	    
2161
	    return newObject;
2162
	  }
2163
	
2164
	/**
2165
	 * Determines if we already have registered an ORE map for this package
2166
	 * NOTE: uses a solr query to locate OREs for the object
2167
	 * @param guid of the EML/packaging object
2168
	 * @return list of resource map identifiers for the given pid
2169
	 */
2170
	public List<Identifier> lookupOreFor(Identifier guid, boolean includeObsolete) {
2171
		// Search for the ORE if we can find it
2172
		String pid = guid.getValue();
2173
		List<Identifier> retList = null;
2174
		try {
2175
			String query = "fl=id,resourceMap&wt=xml&q=-obsoletedBy:[* TO *]+resourceMap:[* TO *]+id:\"" + pid + "\"";
2176
			if (includeObsolete) {
2177
				query = "fl=id,resourceMap&wt=xml&q=resourceMap:[* TO *]+id:\"" + pid + "\"";
2178
			}
2179
			
2180
			InputStream results = this.query(null, "solr", query);
2181
			org.w3c.dom.Node rootNode = XMLUtilities.getXMLReaderAsDOMTreeRootNode(new InputStreamReader(results, "UTF-8"));
2182
			//String resultString = XMLUtilities.getDOMTreeAsString(rootNode);
2183
			org.w3c.dom.NodeList nodeList = XMLUtilities.getNodeListWithXPath(rootNode, "//arr[@name=\"resourceMap\"]/str");
2184
			if (nodeList != null && nodeList.getLength() > 0) {
2185
				retList = new ArrayList<Identifier>();
2186
				for (int i = 0; i < nodeList.getLength(); i++) {
2187
					String found = nodeList.item(i).getFirstChild().getNodeValue();
2188
					Identifier oreId = new Identifier();
2189
					oreId.setValue(found);
2190
					retList.add(oreId);
2191
				}
2192
			}
2193
		} catch (Exception e) {
2194
			logMetacat.error("Error checking for resourceMap[s] on pid " + pid + ". " + e.getMessage(), e);
2195
		}
2196
		
2197
		return retList;
2198
	}
2199
	
2200

    
2201
	@Override
2202
	public InputStream getPackage(Session session, ObjectFormatIdentifier formatId,
2203
			Identifier pid) throws InvalidToken, ServiceFailure,
2204
			NotAuthorized, InvalidRequest, NotImplemented, NotFound {
2205
	    if(formatId == null) {
2206
	        throw new InvalidRequest("2873", "The format type can't be null in the getpackage method.");
2207
	    } else if(!formatId.getValue().equals("application/bagit-097")) {
2208
	        throw new NotImplemented("", "The format "+formatId.getValue()+" is not supported in the getpackage method");
2209
	    }
2210
	    String serviceFailureCode = "2871";
2211
	    Identifier sid = getPIDForSID(pid, serviceFailureCode);
2212
	    if(sid != null) {
2213
	        pid = sid;
2214
	    }
2215
		InputStream bagInputStream = null;
2216
		BagFactory bagFactory = new BagFactory();
2217
		Bag bag = bagFactory.createBag();
2218
		
2219
		// track the temp files we use so we can delete them when finished
2220
		List<File> tempFiles = new ArrayList<File>();
2221
		
2222
		// the pids to include in the package
2223
		List<Identifier> packagePids = new ArrayList<Identifier>();
2224
		
2225
		// catch non-D1 service errors and throw as ServiceFailures
2226
		try {
2227
			//Create a map of dataone ids and file names
2228
			Map<Identifier, String> fileNames = new HashMap<Identifier, String>();
2229
			
2230
			// track the pid-to-file mapping
2231
			StringBuffer pidMapping = new StringBuffer();
2232
			
2233
			// find the package contents
2234
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2235
			if (ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
2236
				//Get the resource map as a map of Identifiers
2237
				InputStream oreInputStream = this.get(session, pid);
2238
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
2239
				packagePids.addAll(resourceMapStructure.keySet());
2240
				//Loop through each object in this resource map
2241
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
2242
					//Loop through each metadata object in this entry
2243
					Set<Identifier> metadataIdentifiers = entries.keySet();
2244
					for(Identifier metadataID: metadataIdentifiers){
2245
						try{
2246
							//Get the system metadata for this metadata object
2247
							SystemMetadata metadataSysMeta = this.getSystemMetadata(session, metadataID);
2248
							
2249
							// include user-friendly metadata
2250
							if (ObjectFormatCache.getInstance().getFormat(metadataSysMeta.getFormatId()).getFormatType().equals("METADATA")) {
2251
								InputStream metadataStream = this.get(session, metadataID);
2252
							
2253
								try {
2254
									// transform
2255
						            String format = "default";
2256

    
2257
									DBTransform transformer = new DBTransform();
2258
						            String documentContent = IOUtils.toString(metadataStream, "UTF-8");
2259
						            String sourceType = metadataSysMeta.getFormatId().getValue();
2260
						            String targetType = "-//W3C//HTML//EN";
2261
						            ByteArrayOutputStream baos = new ByteArrayOutputStream();
2262
						            Writer writer = new OutputStreamWriter(baos , "UTF-8");
2263
						            // TODO: include more params?
2264
						            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2265
						            String localId = null;
2266
									try {
2267
										localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2268
									} catch (McdbDocNotFoundException e) {
2269
										throw new NotFound("1020", e.getMessage());
2270
									}
2271
									params.put("qformat", new String[] {format});	            
2272
						            params.put("docid", new String[] {localId});
2273
						            params.put("pid", new String[] {pid.getValue()});
2274
						            params.put("displaymodule", new String[] {"printall"});
2275
						            
2276
						            transformer.transformXMLDocument(
2277
						                    documentContent , 
2278
						                    sourceType, 
2279
						                    targetType , 
2280
						                    format, 
2281
						                    writer, 
2282
						                    params, 
2283
						                    null //sessionid
2284
						                    );
2285
						            
2286
						            // finally, get the HTML back
2287
						            ContentTypeByteArrayInputStream resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2288
						            
2289
						            // write to temp file with correct css path
2290
						            File tmpDir = File.createTempFile("package_", "_dir");
2291
						            tmpDir.delete();
2292
						            tmpDir.mkdir();
2293
						            File htmlFile = File.createTempFile("metadata", ".html", tmpDir);
2294
						            File cssDir = new File(tmpDir, format);
2295
						            cssDir.mkdir();
2296
						            File cssFile = new File(tmpDir, format + "/" + format + ".css");
2297
						            String pdfFileName = metadataID.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-METADATA.pdf";
2298
						            File pdfFile = new File(tmpDir, pdfFileName);
2299
						            //File pdfFile = File.createTempFile("metadata", ".pdf", tmpDir);
2300
						            
2301
						            // put the CSS file in place for the html to find it
2302
						            String originalCssPath = SystemUtil.getContextDir() + "/style/skins/" + format + "/" + format + ".css";
2303
						            IOUtils.copy(new FileInputStream(originalCssPath), new FileOutputStream(cssFile));
2304
						            
2305
						            // write the HTML file
2306
						            IOUtils.copy(resultInputStream, new FileOutputStream(htmlFile));
2307
						            
2308
						            // convert to PDF
2309
						            HtmlToPdf.export(htmlFile.getAbsolutePath(), pdfFile.getAbsolutePath());
2310
						            
2311
						            //add to the package
2312
						            bag.addFileToPayload(pdfFile);
2313
									pidMapping.append(metadataID.getValue() + " (pdf)" +  "\t" + "data/" + pdfFile.getName() + "\n");
2314
						            
2315
						            // mark for clean up after we are done
2316
									htmlFile.delete();
2317
									cssFile.delete();
2318
									cssDir.delete();
2319
						            tempFiles.add(tmpDir);
2320
									tempFiles.add(pdfFile); // delete this first later on
2321
						            
2322
								} catch (Exception e) {
2323
									logMetacat.warn("Could not transform metadata", e);
2324
								}
2325
							}
2326

    
2327
							
2328
							//If this is in eml format, extract the filename and GUID from each entity in its package
2329
							if (metadataSysMeta.getFormatId().getValue().startsWith("eml://")) {
2330
								//Get the package
2331
								DataPackageParserInterface parser = new Eml200DataPackageParser();
2332
								InputStream emlStream = this.get(session, metadataID);
2333
								parser.parse(emlStream);
2334
								DataPackage dataPackage = parser.getDataPackage();
2335
								
2336
								//Get all the entities in this package and loop through each to extract its ID and file name
2337
								Entity[] entities = dataPackage.getEntityList();
2338
								for(Entity entity: entities){
2339
									try{
2340
										//Get the file name from the metadata
2341
										String fileNameFromMetadata = entity.getName();
2342
										
2343
										//Get the ecogrid URL from the metadata
2344
										String ecogridIdentifier = entity.getEntityIdentifier();
2345
										//Parse the ecogrid URL to get the local id
2346
										String idFromMetadata = DocumentUtil.getAccessionNumberFromEcogridIdentifier(ecogridIdentifier);
2347
										
2348
										//Get the docid and rev pair
2349
										String docid = DocumentUtil.getDocIdFromString(idFromMetadata);
2350
										String rev = DocumentUtil.getRevisionStringFromString(idFromMetadata);
2351
										
2352
										//Get the GUID
2353
										String guid = IdentifierManager.getInstance().getGUID(docid, Integer.valueOf(rev));
2354
										Identifier dataIdentifier = new Identifier();
2355
										dataIdentifier.setValue(guid);
2356
										
2357
										//Add the GUID to our GUID & file name map
2358
										fileNames.put(dataIdentifier, fileNameFromMetadata);
2359
									}
2360
									catch(Exception e){
2361
										//Prevent just one entity error
2362
										e.printStackTrace();
2363
										logMetacat.debug(e.getMessage(), e);
2364
									}
2365
								}
2366
							}
2367
						}
2368
						catch(Exception e){
2369
							//Catch errors that would prevent package download
2370
							logMetacat.debug(e.toString());
2371
						}
2372
					}
2373
					packagePids.addAll(entries.keySet());
2374
					for (List<Identifier> dataPids: entries.values()) {
2375
						packagePids.addAll(dataPids);
2376
					}
2377
				}
2378
			} else {
2379
				// just the lone pid in this package
2380
				//packagePids.add(pid);
2381
			    //throw an invalid request exception
2382
			    throw new InvalidRequest("2873", "The given pid "+pid.getValue()+" is not a package id (resource map id). Please use a package id instead.");
2383
			}
2384
			
2385
			//Create a temp file, then delete it and make a directory with that name
2386
			File tempDir = File.createTempFile("temp", Long.toString(System.nanoTime()));
2387
			tempDir.delete();
2388
			tempDir = new File(tempDir.getPath() + "_dir");
2389
			tempDir.mkdir();			
2390
			tempFiles.add(tempDir);
2391
			File pidMappingFile = new File(tempDir, "pid-mapping.txt");
2392
			
2393
			// loop through the package contents
2394
			for (Identifier entryPid: packagePids) {
2395
				//Get the system metadata for each item
2396
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);					
2397
				
2398
				String objectFormatType = ObjectFormatCache.getInstance().getFormat(entrySysMeta.getFormatId()).getFormatType();
2399
				String fileName = null;
2400
				
2401
				//TODO: Be more specific of what characters to replace. Make sure periods arent replaced for the filename from metadata
2402
				//Our default file name is just the ID + format type (e.g. walker.1.1-DATA)
2403
				fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + objectFormatType;
2404

    
2405
				if (fileNames.containsKey(entryPid)){
2406
					//Let's use the file name and extension from the metadata is we have it
2407
					fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + fileNames.get(entryPid).replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
2408
				}
2409
				
2410
				// ensure there is a file extension for the object
2411
				String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
2412
				fileName += extension;
2413
				
2414
				// if SM has the file name, ignore everything else and use that
2415
				if (entrySysMeta.getFileName() != null) {
2416
					fileName = entrySysMeta.getFileName().replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
2417
				}
2418
				
2419
		        //Create a new file for this item and add to the list
2420
				File tempFile = new File(tempDir, fileName);
2421
				tempFiles.add(tempFile);
2422
				
2423
				InputStream entryInputStream = this.get(session, entryPid);			
2424
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
2425
				bag.addFileToPayload(tempFile);
2426
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
2427
			}
2428
			
2429
			//add the the pid to data file map
2430
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
2431
			bag.addFileAsTag(pidMappingFile);
2432
			tempFiles.add(pidMappingFile);
2433
			
2434
			bag = bag.makeComplete();
2435
			
2436
			///Now create the zip file
2437
			//Use the pid as the file name prefix, replacing all non-word characters
2438
			String zipName = pid.getValue().replaceAll("\\W", "_");
2439
			
2440
			File bagFile = new File(tempDir, zipName+".zip");
2441
			
2442
			bag.setFile(bagFile);
2443
			ZipWriter zipWriter = new ZipWriter(bagFactory);
2444
			bag.write(zipWriter, bagFile);
2445
			bagFile = bag.getFile();
2446
			// use custom FIS that will delete the file when closed
2447
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
2448
			// also mark for deletion on shutdown in case the stream is never closed
2449
			bagFile.deleteOnExit();
2450
			tempFiles.add(bagFile);
2451
			
2452
			// clean up other temp files
2453
			for (int i=tempFiles.size()-1; i>=0; i--){
2454
				tempFiles.get(i).delete();
2455
			}
2456
			
2457
		} catch (IOException e) {
2458
			// report as service failure
2459
		    e.printStackTrace();
2460
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2461
			sf.initCause(e);
2462
			throw sf;
2463
		} catch (OREException e) {
2464
			// report as service failure
2465
		    e.printStackTrace();
2466
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2467
			sf.initCause(e);
2468
			throw sf;
2469
		} catch (URISyntaxException e) {
2470
			// report as service failure
2471
		    e.printStackTrace();
2472
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2473
			sf.initCause(e);
2474
			throw sf;
2475
		} catch (OREParserException e) {
2476
			// report as service failure
2477
		    e.printStackTrace();
2478
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2479
			sf.initCause(e);
2480
			throw sf;
2481
		}
2482
		
2483
		return bagInputStream;
2484
	}
2485
	
2486
	 /**
2487
	   * Archives an object, where the object is either a 
2488
	   * data object or a science metadata object.
2489
	   * 
2490
	   * @param session - the Session object containing the credentials for the Subject
2491
	   * @param pid - The object identifier to be archived
2492
	   * 
2493
	   * @return pid - the identifier of the object used for the archiving
2494
	   * 
2495
	   * @throws InvalidToken
2496
	   * @throws ServiceFailure
2497
	   * @throws NotAuthorized
2498
	   * @throws NotFound
2499
	   * @throws NotImplemented
2500
	   * @throws InvalidRequest
2501
	   */
2502
	  public Identifier archive(Session session, Identifier pid) 
2503
	      throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
2504
	      if(isReadOnlyMode()) {
2505
	            throw new ServiceFailure("2912", ReadOnlyChecker.DATAONEERROR);
2506
	        }
2507
	      boolean allowed = false;
2508
	      // do we have a valid pid?
2509
	      if (pid == null || pid.getValue().trim().equals("")) {
2510
	          throw new ServiceFailure("1350", "The provided identifier was invalid.");
2511
	      }
2512
	      
2513
	      String serviceFailureCode = "1350";
2514
	      Identifier sid = getPIDForSID(pid, serviceFailureCode);
2515
	      if(sid != null) {
2516
	          pid = sid;
2517
	      }
2518
	      // does the subject have archive (a D1 CHANGE_PERMISSION level) privileges on the pid?
2519
	      try {
2520
	            allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
2521
	        } catch (InvalidRequest e) {
2522
	          throw new ServiceFailure("1350", e.getDescription());
2523
	        } 
2524

    
2525
	      if (allowed) {
2526
	         try {
2527
	             HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
2528
	             logMetacat.debug("MNodeService.archive - lock the identifier "+pid.getValue()+" in the system metadata map.");
2529
	             SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
2530
	             boolean needModifyDate = true;
2531
	             boolean logArchive = true;
2532
	             super.archiveObject(logArchive, session, pid, sysmeta, needModifyDate); 
2533
	         } finally {
2534
	             HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
2535
	             logMetacat.debug("MNodeService.archive - unlock the identifier "+pid.getValue()+" in the system metadata map.");
2536
	         }
2537
	        
2538

    
2539
	      } else {
2540
	          throw new NotAuthorized("1320", "The provided identity does not have " + "permission to archive the object on the Node.");
2541
	      }
2542

    
2543
	      return pid;
2544
	  }
2545
    
2546
	/**
2547
	 * Update the system metadata of the specified pid.
2548
	 */
2549
	@Override
2550
	public boolean updateSystemMetadata(Session session, Identifier pid,
2551
            SystemMetadata sysmeta) throws NotImplemented, NotAuthorized,
2552
            ServiceFailure, InvalidRequest, InvalidSystemMetadata, InvalidToken {
2553
	  
2554
	  if(isReadOnlyMode()) {
2555
            throw new ServiceFailure("4868",  ReadOnlyChecker.DATAONEERROR);
2556
      }
2557
	 if(sysmeta == null) {
2558
	     throw  new InvalidRequest("4869", "The system metadata object should NOT be null in the updateSystemMetadata request.");
2559
	 }
2560
	 if(pid == null || pid.getValue() == null) {
2561
         throw new InvalidRequest("4869", "Please specify the id in the updateSystemMetadata request ") ;
2562
     }
2563

    
2564
     if (session == null) {
2565
         //TODO: many of the thrown exceptions do not use the correct error codes
2566
         //check these against the docs and correct them
2567
         throw new NotAuthorized("4861", "No Session - could not authorize for updating system metadata." +
2568
                 "  If you are not logged in, please do so and retry the request.");
2569
     } else {
2570
         try {
2571
             //Following session can do the change:
2572
           //- Authoritative Member Node (we can use isNodeAdmin since we checked isAuthoritativeNode )
2573
             //- Owner of object (coved by the userHasPermission method)
2574
             //- user subjects with the change permission
2575
             //Note: Coordinating Node can not because MN is authoritative
2576
             /*if(!isAuthoritativeNode(pid)) {
2577
                throw  new InvalidRequest("4863", "Client can only call updateSystemMetadata request on the authoritative memember node.");
2578
             }
2579
             if(!isNodeAdmin(session) && !userHasPermission(session, pid, Permission.CHANGE_PERMISSION)) {
2580
                 throw new NotAuthorized("4861", "The client -"+ session.getSubject().getValue()+ "is not authorized for updating the system metadata of the object "+pid.getValue());
2581
             }*/
2582
             if(!allowUpdating(session, pid, Permission.CHANGE_PERMISSION)) {
2583
                 throw new NotAuthorized("4861", "The client -"+ session.getSubject().getValue()+ "is not authorized for updating the system metadata of the object "+pid.getValue());
2584
             }
2585
         } catch (NotFound e) {
2586
             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());
2587
         }
2588
         
2589
     }
2590
      //update the system metadata locally
2591
      boolean success = false;
2592
      try {
2593
          HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
2594
          SystemMetadata currentSysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
2595
          if(currentSysmeta == null) {
2596
              throw  new InvalidRequest("4869", "We can't find the current system metadata on the member node for the id "+pid.getValue());
2597
          }
2598
          Date currentModiDate = currentSysmeta.getDateSysMetadataModified();
2599
          Date commingModiDate = sysmeta.getDateSysMetadataModified();
2600
          if(commingModiDate == null) {
2601
              throw  new InvalidRequest("4869", "The system metadata modification date can't be null.");
2602
          }
2603
          if(currentModiDate != null && commingModiDate.getTime() != currentModiDate.getTime()) {
2604
              throw new InvalidRequest("4869", "Your system metadata modification date is "+commingModiDate.toString()+
2605
                      ". It doesn't match our current system metadata modification date in the member node - "+currentModiDate.toString()+
2606
                      ". Please check if you have got the newest version of the system metadata before the modification.");
2607
          }
2608
          boolean needUpdateModificationDate = true;
2609
          boolean fromCN = false;
2610
          success = updateSystemMetadata(session, pid, sysmeta, needUpdateModificationDate, currentSysmeta, fromCN);
2611
      } finally {
2612
          HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
2613
      }
2614
      
2615
      if(success) {
2616
          //TODO
2617
          //notify the cns the synchornize the new system metadata.
2618
          this.cn = D1Client.getCN();
2619
          try {
2620
              if(this.cn == null)  {
2621
                  logMetacat.warn("updateSystemMetadata - can't get the instance of the CN. So the system metadata in CN can't be updated.");
2622
              } else {
2623
                  this.cn.synchronize(null, pid);
2624
              }
2625
          } catch (BaseException e) {
2626
              e.printStackTrace();
2627
              logMetacat.error("It is a DataONEBaseException and its detail code is "+e.getDetail_code() +" and its code is "+e.getCode());
2628
              logMetacat.error("Can't update the systemmetadata of pid "+pid.getValue()+" in CNs since "+e.getMessage());
2629
          } catch (Exception e) {
2630
              e.printStackTrace();
2631
              logMetacat.error("Can't update the systemmetadata of pid "+pid.getValue()+" in CNs since "+e.getMessage());
2632
          }
2633
          
2634
          // attempt to re-register the identifier (it checks if it is a doi)
2635
          try {
2636
        	  DOIService.getInstance().registerDOI(sysmeta);
2637
          } catch (Exception e) {
2638
  			logMetacat.warn("Could not [re]register DOI: " + e.getMessage(), e);
2639
          }
2640
      }
2641
      return success;
2642
    }
2643
	
2644
	/*
2645
     * Determine if the current node is the authoritative node for the given pid.
2646
     */
2647
    protected boolean isAuthoritativeNode(Identifier pid) throws InvalidRequest {
2648
        boolean isAuthoritativeNode = false;
2649
        if(pid != null && pid.getValue() != null) {
2650
            SystemMetadata sys = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
2651
            if(sys != null) {
2652
                NodeReference node = sys.getAuthoritativeMemberNode();
2653
                if(node != null) {
2654
                    String nodeValue = node.getValue();
2655
                    logMetacat.debug("The authoritative node for id "+pid.getValue()+" is "+nodeValue);
2656
                    //System.out.println("The authoritative node for id "+pid.getValue()+" is "+nodeValue);
2657
                    String currentNodeId = Settings.getConfiguration().getString("dataone.nodeId");
2658
                    logMetacat.debug("The node id in metacat.properties is "+currentNodeId);
2659
                    //System.out.println("The node id in metacat.properties is "+currentNodeId);
2660
                    if(currentNodeId != null && !currentNodeId.trim().equals("") && currentNodeId.equals(nodeValue)) {
2661
                        logMetacat.debug("They are matching");
2662
                        //System.out.println("They are matching");
2663
                        isAuthoritativeNode = true;
2664
                    }
2665
                } else {
2666
                    throw new InvalidRequest("4869", "Coudn't find the authoritative member node in the system metadata associated with the pid "+pid.getValue());
2667
                }
2668
            } else {
2669
                throw new InvalidRequest("4869", "Coudn't find the system metadata associated with the pid "+pid.getValue());
2670
            }
2671
        } else {
2672
            throw new InvalidRequest("4869", "The request pid is null");
2673
        }
2674
        return isAuthoritativeNode;
2675
    }
2676
    
2677
    /*
2678
     * Rules are:
2679
     * 1. If the session has an cn object, it is allowed.
2680
     * 2. If it is not a cn object, the client should have approperate permission and it should also happen on the authorative node.
2681
     */
2682
    private boolean allowUpdating(Session session, Identifier pid, Permission permission) throws NotAuthorized, NotFound, InvalidRequest {
2683
        boolean allow = false;
2684
        if(isCNAdmin (session)) {
2685
            allow = true;
2686
        } else {
2687
            if(isAuthoritativeNode(pid)) {
2688
                if(userHasPermission(session, pid, permission)) {
2689
                    allow = true;
2690
                } else {
2691
                    allow = false;
2692
                }
2693
            } else {
2694
                throw new NotAuthorized("4861", "Client can only call the request on the authoritative memember node.");
2695
            }
2696
        }
2697
        return allow;
2698
        
2699
    }
2700
    
2701
    /**
2702
     * Check if the metacat is in the read-only mode.
2703
     * @return true if it is; otherwise false.
2704
     */
2705
    protected boolean isReadOnlyMode() {
2706
        boolean readOnly = false;
2707
        ReadOnlyChecker checker = new ReadOnlyChecker();
2708
        readOnly = checker.isReadOnly();
2709
        return readOnly;
2710
    }
2711
    
2712
}
(5-5/8)