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.UnsupportedEncodingException;
36
import java.io.Writer;
37
import java.math.BigInteger;
38
import java.net.URISyntaxException;
39
import java.security.NoSuchAlgorithmException;
40
import java.sql.SQLException;
41
import java.util.ArrayList;
42
import java.util.Calendar;
43
import java.util.Date;
44
import java.util.HashMap;
45
import java.util.HashSet;
46
import java.util.Hashtable;
47
import java.util.List;
48
import java.util.Map;
49
import java.util.Set;
50
import java.util.Timer;
51
import java.util.UUID;
52
import java.util.Vector;
53

    
54
import javax.servlet.http.HttpServletRequest;
55

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

    
130
import edu.ucsb.nceas.ezid.EZIDException;
131
import edu.ucsb.nceas.metacat.DBQuery;
132
import edu.ucsb.nceas.metacat.DBTransform;
133
import edu.ucsb.nceas.metacat.EventLog;
134
import edu.ucsb.nceas.metacat.IdentifierManager;
135
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
136
import edu.ucsb.nceas.metacat.MetaCatServlet;
137
import edu.ucsb.nceas.metacat.MetacatHandler;
138
import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines;
139
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeByteArrayInputStream;
140
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
141
import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler;
142
import edu.ucsb.nceas.metacat.index.MetacatSolrIndex;
143
import edu.ucsb.nceas.metacat.properties.PropertyService;
144
import edu.ucsb.nceas.metacat.shared.MetacatUtilException;
145
import edu.ucsb.nceas.metacat.util.DeleteOnCloseFileInputStream;
146
import edu.ucsb.nceas.metacat.util.DocumentUtil;
147
import edu.ucsb.nceas.metacat.util.SkinUtil;
148
import edu.ucsb.nceas.metacat.util.SystemUtil;
149
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
150
import edu.ucsb.nceas.utilities.XMLUtilities;
151
import edu.ucsb.nceas.utilities.export.HtmlToPdf;
152
import gov.loc.repository.bagit.Bag;
153
import gov.loc.repository.bagit.BagFactory;
154
import gov.loc.repository.bagit.writer.impl.ZipWriter;
155

    
156
/**
157
 * Represents Metacat's implementation of the DataONE Member Node 
158
 * service API. Methods implement the various MN* interfaces, and methods common
159
 * to both Member Node and Coordinating Node interfaces are found in the
160
 * D1NodeService base class.
161
 * 
162
 * Implements:
163
 * MNCore.ping()
164
 * MNCore.getLogRecords()
165
 * MNCore.getObjectStatistics()
166
 * MNCore.getOperationStatistics()
167
 * MNCore.getStatus()
168
 * MNCore.getCapabilities()
169
 * MNRead.get()
170
 * MNRead.getSystemMetadata()
171
 * MNRead.describe()
172
 * MNRead.getChecksum()
173
 * MNRead.listObjects()
174
 * MNRead.synchronizationFailed()
175
 * MNAuthorization.isAuthorized()
176
 * MNAuthorization.setAccessPolicy()
177
 * MNStorage.create()
178
 * MNStorage.update()
179
 * MNStorage.delete()
180
 * MNReplication.replicate()
181
 * 
182
 */
183
public class MNodeService extends D1NodeService 
184
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage, MNQuery, MNView, MNPackage {
185

    
186
    //private static final String PATHQUERY = "pathquery";
187
	public static final String UUID_SCHEME = "UUID";
188
	public static final String DOI_SCHEME = "DOI";
189
	private static final String UUID_PREFIX = "urn:uuid:";
190

    
191
	/* the logger instance */
192
    private Logger logMetacat = null;
193
    
194
    /* A reference to a remote Memeber Node */
195
    private MNode mn;
196
    
197
    /* A reference to a Coordinating Node */
198
    private CNode cn;
199

    
200

    
201
    /**
202
     * Singleton accessor to get an instance of MNodeService.
203
     * 
204
     * @return instance - the instance of MNodeService
205
     */
206
    public static MNodeService getInstance(HttpServletRequest request) {
207
        return new MNodeService(request);
208
    }
209

    
210
    /**
211
     * Constructor, private for singleton access
212
     */
213
    private MNodeService(HttpServletRequest request) {
214
        super(request);
215
        logMetacat = Logger.getLogger(MNodeService.class);
216
        
217
        // set the Member Node certificate file location
218
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
219
    }
220

    
221
    /**
222
     * Deletes an object from the Member Node, where the object is either a 
223
     * data object or a science metadata object.
224
     * 
225
     * @param session - the Session object containing the credentials for the Subject
226
     * @param pid - The object identifier to be deleted
227
     * 
228
     * @return pid - the identifier of the object used for the deletion
229
     * 
230
     * @throws InvalidToken
231
     * @throws ServiceFailure
232
     * @throws NotAuthorized
233
     * @throws NotFound
234
     * @throws NotImplemented
235
     * @throws InvalidRequest
236
     */
237
    @Override
238
    public Identifier delete(Session session, Identifier pid) 
239
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
240

    
241
    	// only admin of  the MN or the CN is allowed a full delete
242
        boolean allowed = false;
243
        allowed = isAdminAuthorized(session);
244
        
245
        //check if it is the authoritative member node
246
        if(!allowed) {
247
            allowed = isAuthoritativeMNodeAdmin(session, pid);
248
        }
249
        
250
        if (!allowed) { 
251
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
252
        }
253
    	
254
    	// defer to superclass implementation
255
        return super.delete(session, pid);
256
    }
257

    
258
    /**
259
     * Updates an existing object by creating a new object identified by 
260
     * newPid on the Member Node which explicitly obsoletes the object 
261
     * identified by pid through appropriate changes to the SystemMetadata 
262
     * of pid and newPid
263
     * 
264
     * @param session - the Session object containing the credentials for the Subject
265
     * @param pid - The identifier of the object to be updated
266
     * @param object - the new object bytes
267
     * @param sysmeta - the new system metadata describing the object
268
     * 
269
     * @return newPid - the identifier of the new object
270
     * 
271
     * @throws InvalidToken
272
     * @throws ServiceFailure
273
     * @throws NotAuthorized
274
     * @throws NotFound
275
     * @throws NotImplemented
276
     * @throws IdentifierNotUnique
277
     * @throws UnsupportedType
278
     * @throws InsufficientResources
279
     * @throws InvalidSystemMetadata
280
     * @throws InvalidRequest
281
     */
282
    @Override
283
    public Identifier update(Session session, Identifier pid, InputStream object, 
284
        Identifier newPid, SystemMetadata sysmeta) 
285
        throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
286
        UnsupportedType, InsufficientResources, NotFound, 
