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

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

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

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

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

    
216

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

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

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

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

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

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

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

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

    
395
        // does the subject have WRITE ( == update) priveleges on the pid?
396
        //allowed = isAuthorized(session, pid, Permission.WRITE);
397
        //CN having the permission is allowed; user with the write permission and calling on the authoritative node is allowed.
398
        try {
399
            allowed = allowUpdating(session, pid, Permission.WRITE);
400
        }   catch (NotFound e) {
401
            throw new NotFound("1280", "Can't determine if the client has the permission to update the object with id "+pid.getValue()+" since "+e.getDescription());
402
        } catch(ServiceFailure e) {
403
            throw new ServiceFailure("1310", "Can't determine if the client has the permission to update the object with id "+pid.getValue()+" since "+e.getDescription());
404
        } catch(NotAuthorized e) {
405
            throw new NotAuthorized("1200", "Can't determine if the client has the permission to update the object with id "+pid.getValue()+" since "+e.getDescription());
406
        } catch(NotImplemented e) {
407
            throw new NotImplemented("1201","Can't determine if the client has the permission to update he object with id "+pid.getValue()+" since "+e.getDescription());
408
        } catch(InvalidRequest e) {
409
            throw new InvalidRequest("1202", "Can't determine if the client has the permission to update the object with id "+pid.getValue()+" since "+e.getDescription());
410
        } catch(InvalidToken e) {
411
            throw new InvalidToken("1210", "Can't determine if the client has the permission to update the object with id "+pid.getValue()+" since "+e.getDescription());
412
        }
413
        
414
        if (allowed) {
415
        	
416
        	// check quality of SM
417
        	if (sysmeta.getObsoletedBy() != null) {
418
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
419
        	}
420
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
421
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
422
        	}
423

    
424
            // get the existing system metadata for the object
425
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
426
            //System.out.println("the archive is "+existingSysMeta.getArchived());
427
            //Base on documentation, we can't update an archived object:
428
            //The update operation MUST fail with Exceptions.InvalidRequest on objects that have the Types.SystemMetadata.archived property set to true.
429
            if(existingSysMeta.getArchived() != null && existingSysMeta.getArchived()) {
430
                throw new InvalidRequest("1202","An archived object"+pid.getValue()+" can't be updated");
431
            }
432

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

    
482
            isScienceMetadata = isScienceMetadata(sysmeta);
483

    
484
            // do we have XML metadata or a data object?
485
            if (isScienceMetadata) {
486

    
487
                // update the science metadata XML document
488
                // TODO: handle non-XML metadata/data documents (like netCDF)
489
                // TODO: don't put objects into memory using stream to string
490
                //String objectAsXML = "";
491
                try {
492
                    //objectAsXML = IOUtils.toString(object, "UTF-8");
493
                	String formatId = null;
494
                	if(sysmeta.getFormatId() != null) {
495
                	    formatId = sysmeta.getFormatId().getValue();
496
                	}
497
                    localId = insertOrUpdateDocument(object, "UTF-8", pid, session, "update", formatId);
498
                    
499
                    // register the newPid and the generated localId
500
                    if (newPid != null) {
501
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
502
                    }
503

    
504
                } catch (IOException e) {
505
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
506
                    logMetacat.info(msg);
507
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
508

    
509
                }
510

    
511
            } else {
512

    
513
                // update the data object
514
                localId = insertDataObject(object, newPid, session);
515

    
516
            }
517
            
518
            // add the newPid to the obsoletedBy list for the existing sysmeta
519
            existingSysMeta.setObsoletedBy(newPid);
520
            //increase version
521
            BigInteger current = existingSysMeta.getSerialVersion();
522
            //System.out.println("the current version is "+current);
523
            current = current.add(BigInteger.ONE);
524
            //System.out.println("the new current version is "+current);
525
            existingSysMeta.setSerialVersion(current);
526
            // then update the existing system metadata
527
            updateSystemMetadata(existingSysMeta);
528

    
529
            // prep the new system metadata, add pid to the affected lists
530
            sysmeta.setObsoletes(pid);
