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.restservice.v2.MNResourceHandler;
158
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
159
import edu.ucsb.nceas.metacat.util.DeleteOnCloseFileInputStream;
160
import edu.ucsb.nceas.metacat.util.DocumentUtil;
161
import edu.ucsb.nceas.metacat.util.SkinUtil;
162
import edu.ucsb.nceas.metacat.util.SystemUtil;
163
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
164
import edu.ucsb.nceas.utilities.XMLUtilities;
165
import edu.ucsb.nceas.utilities.export.HtmlToPdf;
166
import gov.loc.repository.bagit.Bag;
167
import gov.loc.repository.bagit.BagFactory;
168
import gov.loc.repository.bagit.writer.impl.ZipWriter;
169

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

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

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

    
217

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

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

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

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

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

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

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

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

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

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

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

    
483
            isScienceMetadata = isScienceMetadata(sysmeta);
484

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

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

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

    
510
                }
511

    
512
            } else {
513

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

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

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

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

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

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

    
552
        return newPid;
553
    }
554

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

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

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

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

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

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

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

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

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

    
717

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

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

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

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

    
792
                }
793

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

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

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

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

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

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

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

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

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

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

    
948
    }
949

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

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

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

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

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

    
995
        return checksum;
996
    }
997

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1246
    
1247

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

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

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

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

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

    
1319
    }
1320

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

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

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

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

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

    
1356
        Subject targetNodeSubject = session.getSubject();
1357

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

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

    
1373
        }
1374

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

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

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

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

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

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

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

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

    
1761
	
1762

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2752
				} catch (ServiceFailure e) {
2753
					logMetacat.debug("Failed to authorize the Member Node Admin Subject: " + e.getMessage());
2754
					
2755
				}
2756
            	
2757
            	// Check for user authorization
2758
            	if ( !allow ) {
2759
                    allow = userHasPermission(session, pid, permission);
2760
                    
2761
            	}
2762
                    
2763
            } else {
2764
                throw new NotAuthorized("4861", "Client can only call the request on the authoritative memember node of the object "+pid.getValue());
2765
                
2766
            }
2767
        }
2768
        return allow;
2769
        
2770
    }
2771
    
2772
    /**
2773
     * Check if the metacat is in the read-only mode.
2774
     * @return true if it is; otherwise false.
2775
     */
2776
    protected boolean isReadOnlyMode() {
2777
        boolean readOnly = false;
2778
        ReadOnlyChecker checker = new ReadOnlyChecker();
2779
        readOnly = checker.isReadOnly();
2780
        return readOnly;
2781
    }
2782
    
2783
}
(5-5/8)