287
        InvalidSystemMetadata, NotImplemented, InvalidRequest {
288

    
289
        //transform a sid to a pid if it is applicable
290
        String serviceFailureCode = "1310";
291
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
292
        if(sid != null) {
293
            pid = sid;
294
        }
295
        
296
        String localId = null;
297
        boolean allowed = false;
298
        boolean isScienceMetadata = false;
299
        
300
        if (session == null) {
301
        	throw new InvalidToken("1210", "No session has been provided");
302
        }
303
        Subject subject = session.getSubject();
304

    
305
        // verify the pid is valid format
306
        if (!isValidIdentifier(pid)) {
307
        	throw new InvalidRequest("1202", "The provided identifier is invalid.");
308
        }
309
        
310
        // verify the new pid is valid format
311
        if (!isValidIdentifier(newPid)) {
312
            throw new InvalidRequest("1202", "The provided identifier is invalid.");
313
        }
314
        
315
        // make sure that the newPid doesn't exists
316
        boolean idExists = true;
317
        try {
318
            idExists = IdentifierManager.getInstance().identifierExists(newPid.getValue());
319
        } catch (SQLException e) {
320
            throw new ServiceFailure("1310", 
321
                                    "The requested identifier " + newPid.getValue() +
322
                                    " couldn't be determined if it is unique since : "+e.getMessage());
323
        }
324
        if (idExists) {
325
                throw new IdentifierNotUnique("1220", 
326
                          "The requested identifier " + newPid.getValue() +
327
                          " is already used by another object and" +
328
                          "therefore can not be used for this object. Clients should choose" +
329
                          "a new identifier that is unique and retry the operation or " +
330
                          "use CN.reserveIdentifier() to reserve one.");
331
            
332
        }
333
        
334
       
335

    
336
        // check for the existing identifier
337
        try {
338
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
339
            
340
        } catch (McdbDocNotFoundException e) {
341
            throw new InvalidRequest("1202", "The object with the provided " + 
342
                "identifier was not found.");
343
            
344
        } catch (SQLException ee) {
345
            throw new ServiceFailure("1310", "The object with the provided " + 
346
                    "identifier "+pid.getValue()+" can't be identified since - "+ee.getMessage());
347
        }
348
        
349
        // set the originating node
350
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
351
        sysmeta.setOriginMemberNode(originMemberNode);
352
        
353
        // set the submitter to match the certificate
354
        sysmeta.setSubmitter(subject);
355
        // set the dates
356
        Date now = Calendar.getInstance().getTime();
357
        sysmeta.setDateSysMetadataModified(now);
358
        sysmeta.setDateUploaded(now);
359
        
360
        // make sure serial version is set to something
361
        BigInteger serialVersion = sysmeta.getSerialVersion();
362
        if (serialVersion == null) {
363
        	sysmeta.setSerialVersion(BigInteger.ZERO);
364
        }
365

    
366
        // does the subject have WRITE ( == update) priveleges on the pid?
367
        allowed = isAuthorized(session, pid, Permission.WRITE);
368

    
369
        if (allowed) {
370
        	
371
        	// check quality of SM
372
        	if (sysmeta.getObsoletedBy() != null) {
373
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
374
        	}
375
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
376
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
377
        	}
378

    
379
            // get the existing system metadata for the object
380
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
381

    
382
            // check for previous update
383
            // see: https://redmine.dataone.org/issues/3336
384
            Identifier existingObsoletedBy = existingSysMeta.getObsoletedBy();
385
            if (existingObsoletedBy != null) {
386
            	throw new InvalidRequest("1202", 
387
            			"The previous identifier has already been made obsolete by: " + existingObsoletedBy.getValue());
388
            }
389
            //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.
390
            Identifier sidInSys = sysmeta.getSeriesId();
391
            if(sidInSys != null) {
392
                if (!isValidIdentifier(sidInSys)) {
393
                    throw new InvalidSystemMetadata("1300", "The provided series id in the system metadata is invalid.");
394
                }
395
                Identifier previousSid = existingSysMeta.getSeriesId();
396
                if(previousSid != null) {
397
                    // there is a previous sid, if the new sid doesn't match it, the new sid should be non-existing.
398
                    if(!sidInSys.getValue().equals(previousSid)) {
399
                        try {
400
                            idExists = IdentifierManager.getInstance().identifierExists(sidInSys.getValue());
401
                        } catch (SQLException e) {
402
                            throw new ServiceFailure("1310", 
403
                                                    "The requested identifier " + sidInSys.getValue() +
404
                                                    " couldn't be determined if it is unique since : "+e.getMessage());
405
                        }
406
                        if(idExists) {
407
                            throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata doesn't match the previous series id "
408
                                                            +previousSid.getValue()+", so it should NOT exist. However, it was used by another object.");
409
                        }
410
                    }
411
                } else {
412
                    // there is no previous sid, the new sid should be non-existing.
413
                    try {
414
                        idExists = IdentifierManager.getInstance().identifierExists(sidInSys.getValue());
415
                    } catch (SQLException e) {
416
                        throw new ServiceFailure("1310", 
417
                                                "The requested identifier " + sidInSys.getValue() +
418
                                                " couldn't be determined if it is unique since : "+e.getMessage());
419
                    }
420
                    if(idExists) {
421
                        throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata should NOT exist since the previous series id is null."
422
                                                        +"However, it was used by another object.");
423
                    }
424
                }
425
                //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)
426
                if(sidInSys.getValue().equals(newPid.getValue())) {
427
                    throw new InvalidSystemMetadata("1300", "The series id "+sidInSys.getValue()+" in the system metadata shouldn't have the same value of the pid.");
428
                }
429
            }
430

    
431
            isScienceMetadata = isScienceMetadata(sysmeta);
432

    
433
            // do we have XML metadata or a data object?
434
            if (isScienceMetadata) {
435

    
436
                // update the science metadata XML document
437
                // TODO: handle non-XML metadata/data documents (like netCDF)
438
                // TODO: don't put objects into memory using stream to string
439
                //String objectAsXML = "";
440
                try {
441
                    //objectAsXML = IOUtils.toString(object, "UTF-8");
442
                    // give the old pid so we can calculate the new local id 
443
                    localId = insertOrUpdateDocument(object, "UTF-8", pid, session, "update");
444
                    // register the newPid and the generated localId
445
                    if (newPid != null) {
446
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
447

    
448
                    }
449

    
450
                } catch (IOException e) {
451
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
452
                    logMetacat.info(msg);
453
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
454

    
455
                }
456

    
457
            } else {
458

    
459
                // update the data object
460
                localId = insertDataObject(object, newPid, session);
461

    
462
            }
463
            
464
            // add the newPid to the obsoletedBy list for the existing sysmeta
465
            existingSysMeta.setObsoletedBy(newPid);
466

    
467
            // then update the existing system metadata
468
            updateSystemMetadata(existingSysMeta);
469

    
470
            // prep the new system metadata, add pid to the affected lists
471
            sysmeta.setObsoletes(pid);
472
            //sysmeta.addDerivedFrom(pid);
473

    
474
            // and insert the new system metadata
475
            insertSystemMetadata(sysmeta);
476

    
477
            // log the update event
478
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
479
            
480
            // attempt to register the identifier - it checks if it is a doi
481
            try {
482
    			DOIService.getInstance().registerDOI(sysmeta);
483
    		} catch (Exception e) {
484
                throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
485
    		}
486

    
487
        } else {
488
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
489
                    + " on the Member Node.");
490
        }
491

    
492
        return newPid;
493
    }
494

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

    
498
        // check for null session
499
        if (session == null) {
500
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
501
        }
502
        // set the submitter to match the certificate
503
        sysmeta.setSubmitter(session.getSubject());
504
        // set the originating node
505
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
506
        sysmeta.setOriginMemberNode(originMemberNode);
507
        sysmeta.setArchived(false);
508

    
509
        // set the dates
510
        Date now = Calendar.getInstance().getTime();
511
        sysmeta.setDateSysMetadataModified(now);
512
        sysmeta.setDateUploaded(now);
513
        
514
        // set the serial version
515
        sysmeta.setSerialVersion(BigInteger.ZERO);
516

    
517
        // check that we are not attempting to subvert versioning
518
        if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
519
            throw new InvalidSystemMetadata("1180", 
520
              "The supplied system metadata is invalid. " +
521
              "The obsoletes field cannot have a value when creating entries.");
522
        }
523
        
524
        if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
525
            throw new InvalidSystemMetadata("1180", 
526
              "The supplied system metadata is invalid. " +
527
              "The obsoletedBy field cannot have a value when creating entries.");
528
        }
529

    
530
        // call the shared impl
531
        Identifier resultPid = super.create(session, pid, object, sysmeta);
532
        
533
        // attempt to register the identifier - it checks if it is a doi
534
        try {
535
			DOIService.getInstance().registerDOI(sysmeta);
536
		} catch (Exception e) {
537
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
538
			sf.initCause(e);
539
            throw sf;
540
		}
541
        
542
        // return 
543
		return resultPid ;
544
    }
545

    
546
    /**
547
     * Called by a Coordinating Node to request that the Member Node create a 
548
     * copy of the specified object by retrieving it from another Member 
549
     * Node and storing it locally so that it can be made accessible to 
550
     * the DataONE system.
551
     * 
552
     * @param session - the Session object containing the credentials for the Subject
553
     * @param sysmeta - Copy of the CN held system metadata for the object
554
     * @param sourceNode - A reference to node from which the content should be 
555
     *                     retrieved. The reference should be resolved by 
556
     *                     checking the CN node registry.
557
     * 
558
     * @return true if the replication succeeds
559
     * 
560
     * @throws ServiceFailure
561
     * @throws NotAuthorized
562
     * @throws NotImplemented
563
     * @throws UnsupportedType
564
     * @throws InsufficientResources
565
     * @throws InvalidRequest
566
     */
567
    @Override