531
            //sysmeta.addDerivedFrom(pid);
532

    
533
            // and insert the new system metadata
534
            insertSystemMetadata(sysmeta);
535

    
536
            // log the update event
537
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
538
            
539
            // attempt to register the identifier - it checks if it is a doi
540
            try {
541
    			DOIService.getInstance().registerDOI(sysmeta);
542
    		} catch (Exception e) {
543
                throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
544
    		}
545

    
546
        } else {
547
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
548
                    + " on the Member Node.");
549
        }
550

    
551
        return newPid;
552
    }
553

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

    
557
        if(isReadOnlyMode()) {
558
            throw new ServiceFailure("1190", ReadOnlyChecker.DATAONEERROR);
559
        }
560
        // check for null session
561
        if (session == null) {
562
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
563
        }
564
        // verify the pid is valid format
565
        if (!isValidIdentifier(pid)) {
566
            throw new InvalidRequest("1102", "The provided identifier is invalid.");
567
        }
568
        // set the submitter to match the certificate
569
        sysmeta.setSubmitter(session.getSubject());
570
        // set the originating node
571
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
572
        sysmeta.setOriginMemberNode(originMemberNode);
573
        
574
        // if no authoritative MN, set it to the same
575
        if (sysmeta.getAuthoritativeMemberNode() == null) {
576
        	sysmeta.setAuthoritativeMemberNode(originMemberNode);
577
        }
578
        
579
        sysmeta.setArchived(false);
580

    
581
        // set the dates
582
        Date now = Calendar.getInstance().getTime();
583
        sysmeta.setDateSysMetadataModified(now);
584
        sysmeta.setDateUploaded(now);
585
        
586
        // set the serial version
587
        sysmeta.setSerialVersion(BigInteger.ZERO);
588

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

    
631
        // call the shared impl
632
        Identifier resultPid = super.create(session, pid, object, sysmeta);
633
        
634
        // attempt to register the identifier - it checks if it is a doi
635
        try {
636
			DOIService.getInstance().registerDOI(sysmeta);
637
		} catch (Exception e) {
638
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
639
			sf.initCause(e);
640
            throw sf;
641
		}
642
        
643
        // return 
644
		return resultPid ;
645
    }
646

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

    
677
        if (session != null && sysmeta != null && sourceNode != null) {
678
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
679
                            "\tSession.Subject      = "                           +
680
                            session.getSubject().getValue() + "\n"                +
681
                            "\tidentifier           = "                           + 
682
                            sysmeta.getIdentifier().getValue()                    +
683
                            "\n" + "\tSource NodeReference ="                     +
684
                            sourceNode.getValue());
685
        }
686
        boolean result = false;
687
        String nodeIdStr = null;
688
        NodeReference nodeId = null;
689

    
690
        // get the referenced object
691
        Identifier pid = sysmeta.getIdentifier();
692
        // verify the pid is valid format
693
        if (!isValidIdentifier(pid)) {
694
            throw new InvalidRequest("2153", "The provided identifier in the system metadata is invalid.");
695
        }
696

    
697
        // get from the membernode
698
        // TODO: switch credentials for the server retrieval?
699
        this.cn = D1Client.getCN();
700
        InputStream object = null;
701
        Session thisNodeSession = null;
702
        SystemMetadata localSystemMetadata = null;
703
        BaseException failure = null;
704
        String localId = null;
705
        
706
        // TODO: check credentials
707
        // cannot be called by public
708
        if (session == null || session.getSubject() == null) {
709
            String msg = "No session was provided to replicate identifier " +
710
            sysmeta.getIdentifier().getValue();
711
            logMetacat.error(msg);
712
            throw new NotAuthorized("2152", msg);
713
            
714
        }
715

    
716

    
717
        // get the local node id
718
        try {
719
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
720
            nodeId = new NodeReference();
721
            nodeId.setValue(nodeIdStr);
722

    
723
        } catch (PropertyNotFoundException e1) {
724
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
725
            failure = new ServiceFailure("2151", msg);
726
            //setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
727
            logMetacat.error(msg);
728
            //return true;
729
            throw new ServiceFailure("2151", msg);
730

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

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

    
791
                }
