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
        try {
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
        } finally {
535
            IOUtils.closeQuietly(object);
536
        }
537
        return newPid;
538
    }
539

    
540
    public Identifier create(Session session, Identifier pid, InputStream object, SystemMetadata sysmeta) throws InvalidToken, ServiceFailure, NotAuthorized,
541
            IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, InvalidRequest {
542
        Identifier resultPid = null;
543
        try {
544
            if(isReadOnlyMode()) {
545
                throw new ServiceFailure("1190", ReadOnlyChecker.DATAONEERROR);
546
            }
547
            // check for null session
548
            if (session == null) {
549
              throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
550
            }
551
            // verify the pid is valid format
552
            if (!isValidIdentifier(pid)) {
553
                throw new InvalidRequest("1102", "The provided identifier is invalid.");
554
            }
555
            // set the submitter to match the certificate
556
            sysmeta.setSubmitter(session.getSubject());
557
            // set the originating node
558
            NodeReference originMemberNode = this.getCapabilities().getIdentifier();
559
            sysmeta.setOriginMemberNode(originMemberNode);
560
            
561
            // if no authoritative MN, set it to the same
562
            if (sysmeta.getAuthoritativeMemberNode() == null) {
563
            	sysmeta.setAuthoritativeMemberNode(originMemberNode);
564
            }
565
            
566
            sysmeta.setArchived(false);
567
    
568
            // set the dates
569
            Date now = Calendar.getInstance().getTime();
570
            sysmeta.setDateSysMetadataModified(now);
571
            sysmeta.setDateUploaded(now);
572
            
573
            // set the serial version
574
            sysmeta.setSerialVersion(BigInteger.ZERO);
575
    
576
            // check that we are not attempting to subvert versioning
577
            if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
578
                throw new InvalidSystemMetadata("1180", 
579
                  "The supplied system metadata is invalid. " +
580
                  "The obsoletes field cannot have a value when creating entries.");
581
            }
582
            
583
            if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
584
                throw new InvalidSystemMetadata("1180", 
585
                  "The supplied system metadata is invalid. " +
586
                  "The obsoletedBy field cannot have a value when creating entries.");
587
            }
588
            
589
            // verify the sid in the system metadata
590
            Identifier sid = sysmeta.getSeriesId();
591
            boolean idExists = false;
592
            if(sid != null) {
593
                if (!isValidIdentifier(sid)) {
594
                    throw new InvalidSystemMetadata("1180", "The provided series id is invalid.");
595
                }
596
                try {
597
                    idExists = IdentifierManager.getInstance().identifierExists(sid.getValue());
598
                } catch (SQLException e) {
599
                    throw new ServiceFailure("1190", 
600
                                            "The series identifier " + sid.getValue() +
601
                                            " in the system metadata couldn't be determined if it is unique since : "+e.getMessage());
602
                }
603
                if (idExists) {
604
                        throw new InvalidSystemMetadata("1180", 
605
                                  "The series identifier " + sid.getValue() +
606
                                  " is already used by another object and" +
607
                                  "therefore can not be used for this object. Clients should choose" +
608
                                  "a new identifier that is unique and retry the operation or " +
609
                                  "use CN.reserveIdentifier() to reserve one.");
610
                    
611
                }
612
                //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 )
613
                if(sid.getValue().equals(pid.getValue())) {
614
                    throw new InvalidSystemMetadata("1180", "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
615
                }
616
            }
617
    
618
            // call the shared impl
619
            resultPid = super.create(session, pid, object, sysmeta);
620
            
621
            // attempt to register the identifier - it checks if it is a doi
622
            try {
623
    			DOIService.getInstance().registerDOI(sysmeta);
624
    		} catch (Exception e) {
625
    			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
626
    			sf.initCause(e);
627
                throw sf;
628
    		}
629
        } finally {
630
            IOUtils.closeQuietly(object);
631
        }
632
        // return 
633
		return resultPid ;
634
    }
635

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

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

    
679
        // get the referenced object
680
        Identifier pid = sysmeta.getIdentifier();
681
        // verify the pid is valid format
682
        if (!isValidIdentifier(pid)) {
683
            throw new InvalidRequest("2153", "The provided identifier in the system metadata is invalid.");
684
        }
685

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

    
705

    
706
        // get the local node id
707
        try {
708
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
709
            nodeId = new NodeReference();
710
            nodeId.setValue(nodeIdStr);
711

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

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

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

    
780
                }