568
    public boolean replicate(Session session, SystemMetadata sysmeta,
569
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
570
            NotAuthorized, InvalidRequest, InsufficientResources,
571
            UnsupportedType {
572

    
573
        if (session != null && sysmeta != null && sourceNode != null) {
574
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
575
                            "\tSession.Subject      = "                           +
576
                            session.getSubject().getValue() + "\n"                +
577
                            "\tidentifier           = "                           + 
578
                            sysmeta.getIdentifier().getValue()                    +
579
                            "\n" + "\tSource NodeReference ="                     +
580
                            sourceNode.getValue());
581
        }
582
        boolean result = false;
583
        String nodeIdStr = null;
584
        NodeReference nodeId = null;
585

    
586
        // get the referenced object
587
        Identifier pid = sysmeta.getIdentifier();
588

    
589
        // get from the membernode
590
        // TODO: switch credentials for the server retrieval?
591
        this.mn = D1Client.getMN(sourceNode);
592
        this.cn = D1Client.getCN();
593
        InputStream object = null;
594
        Session thisNodeSession = null;
595
        SystemMetadata localSystemMetadata = null;
596
        BaseException failure = null;
597
        String localId = null;
598
        
599
        // TODO: check credentials
600
        // cannot be called by public
601
        if (session == null || session.getSubject() == null) {
602
            String msg = "No session was provided to replicate identifier " +
603
            sysmeta.getIdentifier().getValue();
604
            logMetacat.error(msg);
605
            throw new NotAuthorized("2152", msg);
606
            
607
        }
608

    
609

    
610
        // get the local node id
611
        try {
612
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
613
            nodeId = new NodeReference();
614
            nodeId.setValue(nodeIdStr);
615

    
616
        } catch (PropertyNotFoundException e1) {
617
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
618
            failure = new ServiceFailure("2151", msg);
619
            //setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
620
            logMetacat.error(msg);
621
            //return true;
622
            throw new ServiceFailure("2151", msg);
623

    
624
        }
625
        
626

    
627
        try {
628
            // do we already have a replica?
629
            try {
630
                localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
631
                // if we have a local id, get the local object
632
                try {
633
                    object = MetacatHandler.read(localId);
634
                } catch (Exception e) {
635
                	// NOTE: we may already know about this ID because it could be a data file described by a metadata file
636
                	// https://redmine.dataone.org/issues/2572
637
                	// TODO: fix this so that we don't prevent ourselves from getting replicas
638
                	
639
                    // let the CN know that the replication failed
640
                	logMetacat.warn("Object content not found on this node despite having localId: " + localId);
641
                	String msg = "Can't read the object bytes properly, replica is invalid.";
642
                    ServiceFailure serviceFailure = new ServiceFailure("2151", msg);
643
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, serviceFailure);
644
                    logMetacat.warn(msg);
645
                    throw serviceFailure;
646
                    
647
                }
648

    
649
            } catch (McdbDocNotFoundException e) {
650
                logMetacat.info("No replica found. Continuing.");
651
                
652
            } catch (SQLException ee) {
653
                throw new ServiceFailure("2151", "Couldn't identify the local id of the object with the specified identifier "
654
                                        +pid.getValue()+" since - "+ee.getMessage());
655
            }
656
            
657
            // no local replica, get a replica
658
            if ( object == null ) {
659
                // session should be null to use the default certificate
660
                // location set in the Certificate manager
661
                object = mn.getReplica(thisNodeSession, pid);
662
                logMetacat.info("MNodeService.getReplica() called for identifier "
663
                                + pid.getValue());
664

    
665
            }
666

    
667
        } catch (InvalidToken e) {            
668
            String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
669
            failure = new ServiceFailure("2151", msg);
670
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
671
            logMetacat.error(msg);
672
            throw new ServiceFailure("2151", msg);
673

    
674
        } catch (NotFound e) {
675
            String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
676
            failure = new ServiceFailure("2151", msg);
677
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
678
            logMetacat.error(msg);
679
            throw new ServiceFailure("2151", msg);
680

    
681
        } catch (NotAuthorized e) {
682
            String msg = "Could not retrieve object to replicate (NotAuthorized): "+ e.getMessage();
683
            failure = new ServiceFailure("2151", msg);
684
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
685
            logMetacat.error(msg);
686
            throw new ServiceFailure("2151", msg);
687
        } catch (NotImplemented e) {
688
            String msg = "Could not retrieve object to replicate (mn.getReplica NotImplemented): "+ e.getMessage();
689
            failure = new ServiceFailure("2151", msg);
690
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
691
            logMetacat.error(msg);
692
            throw new ServiceFailure("2151", msg);
693
        } catch (ServiceFailure e) {
694
            String msg = "Could not retrieve object to replicate (ServiceFailure): "+ e.getMessage();
695
            failure = new ServiceFailure("2151", msg);
696
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
697
            logMetacat.error(msg);
698
            throw new ServiceFailure("2151", msg);
699
        } catch (InsufficientResources e) {
700
            String msg = "Could not retrieve object to replicate (InsufficientResources): "+ e.getMessage();
701
            failure = new ServiceFailure("2151", msg);
702
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
703
            logMetacat.error(msg);
704
            throw new ServiceFailure("2151", msg);
705
        }
706

    
707
        // verify checksum on the object, if supported
708
        if (object.markSupported()) {
709
            Checksum givenChecksum = sysmeta.getChecksum();
710
            Checksum computedChecksum = null;
711
            try {
712
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
713
                object.reset();
714

    
715
            } catch (Exception e) {
716
                String msg = "Error computing checksum on replica: " + e.getMessage();
717
                logMetacat.error(msg);
718
                ServiceFailure sf = new ServiceFailure("2151", msg);
719
                sf.initCause(e);
720
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, sf);
721
                throw sf;
722
            }
723
            if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
724
                logMetacat.error("Given    checksum for " + pid.getValue() + 
725
                    "is " + givenChecksum.getValue());
726
                logMetacat.error("Computed checksum for " + pid.getValue() + 
727
                    "is " + computedChecksum.getValue());
728
                String msg = "Computed checksum does not match declared checksum";
729
                failure = new ServiceFailure("2151", msg);
730
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
731
                throw new ServiceFailure("2151", msg);
732
            }
733
        }
734

    
735
        // add it to local store
736
        Identifier retPid;
737
        try {
738
            // skip the MN.create -- this mutates the system metadata and we don't want it to
739
            if ( localId == null ) {
740
                // TODO: this will fail if we already "know" about the identifier
741
            	// FIXME: see https://redmine.dataone.org/issues/2572
742
                retPid = super.create(session, pid, object, sysmeta);
743
                result = (retPid.getValue().equals(pid.getValue()));
744
            }
745
            
746
        } catch (Exception e) {
747
            String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
748
            failure = new ServiceFailure("2151", msg);
749
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
750
            logMetacat.error(msg);
751
            throw new ServiceFailure("2151", msg);
752
            
753
        }
754

    
755
        // finish by setting the replication status
756
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
757
        return result;
758

    
759
    }
760

    
761
    /**
762
     * Return the object identified by the given object identifier
763
     * 
764
     * @param session - the Session object containing the credentials for the Subject
765
     * @param pid - the object identifier for the given object
766
     * 
767
     * @return inputStream - the input stream of the given object
768
     * 
769
     * @throws InvalidToken
770
     * @throws ServiceFailure
771
     * @throws NotAuthorized
772
     * @throws InvalidRequest
773
     * @throws NotImplemented
774
     */
775
    @Override
776
    public InputStream get(Session session, Identifier pid) 
777
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
778

    
779
        return super.get(session, pid);
780

    
781
    }
782

    
783
    /**
784
     * Returns a Checksum for the specified object using an accepted hashing algorithm
785
     * 
786
     * @param session - the Session object containing the credentials for the Subject
787
     * @param pid - the object identifier for the given object
788
     * @param algorithm -  the name of an algorithm that will be used to compute 
789
     *                     a checksum of the bytes of the object
790
     * 
791
     * @return checksum - the checksum of the given object
792
     * 
793
     * @throws InvalidToken
794
     * @throws ServiceFailure
795
     * @throws NotAuthorized
796
     * @throws NotFound
797
     * @throws InvalidRequest
798
     * @throws NotImplemented
799
     */
800
    @Override
801
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
802
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
803
        InvalidRequest, NotImplemented {
804

    
805
        Checksum checksum = null;
806
        String serviceFailure = "1410";
807
        String notFound = "1420";
808
        //Checkum only handles the pid, not sid
809
        checkV1SystemMetaPidExist(pid, serviceFailure, "The checksum for the object specified by "+pid.getValue()+" couldn't be returned ",  notFound, 
810
                "The object specified by "+pid.getValue()+" does not exist at this node.");
811
        InputStream inputStream = get(session, pid);
812

    
813
        try {
814
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
815

    
816
        } catch (NoSuchAlgorithmException e) {
817
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
818
                    + e.getMessage());
819
        } catch (IOException e) {
820
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
821
                    + e.getMessage());
822
        }
823

    
824
        if (checksum == null) {
825
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
826
        }
827

    
828
        return checksum;
829
    }
830

    
831
    /**
832
     * Return the system metadata for a given object
833
     * 
834
     * @param session - the Session object containing the credentials for the Subject
835
     * @param pid - the object identifier for the given object
836
     * 
837
     * @return inputStream - the input stream of the given system metadata object
838
     * 
839
     * @throws InvalidToken
840
     * @throws ServiceFailure
841
     * @throws NotAuthorized
842
     * @throws NotFound
843
     * @throws InvalidRequest
844
     * @throws NotImplemented
845
     */
846
    @Override
847
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) 
848
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
849
        NotImplemented {
850

    
851
        return super.getSystemMetadata(session, pid);
852
    }
853

    
854
    /**
855
     * Retrieve the list of objects present on the MN that match the calling parameters
856
     * 
857
     * @param session - the Session object containing the credentials for the Subject
858
     * @param startTime - Specifies the beginning of the time range from which 
859
     *                    to return object (>=)
860
     * @param endTime - Specifies the beginning of the time range from which 
861
     *                  to return object (>=)
862
     * @param objectFormat - Restrict results to the specified object format
863
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
864
     * @param start - The zero-based index of the first value, relative to the 
865
     *                first record of the resultset that matches the parameters.
866
     * @param count - The maximum number of entries that should be returned in 
867
     *                the response. The Member Node may return less entries 
868
     *                than specified in this value.
869
     * 
870
     * @return objectList - the list of objects matching the criteria
871
     * 
872
     * @throws InvalidToken
873
     * @throws ServiceFailure
874
     * @throws NotAuthorized
875
     * @throws InvalidRequest
876
     * @throws NotImplemented
877
     */
878
    @Override
879
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Identifier identifier, Boolean replicaStatus, Integer start,
880
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
881

    
882
        ObjectList objectList = null;
883

    
884
        try {
885
        	// safeguard against large requests
886
            if (count == null || count > MAXIMUM_DB_RECORD_COUNT) {
887
            	count = MAXIMUM_DB_RECORD_COUNT;
888
            }
889
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
890
        } catch (Exception e) {
891
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
892
        }
893

    
894
        return objectList;
895
    }