792

    
793
            } catch (InvalidToken e) {            
794
                String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
795
                failure = new ServiceFailure("2151", msg);
796
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
797
                logMetacat.error(msg);
798
                throw new ServiceFailure("2151", msg);
799

    
800
            } catch (NotFound e) {
801
                String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
802
                failure = new ServiceFailure("2151", msg);
803
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
804
                logMetacat.error(msg);
805
                throw new ServiceFailure("2151", msg);
806

    
807
            } catch (NotAuthorized e) {
808
                String msg = "Could not retrieve object to replicate (NotAuthorized): "+ e.getMessage();
809
                failure = new ServiceFailure("2151", msg);
810
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
811
                logMetacat.error(msg);
812
                throw new ServiceFailure("2151", msg);
813
            } catch (NotImplemented e) {
814
                String msg = "Could not retrieve object to replicate (mn.getReplica NotImplemented): "+ e.getMessage();
815
                failure = new ServiceFailure("2151", msg);
816
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
817
                logMetacat.error(msg);
818
                throw new ServiceFailure("2151", msg);
819
            } catch (ServiceFailure e) {
820
                String msg = "Could not retrieve object to replicate (ServiceFailure): "+ e.getMessage();
821
                failure = new ServiceFailure("2151", msg);
822
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
823
                logMetacat.error(msg);
824
                throw new ServiceFailure("2151", msg);
825
            } catch (InsufficientResources e) {
826
                String msg = "Could not retrieve object to replicate (InsufficientResources): "+ e.getMessage();
827
                failure = new ServiceFailure("2151", msg);
828
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
829
                logMetacat.error(msg);
830
                throw new ServiceFailure("2151", msg);
831
            }
832

    
833
            // verify checksum on the object, if supported
834
            if (object.markSupported()) {
835
                Checksum givenChecksum = sysmeta.getChecksum();
836
                Checksum computedChecksum = null;
837
                try {
838
                    computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
839
                    object.reset();
840

    
841
                } catch (Exception e) {
842
                    String msg = "Error computing checksum on replica: " + e.getMessage();
843
                    logMetacat.error(msg);
844
                    ServiceFailure sf = new ServiceFailure("2151", msg);
845
                    sf.initCause(e);
846
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, sf);
847
                    throw sf;
848
                }
849
                if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
850
                    logMetacat.error("Given    checksum for " + pid.getValue() + 
851
                        "is " + givenChecksum.getValue());
852
                    logMetacat.error("Computed checksum for " + pid.getValue() + 
853
                        "is " + computedChecksum.getValue());
854
                    String msg = "Computed checksum does not match declared checksum";
855
                    failure = new ServiceFailure("2151", msg);
856
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
857
                    throw new ServiceFailure("2151", msg);
858
                }
859
            }
860

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

    
885
        // finish by setting the replication status
886
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
887
        return result;
888

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

    
927
    /**
928
     * Return the object identified by the given object identifier
929
     * 
930
     * @param session - the Session object containing the credentials for the Subject
931
     * @param pid - the object identifier for the given object
932
     * 
933
     * @return inputStream - the input stream of the given object
934
     * 
935
     * @throws InvalidToken
936
     * @throws ServiceFailure
937
     * @throws NotAuthorized
938
     * @throws InvalidRequest
939
     * @throws NotImplemented
940
     */
941
    @Override
942
    public InputStream get(Session session, Identifier pid) 
943
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
944

    
945
        return super.get(session, pid);
946

    
947
    }
948

    
949
    /**
950
     * Returns a Checksum for the specified object using an accepted hashing algorithm
951
     * 
952
     * @param session - the Session object containing the credentials for the Subject
953
     * @param pid - the object identifier for the given object
954
     * @param algorithm -  the name of an algorithm that will be used to compute 
955
     *                     a checksum of the bytes of the object
956
     * 
957
     * @return checksum - the checksum of the given object
958
     * 
959
     * @throws InvalidToken
960
     * @throws ServiceFailure
961
     * @throws NotAuthorized
962
     * @throws NotFound
963
     * @throws InvalidRequest
964
     * @throws NotImplemented
965
     */