781

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

    
789
            } catch (NotFound e) {
790
                String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
791
                failure = new ServiceFailure("2151", msg);
792
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
793
                logMetacat.error(msg);
794
                throw new ServiceFailure("2151", msg);
795

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

    
822
            // verify checksum on the object, if supported
823
            if (object.markSupported()) {
824
                Checksum givenChecksum = sysmeta.getChecksum();
825
                Checksum computedChecksum = null;
826
                try {
827
                    computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
828
                    object.reset();
829

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

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

    
874
        // finish by setting the replication status
875
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
876
        return result;
877

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

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

    
934
        return super.get(session, pid);
935

    
936
    }
937

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

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

    
968
        try {
969
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
970

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

    
979
        if (checksum == null) {
980
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
981
        }
982

    
983
        return checksum;
984
    }
985

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

    
1006
        return super.getSystemMetadata(session, pid);
1007
    }
1008

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

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

    
1064
        String nodeName = null;
1065
        String nodeId = null;
1066
        String subject = null;
1067
        String contactSubject = null;
1068
        String nodeDesc = null;
1069
        String nodeTypeString = null;
1070
        NodeType nodeType = null;
1071
        List<String> mnCoreServiceVersions = null;
1072
        List<String> mnReadServiceVersions = null;
1073
        List<String> mnAuthorizationServiceVersions = null;
1074
        List<String> mnStorageServiceVersions = null;
1075
        List<String> mnReplicationServiceVersions = null;
1076

    
1077
        boolean nodeSynchronize = false;
1078
        boolean nodeReplicate = false;
1079
        List<String> mnCoreServiceAvailables = null;
1080
        List<String> mnReadServiceAvailables = null;
1081
        List<String> mnAuthorizationServiceAvailables = null;
1082
        List<String> mnStorageServiceAvailables = null;
1083
        List<String> mnReplicationServiceAvailables = null;
1084

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

    
1097
            // Set the properties of the node based on configuration information and
1098
            // calls to current status methods
1099
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
1100
            Node node = new Node();
1101
            node.setBaseURL(serviceName + "/" + nodeTypeString);
1102
            node.setDescription(nodeDesc);
1103

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

    
1120
            NodeReference identifier = new NodeReference();
1121
            identifier.setValue(nodeId);
1122
            node.setIdentifier(identifier);
1123
            Subject s = new Subject();
1124
            s.setValue(subject);
1125
            node.addSubject(s);
1126
            Subject contact = new Subject();
1127
            contact.setValue(contactSubject);
1128
            node.addContactSubject(contact);
1129
            node.setName(nodeName);
1130
            node.setReplicate(nodeReplicate);
1131
            node.setSynchronize(nodeSynchronize);
1132

    
1133
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
1134
            Services services = new Services();
1135

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

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

    
1224
            node.setType(nodeType);
1225
            return node;
1226

    
1227
        } catch (PropertyNotFoundException pnfe) {
1228
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
1229
            logMetacat.error(msg);
1230
            throw new ServiceFailure("2162", msg);
1231
        }
1232
    }
1233

    
1234
    
1235

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

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

    
1270
            }
1271
            
1272
        } else {
1273
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1274

    
1275
        }
1276
        
1277
        try {
1278
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1279
        } catch (McdbDocNotFoundException e) {
1280
            throw new ServiceFailure("2161", "The identifier specified by " + 
1281
                    syncFailed.getPid() + " was not found on this node.");
1282

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

    
1307
    }
1308

    
1309
    /**
1310
     * Essentially a get() but with different logging behavior
1311
     */
1312
    @Override
1313
    public InputStream getReplica(Session session, Identifier pid) 
1314
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken, NotFound {
1315

    
1316
        logMetacat.info("MNodeService.getReplica() called.");
1317

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

    
1327
        InputStream inputStream = null; // bytes to be returned
1328
        handler = new MetacatHandler(new Timer());
1329
        boolean allowed = false;
1330
        String localId; // the metacat docid for the pid
1331

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

    
1344
        Subject targetNodeSubject = session.getSubject();
1345

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

    
1357
        } catch (InvalidRequest e1) {
1358
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1359
                    + e1.getMessage());
1360

    
1361
        }
1362

    
1363
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1364
            " for identifier " + pid.getValue());
1365

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

    
1378
        // if we fail to set the input stream
1379
        if (inputStream == null) {
1380
            throw new ServiceFailure("2181", "The object specified by " + 
1381
                pid.getValue() + " can't be returned from the node.");
1382
        }
1383

    
1384
        // log the replica event
1385
        String principal = null;
1386
        if (session.getSubject() != null) {
1387
            principal = session.getSubject().getValue();
1388
        }
1389
        EventLog.getInstance().log(request.getRemoteAddr(), 
1390
            request.getHeader("User-Agent"), principal, localId, "replicate");
1391

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

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

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

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

    
1749
	
1750

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2544
	      } else {
2545
	          throw new NotAuthorized("1320", "The provided identity does not have " + "permission to archive the object on the Node.");
2546
	      }
2547

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

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