896

    
897
    /**
898
     * Return a description of the node's capabilities and services.
899
     * 
900
     * @return node - the technical capabilities of the Member Node
901
     * 
902
     * @throws ServiceFailure
903
     * @throws NotAuthorized
904
     * @throws InvalidRequest
905
     * @throws NotImplemented
906
     */
907
    @Override
908
    public Node getCapabilities() 
909
        throws NotImplemented, ServiceFailure {
910

    
911
        String nodeName = null;
912
        String nodeId = null;
913
        String subject = null;
914
        String contactSubject = null;
915
        String nodeDesc = null;
916
        String nodeTypeString = null;
917
        NodeType nodeType = null;
918
        String mnCoreServiceVersion = null;
919
        String mnReadServiceVersion = null;
920
        String mnAuthorizationServiceVersion = null;
921
        String mnStorageServiceVersion = null;
922
        String mnReplicationServiceVersion = null;
923

    
924
        boolean nodeSynchronize = false;
925
        boolean nodeReplicate = false;
926
        boolean mnCoreServiceAvailable = false;
927
        boolean mnReadServiceAvailable = false;
928
        boolean mnAuthorizationServiceAvailable = false;
929
        boolean mnStorageServiceAvailable = false;
930
        boolean mnReplicationServiceAvailable = false;
931

    
932
        try {
933
            // get the properties of the node based on configuration information
934
            nodeName = PropertyService.getProperty("dataone.nodeName");
935
            nodeId = PropertyService.getProperty("dataone.nodeId");
936
            subject = PropertyService.getProperty("dataone.subject");
937
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
938
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
939
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
940
            nodeType = NodeType.convert(nodeTypeString);
941
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
942
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
943

    
944
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
945
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
946
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
947
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
948
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
949

    
950
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
951
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
952
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
953
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
954
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
955

    
956
            // Set the properties of the node based on configuration information and
957
            // calls to current status methods
958
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
959
            Node node = new Node();
960
            node.setBaseURL(serviceName + "/" + nodeTypeString);
961
            node.setDescription(nodeDesc);
962

    
963
            // set the node's health information
964
            node.setState(NodeState.UP);
965
            
966
            // set the ping response to the current value
967
            Ping canPing = new Ping();
968
            canPing.setSuccess(false);
969
            try {
970
            	Date pingDate = ping();
971
                canPing.setSuccess(pingDate != null);
972
            } catch (BaseException e) {
973
                e.printStackTrace();
974
                // guess it can't be pinged
975
            }
976
            
977
            node.setPing(canPing);
978

    
979
            NodeReference identifier = new NodeReference();
980
            identifier.setValue(nodeId);
981
            node.setIdentifier(identifier);
982
            Subject s = new Subject();
983
            s.setValue(subject);
984
            node.addSubject(s);
985
            Subject contact = new Subject();
986
            contact.setValue(contactSubject);
987
            node.addContactSubject(contact);
988
            node.setName(nodeName);
989
            node.setReplicate(nodeReplicate);
990
            node.setSynchronize(nodeSynchronize);
991

    
992
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
993
            Services services = new Services();
994

    
995
            Service sMNCore = new Service();
996
            sMNCore.setName("MNCore");
997
            sMNCore.setVersion(mnCoreServiceVersion);
998
            sMNCore.setAvailable(mnCoreServiceAvailable);
999

    
1000
            Service sMNRead = new Service();
1001
            sMNRead.setName("MNRead");
1002
            sMNRead.setVersion(mnReadServiceVersion);
1003
            sMNRead.setAvailable(mnReadServiceAvailable);
1004

    
1005
            Service sMNAuthorization = new Service();
1006
            sMNAuthorization.setName("MNAuthorization");
1007
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
1008
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
1009

    
1010
            Service sMNStorage = new Service();
1011
            sMNStorage.setName("MNStorage");
1012
            sMNStorage.setVersion(mnStorageServiceVersion);
1013
            sMNStorage.setAvailable(mnStorageServiceAvailable);
1014

    
1015
            Service sMNReplication = new Service();
1016
            sMNReplication.setName("MNReplication");
1017
            sMNReplication.setVersion(mnReplicationServiceVersion);
1018
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
1019

    
1020
            services.addService(sMNRead);
1021
            services.addService(sMNCore);
1022
            services.addService(sMNAuthorization);
1023
            services.addService(sMNStorage);
1024
            services.addService(sMNReplication);
1025
            node.setServices(services);
1026

    
1027
            // Set the schedule for synchronization
1028
            Synchronization synchronization = new Synchronization();
1029
            Schedule schedule = new Schedule();
1030
            Date now = new Date();
1031
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
1032
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
1033
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
1034
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
1035
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
1036
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
1037
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
1038
            synchronization.setSchedule(schedule);
1039
            synchronization.setLastHarvested(now);
1040
            synchronization.setLastCompleteHarvest(now);
1041
            node.setSynchronization(synchronization);
1042

    
1043
            node.setType(nodeType);
1044
            return node;
1045

    
1046
        } catch (PropertyNotFoundException pnfe) {
1047
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
1048
            logMetacat.error(msg);
1049
            throw new ServiceFailure("2162", msg);
1050
        }
1051
    }
1052

    
1053
    
1054

    
1055
    /**
1056
     * A callback method used by a CN to indicate to a MN that it cannot 
1057
     * complete synchronization of the science metadata identified by pid.  Log
1058
     * the event in the metacat event log.
1059
     * 
1060
     * @param session
1061
     * @param syncFailed
1062
     * 
1063
     * @throws ServiceFailure
1064
     * @throws NotAuthorized
1065
     * @throws NotImplemented
1066
     */
1067
    @Override
1068
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
1069
        throws NotImplemented, ServiceFailure, NotAuthorized {
1070

    
1071
        String localId;
1072
        Identifier pid;
1073
        if ( syncFailed.getPid() != null ) {
1074
            pid = new Identifier();
1075
            pid.setValue(syncFailed.getPid());
1076
            boolean allowed;
1077
            
1078
            //are we allowed? only CNs
1079
            try {
1080
                allowed = isAdminAuthorized(session);
1081
                if ( !allowed ){
1082
                    throw new NotAuthorized("2162", 
1083
                            "Not allowed to call synchronizationFailed() on this node.");
1084
                }
1085
            } catch (InvalidToken e) {
1086
                throw new NotAuthorized("2162", 
1087
                        "Not allowed to call synchronizationFailed() on this node.");
1088

    
1089
            }
1090
            
1091
        } else {
1092
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1093

    
1094
        }
1095
        
1096
        try {
1097
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1098
        } catch (McdbDocNotFoundException e) {
1099
            throw new ServiceFailure("2161", "The identifier specified by " + 
1100
                    syncFailed.getPid() + " was not found on this node.");
1101

    
1102
        } catch (SQLException e) {
1103
            throw new ServiceFailure("2161", "Couldn't identify the local id of the identifier specified by " + 
1104
                    syncFailed.getPid() + " since "+e.getMessage());
1105
        }
1106
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1107
        // method is changed to include the URL as a parameter
1108
        logMetacat.debug("Synchronization for the object identified by " + 
1109
                pid.getValue() + " failed from " + syncFailed.getNodeId() + 
1110
                " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
1111
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1112
        String principal = Constants.SUBJECT_PUBLIC;
1113
        if (session != null && session.getSubject() != null) {
1114
          principal = session.getSubject().getValue();
1115
        }
1116
        try {
1117
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1118
        } catch (Exception e) {
1119
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1120
        }
1121
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
1122
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1123
        return true;
1124

    
1125
    }
1126

    
1127
    /**
1128
     * Essentially a get() but with different logging behavior
1129
     */
1130
    @Override
1131
    public InputStream getReplica(Session session, Identifier pid) 
1132
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1133

    
1134
        logMetacat.info("MNodeService.getReplica() called.");
1135

    
1136
        // cannot be called by public
1137
        if (session == null) {
1138
        	throw new InvalidToken("2183", "No session was provided.");
1139
        }
1140
        
1141
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1142
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1143
             "\tIdentifier           = " + pid.getValue());
1144

    
1145
        InputStream inputStream = null; // bytes to be returned
1146
        handler = new MetacatHandler(new Timer());
1147
        boolean allowed = false;
1148
        String localId; // the metacat docid for the pid
1149

    
1150
        // get the local docid from Metacat
1151
        try {
1152
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1153
        } catch (McdbDocNotFoundException e) {
1154
            throw new ServiceFailure("2181", "The object specified by " + 
1155
                    pid.getValue() + " does not exist at this node.");
1156
            
1157
        } catch (SQLException e) {
1158
            throw new ServiceFailure("2181", "The local id of the object specified by " + 
1159
                    pid.getValue() + " couldn't be identified since "+e.getMessage());
1160
        }
1161

    
1162
        Subject targetNodeSubject = session.getSubject();
1163

    
1164
        // check for authorization to replicate, null session to act as this source MN
1165
        try {
1166
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1167
        } catch (InvalidToken e1) {
1168
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1169
                + e1.getMessage());
1170
            
1171
        } catch (NotFound e1) {
1172
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1173
                    + e1.getMessage());
1174

    
1175
        } catch (InvalidRequest e1) {
1176
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1177
                    + e1.getMessage());
1178

    
1179
        }
1180

    
1181
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1182
            " for identifier " + pid.getValue());