966
    @Override
967
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
968
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
969
        InvalidRequest, NotImplemented {
970

    
971
        Checksum checksum = null;
972
        String serviceFailure = "1410";
973
        String notFound = "1420";
974
        //Checkum only handles the pid, not sid
975
        checkV1SystemMetaPidExist(pid, serviceFailure, "The checksum for the object specified by "+pid.getValue()+" couldn't be returned ",  notFound, 
976
                "The object specified by "+pid.getValue()+" does not exist at this node.");
977
        InputStream inputStream = get(session, pid);
978

    
979
        try {
980
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
981

    
982
        } catch (NoSuchAlgorithmException e) {
983
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
984
                    + e.getMessage());
985
        } catch (IOException e) {
986
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
987
                    + e.getMessage());
988
        }
989

    
990
        if (checksum == null) {
991
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
992
        }
993

    
994
        return checksum;
995
    }
996

    
997
    /**
998
     * Return the system metadata for a given object
999
     * 
1000
     * @param session - the Session object containing the credentials for the Subject
1001
     * @param pid - the object identifier for the given object
1002
     * 
1003
     * @return inputStream - the input stream of the given system metadata object
1004
     * 
1005
     * @throws InvalidToken
1006
     * @throws ServiceFailure
1007
     * @throws NotAuthorized
1008
     * @throws NotFound
1009
     * @throws InvalidRequest
1010
     * @throws NotImplemented
1011
     */
1012
    @Override
1013
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) 
1014
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
1015
        NotImplemented {
1016

    
1017
        return super.getSystemMetadata(session, pid);
1018
    }
1019

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

    
1061
    /**
1062
     * Return a description of the node's capabilities and services.
1063
     * 
1064
     * @return node - the technical capabilities of the Member Node
1065
     * 
1066
     * @throws ServiceFailure
1067
     * @throws NotAuthorized
1068
     * @throws InvalidRequest
1069
     * @throws NotImplemented
1070
     */
1071
    @Override
1072
    public Node getCapabilities() 
1073
        throws NotImplemented, ServiceFailure {
1074

    
1075
        String nodeName = null;
1076
        String nodeId = null;
1077
        String subject = null;
1078
        String contactSubject = null;
1079
        String nodeDesc = null;
1080
        String nodeTypeString = null;
1081
        NodeType nodeType = null;
1082
        List<String> mnCoreServiceVersions = null;
1083
        List<String> mnReadServiceVersions = null;
1084
        List<String> mnAuthorizationServiceVersions = null;
1085
        List<String> mnStorageServiceVersions = null;
1086
        List<String> mnReplicationServiceVersions = null;
1087

    
1088
        boolean nodeSynchronize = false;
1089
        boolean nodeReplicate = false;
1090
        List<String> mnCoreServiceAvailables = null;
1091
        List<String> mnReadServiceAvailables = null;
1092
        List<String> mnAuthorizationServiceAvailables = null;
1093
        List<String> mnStorageServiceAvailables = null;
1094
        List<String> mnReplicationServiceAvailables = null;
1095

    
1096
        try {
1097
            // get the properties of the node based on configuration information
1098
            nodeName = Settings.getConfiguration().getString("dataone.nodeName");
1099
            nodeId = Settings.getConfiguration().getString("dataone.nodeId");
1100
            subject = Settings.getConfiguration().getString("dataone.subject");
1101
            contactSubject = Settings.getConfiguration().getString("dataone.contactSubject");
1102
            nodeDesc = Settings.getConfiguration().getString("dataone.nodeDescription");
1103
            nodeTypeString = Settings.getConfiguration().getString("dataone.nodeType");
1104
            nodeType = NodeType.convert(nodeTypeString);
1105
            nodeSynchronize = new Boolean(Settings.getConfiguration().getString("dataone.nodeSynchronize")).booleanValue();
1106
            nodeReplicate = new Boolean(Settings.getConfiguration().getString("dataone.nodeReplicate")).booleanValue();
1107

    
1108
            // Set the properties of the node based on configuration information and
1109
            // calls to current status methods
1110
            String serviceName = SystemUtil.getContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
1111
            Node node = new Node();
1112
            node.setBaseURL(serviceName + "/" + nodeTypeString);
1113
            node.setDescription(nodeDesc);
1114

    
1115
            // set the node's health information
1116
            node.setState(NodeState.UP);
1117
            
1118
            // set the ping response to the current value
1119
            Ping canPing = new Ping();
1120
            canPing.setSuccess(false);
1121
            try {
1122
            	Date pingDate = ping();
1123
                canPing.setSuccess(pingDate != null);
1124
            } catch (BaseException e) {
1125
                e.printStackTrace();
1126
                // guess it can't be pinged
1127
            }
1128
            
1129
            node.setPing(canPing);
1130

    
1131
            NodeReference identifier = new NodeReference();
1132
            identifier.setValue(nodeId);
1133
            node.setIdentifier(identifier);
1134
            Subject s = new Subject();
1135
            s.setValue(subject);
1136
            node.addSubject(s);
1137
            Subject contact = new Subject();
1138
            contact.setValue(contactSubject);
1139
            node.addContactSubject(contact);
1140
            node.setName(nodeName);
1141
            node.setReplicate(nodeReplicate);
1142
            node.setSynchronize(nodeSynchronize);
1143

    
1144
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
1145
            Services services = new Services();
1146

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

    
1219
            // Set the schedule for synchronization
1220
            Synchronization synchronization = new Synchronization();
1221
            Schedule schedule = new Schedule();
1222
            Date now = new Date();
1223
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
1224
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
1225
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
1226
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
1227
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
1228
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
1229
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
1230
            synchronization.setSchedule(schedule);
1231
            synchronization.setLastHarvested(now);
1232
            synchronization.setLastCompleteHarvest(now);
1233
            node.setSynchronization(synchronization);
1234

    
1235
            node.setType(nodeType);
1236
            return node;
1237

    
1238
        } catch (PropertyNotFoundException pnfe) {
1239
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
1240
            logMetacat.error(msg);
1241
            throw new ServiceFailure("2162", msg);
1242
        }
1243
    }
1244

    
1245
    
1246

    
1247
    /**
1248
     * A callback method used by a CN to indicate to a MN that it cannot 
1249
     * complete synchronization of the science metadata identified by pid.  Log
1250
     * the event in the metacat event log.
1251
     * 
1252
     * @param session
1253
     * @param syncFailed
1254
     * 
1255
     * @throws ServiceFailure
1256
     * @throws NotAuthorized
1257
     * @throws NotImplemented
1258
     */
1259
    @Override