1183

    
1184
        // if the person is authorized, perform the read
1185
        if (allowed) {
1186
            try {
1187
                inputStream = MetacatHandler.read(localId);
1188
            } catch (Exception e) {
1189
                throw new ServiceFailure("1020", "The object specified by " + 
1190
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1191
            }
1192
        }
1193

    
1194
        // if we fail to set the input stream
1195
        if (inputStream == null) {
1196
            throw new ServiceFailure("2181", "The object specified by " + 
1197
                pid.getValue() + "does not exist at this node.");
1198
        }
1199

    
1200
        // log the replica event
1201
        String principal = null;
1202
        if (session.getSubject() != null) {
1203
            principal = session.getSubject().getValue();
1204
        }
1205
        EventLog.getInstance().log(request.getRemoteAddr(), 
1206
            request.getHeader("User-Agent"), principal, localId, "replicate");
1207

    
1208
        return inputStream;
1209
    }
1210

    
1211
    /**
1212
     * A method to notify the Member Node that the authoritative copy of 
1213
     * system metadata on the Coordinating Nodes has changed.
1214
     * 
1215
     * @param session   Session information that contains the identity of the 
1216
     *                  calling user as retrieved from the X.509 certificate 
1217
     *                  which must be traceable to the CILogon service.
1218
     * @param serialVersion   The serialVersion of the system metadata
1219
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1220
     * @throws NotImplemented
1221
     * @throws ServiceFailure
1222
     * @throws NotAuthorized
1223
     * @throws InvalidRequest
1224
     * @throws InvalidToken
1225
     */
1226
    public boolean systemMetadataChanged(Session session, Identifier pid,
1227
        long serialVersion, Date dateSysMetaLastModified) 
1228
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1229
        InvalidToken {
1230
        
1231
        // cannot be called by public
1232
        if (session == null) {
1233
        	throw new InvalidToken("2183", "No session was provided.");
1234
        }
1235

    
1236
        SystemMetadata currentLocalSysMeta = null;
1237
        SystemMetadata newSysMeta = null;
1238
        CNode cn = D1Client.getCN();
1239
        NodeList nodeList = null;
1240
        Subject callingSubject = null;
1241
        boolean allowed = false;
1242
        
1243
        // are we allowed to call this?
1244
        callingSubject = session.getSubject();
1245
        nodeList = cn.listNodes();
1246
        
1247
        for(Node node : nodeList.getNodeList()) {
1248
            // must be a CN
1249
            if ( node.getType().equals(NodeType.CN)) {
1250
               List<Subject> subjectList = node.getSubjectList();
1251
               // the calling subject must be in the subject list
1252
               if ( subjectList.contains(callingSubject)) {
1253
                   allowed = true;
1254
                   
1255
               }
1256
               
1257
            }
1258
        }
1259
        
1260
        if (!allowed ) {
1261
            String msg = "The subject identified by " + callingSubject.getValue() +
1262
              " is not authorized to call this service.";
1263
            throw new NotAuthorized("1331", msg);
1264
            
1265
        }
1266
        
1267
        // compare what we have locally to what is sent in the change notification
1268
        try {
1269
            currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1270
             
1271
        } catch (RuntimeException e) {
1272
            String msg = "SystemMetadata for pid " + pid.getValue() +
1273
              " couldn't be updated because it couldn't be found locally: " +
1274
              e.getMessage();
1275
            logMetacat.error(msg);
1276
            ServiceFailure sf = new ServiceFailure("1333", msg);
1277
            sf.initCause(e);
1278
            throw sf; 
1279
        }
1280
        
1281
        if (currentLocalSysMeta.getSerialVersion().longValue() < serialVersion ) {
1282
            try {
1283
                newSysMeta = cn.getSystemMetadata(null, pid);
1284
            } catch (NotFound e) {
1285
                // huh? you just said you had it
1286
            	String msg = "On updating the local copy of system metadata " + 
1287
                "for pid " + pid.getValue() +", the CN reports it is not found." +
1288
                " The error message was: " + e.getMessage();
1289
                logMetacat.error(msg);
1290
                ServiceFailure sf = new ServiceFailure("1333", msg);
1291
                sf.initCause(e);
1292
                throw sf;
1293
            }
1294
            
1295
            // update the local copy of system metadata for the pid
1296
            try {
1297
                HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1298
                logMetacat.info("Updated local copy of system metadata for pid " +
1299
                    pid.getValue() + " after change notification from the CN.");
1300
                
1301
                // TODO: consider inspecting the change for archive
1302
                // see: https://projects.ecoinformatics.org/ecoinfo/issues/6417
1303
//                if (newSysMeta.getArchived() != null && newSysMeta.getArchived().booleanValue()) {
1304
//                	try {
1305
//						this.archive(session, newSysMeta.getIdentifier());
1306
//					} catch (NotFound e) {
1307
//						// do we care? nothing to do about it now
1308
//						logMetacat.error(e.getMessage(), e);
1309
//					}
1310
//                }
1311
                
1312
            } catch (RuntimeException e) {
1313
                String msg = "SystemMetadata for pid " + pid.getValue() +
1314
                  " couldn't be updated: " +
1315
                  e.getMessage();
1316
                logMetacat.error(msg);
1317
                ServiceFailure sf = new ServiceFailure("1333", msg);
1318
                sf.initCause(e);
1319
                throw sf;
1320
            }
1321
            
1322
            // submit for indexing
1323
            try {
1324
				MetacatSolrIndex.getInstance().submit(newSysMeta.getIdentifier(), newSysMeta, null, true);
1325
			} catch (Exception e) {
1326
                logMetacat.error("Could not submit changed systemMetadata for indexing, pid: " + newSysMeta.getIdentifier().getValue(), e);
1327
			}
1328
        }
1329
        
1330
        return true;
1331
        
1332
    }
1333
    
1334
    /*
1335
     * Set the replication status for the object on the Coordinating Node
1336
     * 
1337
     * @param session - the session for the this target node
1338
     * @param pid - the identifier of the object being updated
1339
     * @param nodeId - the identifier of this target node
1340
     * @param status - the replication status to set
1341
     * @param failure - the exception to include, if any
1342
     */
1343
    private void setReplicationStatus(Session session, Identifier pid, 
1344
        NodeReference nodeId, ReplicationStatus status, BaseException failure) 
1345
        throws ServiceFailure, NotImplemented, NotAuthorized, 
1346
        InvalidRequest {
1347
        
1348
        // call the CN as the MN to set the replication status
1349
        try {
1350
            this.cn = D1Client.getCN();
1351
            this.cn.setReplicationStatus(session, pid, nodeId,
1352
                    status, failure);
1353
            
1354
        } catch (InvalidToken e) {
1355
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (InvalidToken): " + e.getMessage();
1356
            logMetacat.error(msg);
1357
        	throw new ServiceFailure("2151",
1358
                    msg);
1359
            
1360
        } catch (NotFound e) {
1361
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (NotFound): " + e.getMessage();
1362
            logMetacat.error(msg);
1363
        	throw new ServiceFailure("2151",
1364
                    msg);
1365
            
1366
        }
1367
    }
1368
    
1369
    private SystemMetadata makePublicIfNot(SystemMetadata sysmeta, Identifier pid) throws ServiceFailure, InvalidToken, NotFound, NotImplemented, InvalidRequest {
1370
    	// check if it is publicly readable
1371
		boolean isPublic = false;
1372
		Subject publicSubject = new Subject();
1373
		publicSubject.setValue(Constants.SUBJECT_PUBLIC);
1374
		Session publicSession = new Session();
1375
		publicSession.setSubject(publicSubject);
1376
		AccessRule publicRule = new AccessRule();
1377
		publicRule.addPermission(Permission.READ);
1378
		publicRule.addSubject(publicSubject);
1379
		
1380
		// see if we need to add the rule
1381
		try {
1382
			isPublic = this.isAuthorized(publicSession, pid, Permission.READ);
1383
		} catch (NotAuthorized na) {
1384
			// well, certainly not authorized for public read!
1385
		}
1386
		if (!isPublic) {
1387
			sysmeta.getAccessPolicy().addAllow(publicRule);
1388
		}
1389
		
1390
		return sysmeta;
1391
    }
1392

    
1393
	@Override
1394
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1395
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1396
			InvalidRequest {
1397
		
1398
		// check for null session
1399
        if (session == null) {
1400
          throw new InvalidToken("2190", "Session is required to generate an Identifier at this Node.");
1401
        }
1402
		
1403
		Identifier identifier = new Identifier();
1404
		
1405
		// handle different schemes
1406
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1407
			// UUID
1408
			UUID uuid = UUID.randomUUID();
1409
            identifier.setValue(UUID_PREFIX + uuid.toString());
1410
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1411
			// generate a DOI
1412
			try {
1413
				identifier = DOIService.getInstance().generateDOI();
1414
			} catch (EZIDException e) {
1415
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1416
				sf.initCause(e);
1417
				throw sf;
1418
			}
1419
		} else {
1420
			// default if we don't know the scheme
1421
			if (fragment != null) {
1422
				// for now, just autogen with fragment
1423
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1424
				identifier.setValue(autogenId);			
1425
			} else {
1426
				// autogen with no fragment
1427
				String autogenId = DocumentUtil.generateDocumentId(0);
1428
				identifier.setValue(autogenId);
1429
			}
1430
		}