1260
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
1261
        throws NotImplemented, ServiceFailure, NotAuthorized {
1262

    
1263
        String localId;
1264
        Identifier pid;
1265
        if ( syncFailed.getPid() != null ) {
1266
            pid = new Identifier();
1267
            pid.setValue(syncFailed.getPid());
1268
            boolean allowed;
1269
            
1270
            //are we allowed? only CNs
1271
            try {
1272
                allowed = isAdminAuthorized(session);
1273
                if ( !allowed ){
1274
                    throw new NotAuthorized("2162", 
1275
                            "Not allowed to call synchronizationFailed() on this node.");
1276
                }
1277
            } catch (InvalidToken e) {
1278
                throw new NotAuthorized("2162", 
1279
                        "Not allowed to call synchronizationFailed() on this node.");
1280

    
1281
            }
1282
            
1283
        } else {
1284
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1285

    
1286
        }
1287
        
1288
        try {
1289
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1290
        } catch (McdbDocNotFoundException e) {
1291
            throw new ServiceFailure("2161", "The identifier specified by " + 
1292
                    syncFailed.getPid() + " was not found on this node.");
1293

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

    
1318
    }
1319

    
1320
    /**
1321
     * Essentially a get() but with different logging behavior
1322
     */
1323
    @Override
1324
    public InputStream getReplica(Session session, Identifier pid) 
1325
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken, NotFound {
1326

    
1327
        logMetacat.info("MNodeService.getReplica() called.");
1328

    
1329
        // cannot be called by public
1330
        if (session == null) {
1331
        	throw new InvalidToken("2183", "No session was provided.");
1332
        }
1333
        
1334
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1335
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1336
             "\tIdentifier           = " + pid.getValue());
1337

    
1338
        InputStream inputStream = null; // bytes to be returned
1339
        handler = new MetacatHandler(new Timer());
1340
        boolean allowed = false;
1341
        String localId; // the metacat docid for the pid
1342

    
1343
        // get the local docid from Metacat
1344
        try {
1345
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1346
        } catch (McdbDocNotFoundException e) {
1347
            throw new NotFound("2185", "The object specified by " + 
1348
                    pid.getValue() + " does not exist at this node.");
1349
            
1350
        } catch (SQLException e) {
1351
            throw new ServiceFailure("2181", "The local id of the object specified by " + 
1352
                    pid.getValue() + " couldn't be identified since "+e.getMessage());
1353
        }
1354

    
1355
        Subject targetNodeSubject = session.getSubject();
1356

    
1357
        // check for authorization to replicate, null session to act as this source MN
1358
        try {
1359
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1360
        } catch (InvalidToken e1) {
1361
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1362
                + e1.getMessage());
1363
            
1364
        } catch (NotFound e1) {
1365
            throw new NotFound("2185", "Could not find the object "+pid.getValue()+" in this node - " 
1366
                    + e1.getMessage());
1367

    
1368
        } catch (InvalidRequest e1) {
1369
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1370
                    + e1.getMessage());
1371

    
1372
        }
1373

    
1374
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1375
            " for identifier " + pid.getValue());
1376

    
1377
        // if the person is authorized, perform the read
1378
        if (allowed) {
1379
            try {
1380
                inputStream = MetacatHandler.read(localId);
1381
            } catch (Exception e) {
1382
                throw new ServiceFailure("2181", "The object specified by " + 
1383
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1384
            }
1385
        } else {
1386
            throw new NotAuthorized("2182", "The pid "+pid.getValue()+" is not authorized to be read by the client.");
1387
        }
1388

    
1389
        // if we fail to set the input stream
1390
        if (inputStream == null) {
1391
            throw new ServiceFailure("2181", "The object specified by " + 
1392
                pid.getValue() + " can't be returned from the node.");
1393
        }
1394

    
1395
        // log the replica event
1396
        String principal = null;
1397
        if (session.getSubject() != null) {
1398
            principal = session.getSubject().getValue();
1399
        }
1400
        EventLog.getInstance().log(request.getRemoteAddr(), 
1401
            request.getHeader("User-Agent"), principal, localId, "replicate");
1402

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

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

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

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

    
1760
	
1761

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

    
1810
	@Override
1811
	public QueryEngineList listQueryEngines(Session session) throws InvalidToken,
1812
			ServiceFailure, NotAuthorized, NotImplemented {
1813
		QueryEngineList qel = new QueryEngineList();
1814
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1815
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1816
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1817
		for(String name : enables) {
1818
		    qel.addQueryEngine(name);
1819
		}
1820
		return qel;
1821
	}
1822

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

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

    
1914
		// make copy of it using the marshaller to ensure DEEP copy
1915
		SystemMetadata sysmeta = null;
1916
		try {
1917
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
1918
			TypeMarshaller.marshalTypeToOutputStream(originalSystemMetadata, baos);
1919
			sysmeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1920
		} catch (Exception e) {
1921
			// report as service failure
1922
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1923
			sf.initCause(e);
1924
			throw sf;
1925
		}
1926

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

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

    
2136
		    //Get the system metadata for this object
2137
		    SystemMetadata sysMeta = null;
2138
		    try{
2139
		    	sysMeta = getSystemMetadata(session, pid);
2140
		    } catch(NotAuthorized e){
2141
		    	throw new ServiceFailure("1030", "D1NodeService.editScienceMetadata(): " + 
2142
		    			"This session is not authorized to access the system metadata for " +
2143
		    			pid.getValue() + " : " + e.getMessage());
2144
		    } catch(NotFound e){
2145
		    	throw new ServiceFailure("1030", "D1NodeService.editScienceMetadata(): " + 
2146
		    			"Could not find the system metadata for " +
2147
		    			pid.getValue() + " : " + e.getMessage());
2148
		    }
2149
		    
2150
		    //Get the formatId
2151
	        ObjectFormatIdentifier objFormatId = sysMeta.getFormatId();
2152
	        String formatId = objFormatId.getValue();
2153
	        
2154
	    	//For all EML formats
2155
	        if(formatId.indexOf("eml") == 0){
2156
	        	//Update or add the id attribute
2157
	    	    XMLUtilities.addAttributeNodeToDOMTree(docNode, XPATH_EML_ID, newPid.getValue());
2158
	        }
2159

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

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

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

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

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

    
2541
	      if (allowed) {
2542
	         try {
2543
	             HazelcastService.getInstance().getSystemMetadataMap().lock(pid);
2544
	             logMetacat.debug("MNodeService.archive - lock the identifier "+pid.getValue()+" in the system metadata map.");
2545
	             SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
2546
	             boolean needModifyDate = true;
2547
	             boolean logArchive = true;
2548
	             super.archiveObject(logArchive, session, pid, sysmeta, needModifyDate); 
2549
	         } finally {
2550
	             HazelcastService.getInstance().getSystemMetadataMap().unlock(pid);
2551
	             logMetacat.debug("MNodeService.archive - unlock the identifier "+pid.getValue()+" in the system metadata map.");
2552
	         }
2553
	        
2554

    
2555
	      } else {
2556
	          throw new NotAuthorized("1320", "The provided identity does not have " + "permission to archive the object on the Node.");
2557
	      }
2558

    
2559
	      return pid;
2560
	  }