1431
		
1432
		// TODO: reserve the identifier with the CN. We can only do this when
1433
		// 1) the MN is part of a CN cluster
1434
		// 2) the request is from an authenticated user
1435
		
1436
		return identifier;
1437
	}
1438

    
1439
	
1440

    
1441
	@Override
1442
	public QueryEngineDescription getQueryEngineDescription(Session session, String engine)
1443
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1444
			NotFound {
1445
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1446
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1447
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1448
            }
1449
	        QueryEngineDescription qed = new QueryEngineDescription();
1450
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1451
	        qed.setQueryEngineVersion("1.0");
1452
	        qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1453
	        Vector<String> pathsForIndexing = null;
1454
	        try {
1455
	            pathsForIndexing = SystemUtil.getPathsForIndexing();
1456
	        } catch (MetacatUtilException e) {
1457
	            logMetacat.warn("Could not get index paths", e);
1458
	        }
1459
	        for (String fieldName: pathsForIndexing) {
1460
	            QueryField field = new QueryField();
1461
	            field.addDescription("Indexed field for path '" + fieldName + "'");
1462
	            field.setName(fieldName);
1463
	            field.setReturnable(true);
1464
	            field.setSearchable(true);
1465
	            field.setSortable(false);
1466
	            // TODO: determine type and multivaluedness
1467
	            field.setType(String.class.getName());
1468
	            //field.setMultivalued(true);
1469
	            qed.addQueryField(field);
1470
	        }
1471
	        return qed;
1472
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1473
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1474
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1475
            }
1476
	        try {
1477
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1478
	            return qed;
1479
	        } catch (Exception e) {
1480
	            e.printStackTrace();
1481
	            throw new ServiceFailure("Solr server error", e.getMessage());
1482
	        }
1483
	    } else {
1484
	        throw new NotFound("404", "The Metacat member node can't find the query engine - "+engine);
1485
	    }
1486
		
1487
	}
1488

    
1489
	@Override
1490
	public QueryEngineList listQueryEngines(Session session) throws InvalidToken,
1491
			ServiceFailure, NotAuthorized, NotImplemented {
1492
		QueryEngineList qel = new QueryEngineList();
1493
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1494
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1495
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1496
		for(String name : enables) {
1497
		    qel.addQueryEngine(name);
1498
		}
1499
		return qel;
1500
	}
1501

    
1502
	@Override
1503
	public InputStream query(Session session, String engine, String query) throws InvalidToken,
1504
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1505
			NotFound {
1506
	    String user = Constants.SUBJECT_PUBLIC;
1507
        String[] groups= null;
1508
        Set<Subject> subjects = null;
1509
        if (session != null) {
1510
            user = session.getSubject().getValue();
1511
            subjects = AuthUtils.authorizedClientSubjects(session);
1512
            if (subjects != null) {
1513
                List<String> groupList = new ArrayList<String>();
1514
                for (Subject subject: subjects) {
1515
                    groupList.add(subject.getValue());
1516
                }
1517
                groups = groupList.toArray(new String[0]);
1518
            }
1519
        } else {
1520
            //add the public user subject to the set 
1521
            Subject subject = new Subject();
1522
            subject.setValue(Constants.SUBJECT_PUBLIC);
1523
            subjects = new HashSet<Subject>();
1524
            subjects.add(subject);
1525
        }
1526
        //System.out.println("====== user is "+user);
1527
        //System.out.println("====== groups are "+groups);
1528
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1529
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1530
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1531
            }
1532
			try {
1533
				DBQuery queryobj = new DBQuery();
1534
				
1535
				String results = queryobj.performPathquery(query, user, groups);
1536
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1537
				ctbais.setContentType("text/xml");
1538
				return ctbais;
1539

    
1540
			} catch (Exception e) {
1541
				throw new ServiceFailure("Pathquery error", e.getMessage());
1542
			}
1543
			
1544
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1545
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1546
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1547
		    }
1548
		    logMetacat.info("The query is ==================================== \n"+query);
1549
		    try {
1550
		        
1551
                return MetacatSolrIndex.getInstance().query(query, subjects);
1552
            } catch (Exception e) {
1553
                // TODO Auto-generated catch block
1554
                throw new ServiceFailure("Solr server error", e.getMessage());
1555
            } 
1556
		}
1557
		return null;
1558
	}
1559
	
1560
	/**
1561
	 * Given an existing Science Metadata PID, this method mints a DOI
1562
	 * and updates the original object "publishing" the update with the DOI.
1563
	 * This includes updating the ORE map that describes the Science Metadata+data.
1564
	 * TODO: ensure all referenced objects allow public read
1565
	 * 
1566
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1567
	 * 
1568
	 * @param originalIdentifier
1569
	 * @param request
1570
	 * @throws InvalidRequest 
1571
	 * @throws NotImplemented 
1572
	 * @throws NotAuthorized 
1573
	 * @throws ServiceFailure 
1574
	 * @throws InvalidToken 
1575
	 * @throws NotFound
1576
	 * @throws InvalidSystemMetadata 
1577
	 * @throws InsufficientResources 
1578
	 * @throws UnsupportedType 
1579
	 * @throws IdentifierNotUnique 
1580
	 */
1581
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata {
1582
		
1583
		
1584
		// get the original SM
1585
		SystemMetadata originalSystemMetadata = this.getSystemMetadata(session, originalIdentifier);
1586

    
1587
		// make copy of it using the marshaller to ensure DEEP copy
1588
		SystemMetadata sysmeta = null;
1589
		try {
1590
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
1591
			TypeMarshaller.marshalTypeToOutputStream(originalSystemMetadata, baos);
1592
			sysmeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1593
		} catch (Exception e) {
1594
			// report as service failure
1595
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1596
			sf.initCause(e);
1597
			throw sf;
1598
		}
1599

    
1600
		// mint a DOI for the new revision
1601
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1602
				
1603
		// set new metadata values
1604
		sysmeta.setIdentifier(newIdentifier);
1605
		sysmeta.setObsoletes(originalIdentifier);
1606
		sysmeta.setObsoletedBy(null);
1607
		
1608
		// ensure it is publicly readable
1609
		sysmeta = makePublicIfNot(sysmeta, originalIdentifier);
1610
		
1611
		// get the bytes
1612
		InputStream inputStream = this.get(session, originalIdentifier);
1613
		
1614
		// update the object
1615
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
1616
		
1617
		// update ORE that references the scimeta
1618
		// first try the naive method, then check the SOLR index
1619
		try {
1620
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1621
			
1622
			Identifier potentialOreIdentifier = new Identifier();
1623
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1624
			
1625
			InputStream oreInputStream = null;
1626
			try {
1627
				oreInputStream = this.get(session, potentialOreIdentifier);
1628
			} catch (NotFound nf) {
1629
				// this is probably okay for many sci meta data docs
1630
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1631
				// try the SOLR index
1632
				List<Identifier> potentialOreIdentifiers = this.lookupOreFor(originalIdentifier, false);
1633
				if (potentialOreIdentifiers != null) {
1634
					potentialOreIdentifier = potentialOreIdentifiers.get(0);
1635
					try {
1636
						oreInputStream = this.get(session, potentialOreIdentifier);
1637
					} catch (NotFound nf2) {
1638
						// this is probably okay for many sci meta data docs
1639
						logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1640
					}
1641
				}
1642
			}
1643
			if (oreInputStream != null) {
1644
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1645
	
1646
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1647
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1648
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1649
					
1650
				// reconstruct the ORE with the new identifiers
1651
				sciMetaMap.remove(originalIdentifier);
1652
				sciMetaMap.put(newIdentifier, dataIdentifiers);
1653
				
1654
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1655
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1656
				
1657
				// get the original ORE SM and update the values
1658
				SystemMetadata originalOreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1659
				SystemMetadata oreSysMeta = new SystemMetadata();
1660
				try {
1661
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
1662
					TypeMarshaller.marshalTypeToOutputStream(originalOreSysMeta, baos);
1663
					oreSysMeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1664
				} catch (Exception e) {
1665
					// report as service failure
1666
					ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1667
					sf.initCause(e);
1668
					throw sf;
1669
				}
1670

    
1671
				oreSysMeta.setIdentifier(newOreIdentifier);
1672
				oreSysMeta.setObsoletes(potentialOreIdentifier);
1673
				oreSysMeta.setObsoletedBy(null);
1674
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
1675
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
1676
				
1677
				// ensure ORE is publicly readable
1678
				oreSysMeta = makePublicIfNot(oreSysMeta, potentialOreIdentifier);
1679
				
1680
				// ensure all data objects allow public read
1681
				List<String> pidsToSync = new ArrayList<String>();
1682
				for (Identifier dataId: dataIdentifiers) {
1683
					SystemMetadata dataSysMeta = this.getSystemMetadata(session, dataId);
1684
					dataSysMeta = makePublicIfNot(dataSysMeta, dataId);
1685
					this.updateSystemMetadata(dataSysMeta);
1686
					pidsToSync.add(dataId.getValue());
1687
				}
1688
				SyncAccessPolicy sap = new SyncAccessPolicy();
1689
				try {
1690
					sap.sync(pidsToSync);
1691
				} catch (Exception e) {
1692
					// ignore
1693
					logMetacat.warn("Error attempting to sync access for data objects when publishing package");
1694
				}
1695
				
1696
				// save the updated ORE
1697
				this.update(
1698
						session, 
1699
						potentialOreIdentifier, 
1700
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")), 
1701
						newOreIdentifier, 
1702
						oreSysMeta);
1703
				
1704
			} else {
1705
				// create a new ORE for them
1706
				// https://projects.ecoinformatics.org/ecoinfo/issues/6194
1707
				try {
1708
					// find the local id for the NEW package.
1709
					String newLocalId = IdentifierManager.getInstance().getLocalId(newIdentifier.getValue());
1710
	
1711
					@SuppressWarnings("unused")
1712
					SystemMetadata extraSysMeta = SystemMetadataFactory.createSystemMetadata(newLocalId, true, false);
1713
					// should be done generating the ORE here, and the same permissions were used from the metadata object
1714
					
1715
				} catch (Exception e) {
1716
					// oops, guess there was a problem - no package for you
1717
					logMetacat.error("Could not generate new ORE for published object: " + newIdentifier.getValue(), e);
1718
				}
1719
			}
1720
		} catch (McdbDocNotFoundException e) {
1721
			// report as service failure
1722
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1723
			sf.initCause(e);
1724
			throw sf;
1725
		} catch (UnsupportedEncodingException e) {
1726
			// report as service failure
1727
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1728
			sf.initCause(e);
1729
			throw sf;
1730
		} catch (OREException e) {
1731
			// report as service failure
1732
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1733
			sf.initCause(e);
1734
			throw sf;
1735
		} catch (URISyntaxException e) {
1736
			// report as service failure
1737
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1738
			sf.initCause(e);
1739
			throw sf;
1740
		} catch (OREParserException e) {
1741
			// report as service failure
1742
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1743
			sf.initCause(e);
1744
			throw sf;
1745
		} catch (ORESerialiserException e) {
1746
			// report as service failure
1747
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1748
			sf.initCause(e);
1749
			throw sf;
1750
		} catch (NoSuchAlgorithmException e) {
1751
			// report as service failure
1752
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1753
			sf.initCause(e);
1754
			throw sf;
1755
		} catch (SQLException e) {
1756
            // report as service failure
1757
            ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1758
            sf.initCause(e);
1759
            throw sf;
1760
        }
1761
		
1762
		return newIdentifier;
1763
	}