2561
    
2562
	/**
2563
	 * Update the system metadata of the specified pid.
2564
	 */
2565
	@Override
2566
	public boolean updateSystemMetadata(Session session, Identifier pid,
2567
            SystemMetadata sysmeta) throws NotImplemented, NotAuthorized,
2568
            ServiceFailure, InvalidRequest, InvalidSystemMetadata, InvalidToken {
2569
	  
2570
	  if(isReadOnlyMode()) {
2571
            throw new ServiceFailure("4868",  ReadOnlyChecker.DATAONEERROR);
2572
      }
2573
	 if(sysmeta == null) {
2574
	     throw  new InvalidRequest("4869", "The system metadata object should NOT be null in the updateSystemMetadata request.");
2575
	 }
2576
	 if(pid == null || pid.getValue() == null) {
2577
         throw new InvalidRequest("4869", "Please specify the id in the updateSystemMetadata request ") ;
2578
     }
2579

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

    
2725
				} catch (ServiceFailure e) {
2726
					logMetacat.debug("Failed to authorize the Member Node Admin Subject: " + e.getMessage());
2727
					
2728
				}
2729
            	
2730
            	// Check for user authorization
2731
            	if ( !allow ) {
2732
                    allow = userHasPermission(session, pid, permission);
2733
                    
2734
            	}
2735
                    
2736
            } else {
2737
                throw new NotAuthorized("4861", "Client can only call the request on the authoritative memember node of the object "+pid.getValue());
2738
                
2739
            }
2740
        }
2741
        return allow;
2742
        
2743
    }
2744
    
2745
    /**
2746
     * Check if the metacat is in the read-only mode.
2747
     * @return true if it is; otherwise false.
2748
     */
2749
    protected boolean isReadOnlyMode() {
2750
        boolean readOnly = false;
2751
        ReadOnlyChecker checker = new ReadOnlyChecker();
2752
        readOnly = checker.isReadOnly();
2753
        return readOnly;
2754
    }
2755
    
2756
}
(5-5/8)