1764
	
1765
	/**
1766
	 * Determines if we already have registered an ORE map for this package
1767
	 * NOTE: uses a solr query to locate OREs for the object
1768
	 * @param guid of the EML/packaging object
1769
	 * @return list of resource map identifiers for the given pid
1770
	 */
1771
	public List<Identifier> lookupOreFor(Identifier guid, boolean includeObsolete) {
1772
		// Search for the ORE if we can find it
1773
		String pid = guid.getValue();
1774
		List<Identifier> retList = null;
1775
		try {
1776
			String query = "fl=id,resourceMap&wt=xml&q=-obsoletedBy:[* TO *]+resourceMap:[* TO *]+id:\"" + pid + "\"";
1777
			if (includeObsolete) {
1778
				query = "fl=id,resourceMap&wt=xml&q=resourceMap:[* TO *]+id:\"" + pid + "\"";
1779
			}
1780
			
1781
			InputStream results = this.query(null, "solr", query);
1782
			org.w3c.dom.Node rootNode = XMLUtilities.getXMLReaderAsDOMTreeRootNode(new InputStreamReader(results, "UTF-8"));
1783
			//String resultString = XMLUtilities.getDOMTreeAsString(rootNode);
1784
			org.w3c.dom.NodeList nodeList = XMLUtilities.getNodeListWithXPath(rootNode, "//arr[@name=\"resourceMap\"]/str");
1785
			if (nodeList != null && nodeList.getLength() > 0) {
1786
				retList = new ArrayList<Identifier>();
1787
				for (int i = 0; i < nodeList.getLength(); i++) {
1788
					String found = nodeList.item(i).getFirstChild().getNodeValue();
1789
					Identifier oreId = new Identifier();
1790
					oreId.setValue(found);
1791
					retList.add(oreId);
1792
				}
1793
			}
1794
		} catch (Exception e) {
1795
			logMetacat.error("Error checking for resourceMap[s] on pid " + pid + ". " + e.getMessage(), e);
1796
		}
1797
		
1798
		return retList;
1799
	}
1800
	
1801

    
1802
	@Override
1803
	public InputStream getPackage(Session session, ObjectFormatIdentifier formatId,
1804
			Identifier pid) throws InvalidToken, ServiceFailure,
1805
			NotAuthorized, InvalidRequest, NotImplemented, NotFound {
1806
		InputStream bagInputStream = null;
1807
		BagFactory bagFactory = new BagFactory();
1808
		Bag bag = bagFactory.createBag();
1809
		
1810
		// track the temp files we use so we can delete them when finished
1811
		List<File> tempFiles = new ArrayList<File>();
1812
		
1813
		// the pids to include in the package
1814
		List<Identifier> packagePids = new ArrayList<Identifier>();
1815
		
1816
		// catch non-D1 service errors and throw as ServiceFailures
1817
		try {
1818
			//Create a map of dataone ids and file names
1819
			Map<Identifier, String> fileNames = new HashMap<Identifier, String>();
1820
			
1821
			// track the pid-to-file mapping
1822
			StringBuffer pidMapping = new StringBuffer();
1823
			
1824
			// find the package contents
1825
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1826
			if (ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
1827
				//Get the resource map as a map of Identifiers
1828
				InputStream oreInputStream = this.get(session, pid);
1829
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1830
				packagePids.addAll(resourceMapStructure.keySet());
1831
				//Loop through each object in this resource map
1832
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
1833
					//Loop through each metadata object in this entry
1834
					Set<Identifier> metadataIdentifiers = entries.keySet();
1835
					for(Identifier metadataID: metadataIdentifiers){
1836
						try{
1837
							//Get the system metadata for this metadata object
1838
							SystemMetadata metadataSysMeta = this.getSystemMetadata(session, metadataID);
1839
							
1840
							// include user-friendly metadata
1841
							if (ObjectFormatCache.getInstance().getFormat(metadataSysMeta.getFormatId()).getFormatType().equals("METADATA")) {
1842
								InputStream metadataStream = this.get(session, metadataID);
1843
							
1844
								try {
1845
									// transform
1846
						            String format = "default";
1847

    
1848
									DBTransform transformer = new DBTransform();
1849
						            String documentContent = IOUtils.toString(metadataStream, "UTF-8");
1850
						            String sourceType = metadataSysMeta.getFormatId().getValue();
1851
						            String targetType = "-//W3C//HTML//EN";
1852
						            ByteArrayOutputStream baos = new ByteArrayOutputStream();
1853
						            Writer writer = new OutputStreamWriter(baos , "UTF-8");
1854
						            // TODO: include more params?
1855
						            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
1856
						            String localId = null;
1857
									try {
1858
										localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1859
									} catch (McdbDocNotFoundException e) {
1860
										throw new NotFound("1020", e.getMessage());
1861
									}
1862
									params.put("qformat", new String[] {format});	            
1863
						            params.put("docid", new String[] {localId});
1864
						            params.put("pid", new String[] {pid.getValue()});
1865
						            params.put("displaymodule", new String[] {"printall"});
1866
						            
1867
						            transformer.transformXMLDocument(
1868
						                    documentContent , 
1869
						                    sourceType, 
1870
						                    targetType , 
1871
						                    format, 
1872
						                    writer, 
1873
						                    params, 
1874
						                    null //sessionid
1875
						                    );
1876
						            
1877
						            // finally, get the HTML back
1878
						            ContentTypeByteArrayInputStream resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
1879
						            
1880
						            // write to temp file with correct css path
1881
						            File tmpDir = File.createTempFile("package_", "_dir");
1882
						            tmpDir.delete();
1883
						            tmpDir.mkdir();
1884
						            File htmlFile = File.createTempFile("metadata", ".html", tmpDir);
1885
						            File cssDir = new File(tmpDir, format);
1886
						            cssDir.mkdir();
1887
						            File cssFile = new File(tmpDir, format + "/" + format + ".css");
1888
						            String pdfFileName = metadataID.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-METADATA.pdf";
1889
						            File pdfFile = new File(tmpDir, pdfFileName);
1890
						            //File pdfFile = File.createTempFile("metadata", ".pdf", tmpDir);
1891
						            
1892
						            // put the CSS file in place for the html to find it
1893
						            String originalCssPath = SystemUtil.getContextDir() + "/style/skins/" + format + "/" + format + ".css";
1894
						            IOUtils.copy(new FileInputStream(originalCssPath), new FileOutputStream(cssFile));
1895
						            
1896
						            // write the HTML file
1897
						            IOUtils.copy(resultInputStream, new FileOutputStream(htmlFile));
1898
						            
1899
						            // convert to PDF
1900
						            HtmlToPdf.export(htmlFile.getAbsolutePath(), pdfFile.getAbsolutePath());
1901
						            
1902
						            //add to the package
1903
						            bag.addFileToPayload(pdfFile);
1904
									pidMapping.append(metadataID.getValue() + " (pdf)" +  "\t" + "data/" + pdfFile.getName() + "\n");
1905
						            
1906
						            // mark for clean up after we are done
1907
									htmlFile.delete();
1908
									cssFile.delete();
1909
									cssDir.delete();
1910
						            tempFiles.add(tmpDir);
1911
									tempFiles.add(pdfFile); // delete this first later on
1912
						            
1913
								} catch (Exception e) {
1914
									logMetacat.warn("Could not transform metadata", e);
1915
								}
1916
							}
1917

    
1918
							
1919
							//If this is in eml format, extract the filename and GUID from each entity in its package
1920
							if (metadataSysMeta.getFormatId().getValue().startsWith("eml://")) {
1921
								//Get the package
1922
								DataPackageParserInterface parser = new Eml200DataPackageParser();
1923
								InputStream emlStream = this.get(session, metadataID);
1924
								parser.parse(emlStream);
1925
								DataPackage dataPackage = parser.getDataPackage();
1926
								
1927
								//Get all the entities in this package and loop through each to extract its ID and file name
1928
								Entity[] entities = dataPackage.getEntityList();
1929
								for(Entity entity: entities){
1930
									try{
1931
										//Get the file name from the metadata
1932
										String fileNameFromMetadata = entity.getName();
1933
										
1934
										//Get the ecogrid URL from the metadata
1935
										String ecogridIdentifier = entity.getEntityIdentifier();
1936
										//Parse the ecogrid URL to get the local id
1937
										String idFromMetadata = DocumentUtil.getAccessionNumberFromEcogridIdentifier(ecogridIdentifier);
1938
										
1939
										//Get the docid and rev pair
1940
										String docid = DocumentUtil.getDocIdFromString(idFromMetadata);
1941
										String rev = DocumentUtil.getRevisionStringFromString(idFromMetadata);
1942
										
1943
										//Get the GUID
1944
										String guid = IdentifierManager.getInstance().getGUID(docid, Integer.valueOf(rev));
1945
										Identifier dataIdentifier = new Identifier();
1946
										dataIdentifier.setValue(guid);
1947
										
1948
										//Add the GUID to our GUID & file name map
1949
										fileNames.put(dataIdentifier, fileNameFromMetadata);
1950
									}
1951
									catch(Exception e){
1952
										//Prevent just one entity error
1953
										e.printStackTrace();
1954
										logMetacat.debug(e.getMessage(), e);
1955
									}
1956
								}
1957
							}
1958
						}
1959
						catch(Exception e){
1960
							//Catch errors that would prevent package download
1961
							logMetacat.debug(e.toString());
1962
						}
1963
					}
1964
					packagePids.addAll(entries.keySet());
1965
					for (List<Identifier> dataPids: entries.values()) {
1966
						packagePids.addAll(dataPids);
1967
					}
1968
				}
1969
			} else {
1970
				// just the lone pid in this package
1971
				packagePids.add(pid);
1972
			}
1973
			
1974
			//Create a temp file, then delete it and make a directory with that name
1975
			File tempDir = File.createTempFile("temp", Long.toString(System.nanoTime()));
1976
			tempDir.delete();
1977
			tempDir = new File(tempDir.getPath() + "_dir");
1978
			tempDir.mkdir();			
1979
			tempFiles.add(tempDir);
1980
			File pidMappingFile = new File(tempDir, "pid-mapping.txt");
1981
			
1982
			// loop through the package contents
1983
			for (Identifier entryPid: packagePids) {
1984
				//Get the system metadata for each item
1985
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);					
1986
				
1987
				String objectFormatType = ObjectFormatCache.getInstance().getFormat(entrySysMeta.getFormatId()).getFormatType();
1988
				String fileName = null;
1989
				
1990
				//TODO: Be more specific of what characters to replace. Make sure periods arent replaced for the filename from metadata
1991
				//Our default file name is just the ID + format type (e.g. walker.1.1-DATA)
1992
				fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + objectFormatType;
1993

    
1994
				if(fileNames.containsKey(entryPid)){
1995
					//Let's use the file name and extension from the metadata is we have it
1996
					fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + fileNames.get(entryPid).replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
1997
				}
1998
				else{
1999
					//If we couldn't find a given file name, use the system metadata extension
2000
					String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
2001
					fileName += extension;
2002
				}
2003
				
2004
		        //Create a new file for this item and add to the list
2005
				File tempFile = new File(tempDir, fileName);
2006
				tempFiles.add(tempFile);
2007
				
2008
				InputStream entryInputStream = this.get(session, entryPid);			
2009
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
2010
				bag.addFileToPayload(tempFile);
2011
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
2012
			}
2013
			
2014
			//add the the pid to data file map
2015
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
2016
			bag.addFileAsTag(pidMappingFile);
2017
			tempFiles.add(pidMappingFile);
2018
			
2019
			bag = bag.makeComplete();
2020
			
2021
			///Now create the zip file
2022
			//Use the pid as the file name prefix, replacing all non-word characters
2023
			String zipName = pid.getValue().replaceAll("\\W", "_");
2024
			
2025
			File bagFile = new File(tempDir, zipName+".zip");
2026
			
2027
			bag.setFile(bagFile);
2028
			ZipWriter zipWriter = new ZipWriter(bagFactory);
2029
			bag.write(zipWriter, bagFile);
2030
			bagFile = bag.getFile();
2031
			// use custom FIS that will delete the file when closed
2032
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
2033
			// also mark for deletion on shutdown in case the stream is never closed
2034
			bagFile.deleteOnExit();
2035
			tempFiles.add(bagFile);
2036
			
2037
			// clean up other temp files
2038
			for (int i=tempFiles.size()-1; i>=0; i--){
2039
				tempFiles.get(i).delete();
2040
			}
2041
			
2042
		} catch (IOException e) {
2043
			// report as service failure
2044
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2045
			sf.initCause(e);
2046
			throw sf;
2047
		} catch (OREException e) {
2048
			// report as service failure
2049
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2050
			sf.initCause(e);
2051
			throw sf;
2052
		} catch (URISyntaxException e) {
2053
			// report as service failure
2054
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2055
			sf.initCause(e);
2056
			throw sf;
2057
		} catch (OREParserException e) {
2058
			// report as service failure
2059
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2060
			sf.initCause(e);
2061
			throw sf;
2062
		}
2063
		
2064
		return bagInputStream;
2065
	}
2066

    
2067
	@Override
2068
	public OptionList listViews(Session arg0) throws InvalidToken,
2069
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented {
2070
		OptionList views = new OptionList();
2071
		Vector<String> skinNames = null;
2072
		try {
2073
			skinNames = SkinUtil.getSkinNames();
2074
		} catch (PropertyNotFoundException e) {
2075
			throw new ServiceFailure("2841", e.getMessage());
2076
		}
2077
		for (String skinName: skinNames) {
2078
			views.addOption(skinName);
2079
		}
2080
		return views;
2081
	}
2082

    
2083
	@Override
2084
	public InputStream view(Session session, String format, Identifier pid)
2085
			throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
2086
			NotImplemented, NotFound {
2087
		InputStream resultInputStream = null;
2088
		
2089
		SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2090
		InputStream object = this.get(session, pid);
2091

    
2092
		try {
2093
			// can only transform metadata, really
2094
			ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2095
			if (objectFormat.getFormatType().equals("METADATA")) {
2096
				// transform
2097
				DBTransform transformer = new DBTransform();
2098
	            String documentContent = IOUtils.toString(object, "UTF-8");
2099
	            String sourceType = objectFormat.getFormatId().getValue();
2100
	            String targetType = "-//W3C//HTML//EN";
2101
	            ByteArrayOutputStream baos = new ByteArrayOutputStream();
2102
	            Writer writer = new OutputStreamWriter(baos , "UTF-8");
2103
	            // TODO: include more params?
2104
	            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2105
	            String localId = null;
2106
				try {
2107
					localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2108
				} catch (McdbDocNotFoundException e) {
2109
					throw new NotFound("1020", e.getMessage());
2110
				}
2111
	            params.put("qformat", new String[] {format});	            
2112
	            params.put("docid", new String[] {localId});
2113
	            params.put("pid", new String[] {pid.getValue()});
2114
	            transformer.transformXMLDocument(
2115
	                    documentContent , 
2116
	                    sourceType, 
2117
	                    targetType , 
2118
	                    format, 
2119
	                    writer, 
2120
	                    params, 
2121
	                    null //sessionid
2122
	                    );
2123
	            
2124
	            // finally, get the HTML back
2125
	            resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2126
	            ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2127
	
2128
			} else {
2129
				// just return the raw bytes
2130
				resultInputStream = object;
2131
			}
2132
		} catch (IOException e) {
2133
			// report as service failure
2134
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2135
			sf.initCause(e);
2136
			throw sf;
2137
		} catch (PropertyNotFoundException e) {
2138
			// report as service failure
2139
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2140
			sf.initCause(e);
2141
			throw sf;
2142
		} catch (SQLException e) {
2143
			// report as service failure
2144
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2145
			sf.initCause(e);
2146
			throw sf;
2147
		} catch (ClassNotFoundException e) {
2148
			// report as service failure
2149
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2150
			sf.initCause(e);
2151
			throw sf;
2152
		}
2153
		
2154
		return resultInputStream;
2155
	}	
2156
    
2157
}
(4-4/7)