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
        String serviceFailureCode = "2902";
246
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
247
        if(sid != null) {
248
            pid = sid;
249
        }
250
        
251
        //check if it is the authoritative member node
252
        if(!allowed) {
253
            allowed = isAuthoritativeMNodeAdmin(session, pid);
254
        }
255
        
256
        if (!allowed) { 
257
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to delete objects on the Node.");
258
        }
259
    	
260
    	// defer to superclass implementation
261
        return super.delete(session, pid);
262
    }
263

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

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

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

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

    
372
        // does the subject have WRITE ( == update) priveleges on the pid?
373
        allowed = isAuthorized(session, pid, Permission.WRITE);
374

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

    
385
            // get the existing system metadata for the object
386
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
387

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

    
437
            isScienceMetadata = isScienceMetadata(sysmeta);
438

    
439
            // do we have XML metadata or a data object?
440
            if (isScienceMetadata) {
441

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

    
454
                    }
455

    
456
                } catch (IOException e) {
457
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
458
                    logMetacat.info(msg);
459
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
460

    
461
                }
462

    
463
            } else {
464

    
465
                // update the data object
466
                localId = insertDataObject(object, newPid, session);
467

    
468
            }
469
            
470
            // add the newPid to the obsoletedBy list for the existing sysmeta
471
            existingSysMeta.setObsoletedBy(newPid);
472

    
473
            // then update the existing system metadata
474
            updateSystemMetadata(existingSysMeta);
475

    
476
            // prep the new system metadata, add pid to the affected lists
477
            sysmeta.setObsoletes(pid);
478
            //sysmeta.addDerivedFrom(pid);
479

    
480
            // and insert the new system metadata
481
            insertSystemMetadata(sysmeta);
482

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

    
493
        } else {
494
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
495
                    + " on the Member Node.");
496
        }
497

    
498
        return newPid;
499
    }
500

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

    
504
        // check for null session
505
        if (session == null) {
506
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
507
        }
508
        // verify the pid is valid format
509
        if (!isValidIdentifier(pid)) {
510
            throw new InvalidRequest("1102", "The provided identifier is invalid.");
511
        }
512
        // set the submitter to match the certificate
513
        sysmeta.setSubmitter(session.getSubject());
514
        // set the originating node
515
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
516
        sysmeta.setOriginMemberNode(originMemberNode);
517
        sysmeta.setArchived(false);
518

    
519
        // set the dates
520
        Date now = Calendar.getInstance().getTime();
521
        sysmeta.setDateSysMetadataModified(now);
522
        sysmeta.setDateUploaded(now);
523
        
524
        // set the serial version
525
        sysmeta.setSerialVersion(BigInteger.ZERO);
526

    
527
        // check that we are not attempting to subvert versioning
528
        if (sysmeta.getObsoletes() != null && sysmeta.getObsoletes().getValue() != null) {
529
            throw new InvalidSystemMetadata("1180", 
530
              "The supplied system metadata is invalid. " +
531
              "The obsoletes field cannot have a value when creating entries.");
532
        }
533
        
534
        if (sysmeta.getObsoletedBy() != null && sysmeta.getObsoletedBy().getValue() != null) {
535
            throw new InvalidSystemMetadata("1180", 
536
              "The supplied system metadata is invalid. " +
537
              "The obsoletedBy field cannot have a value when creating entries.");
538
        }
539
        
540
        // verify the sid in the system metadata
541
        Identifier sid = sysmeta.getSeriesId();
542
        boolean idExists = false;
543
        if(sid != null) {
544
            if (!isValidIdentifier(sid)) {
545
                throw new InvalidSystemMetadata("1180", "The provided series id is invalid.");
546
            }
547
            try {
548
                idExists = IdentifierManager.getInstance().identifierExists(sid.getValue());
549
            } catch (SQLException e) {
550
                throw new ServiceFailure("1190", 
551
                                        "The series identifier " + sid.getValue() +
552
                                        " in the system metadata couldn't be determined if it is unique since : "+e.getMessage());
553
            }
554
            if (idExists) {
555
                    throw new InvalidSystemMetadata("1180", 
556
                              "The series identifier " + sid.getValue() +
557
                              " is already used by another object and" +
558
                              "therefore can not be used for this object. Clients should choose" +
559
                              "a new identifier that is unique and retry the operation or " +
560
                              "use CN.reserveIdentifier() to reserve one.");
561
                
562
            }
563
            //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 )
564
            if(sid.getValue().equals(pid.getValue())) {
565
                throw new InvalidSystemMetadata("1180", "The series id "+sid.getValue()+" in the system metadata shouldn't have the same value of the pid.");
566
            }
567
        }
568

    
569
        // call the shared impl
570
        Identifier resultPid = super.create(session, pid, object, sysmeta);
571
        
572
        // attempt to register the identifier - it checks if it is a doi
573
        try {
574
			DOIService.getInstance().registerDOI(sysmeta);
575
		} catch (Exception e) {
576
			ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage());
577
			sf.initCause(e);
578
            throw sf;
579
		}
580
        
581
        // return 
582
		return resultPid ;
583
    }
584

    
585
    /**
586
     * Called by a Coordinating Node to request that the Member Node create a 
587
     * copy of the specified object by retrieving it from another Member 
588
     * Node and storing it locally so that it can be made accessible to 
589
     * the DataONE system.
590
     * 
591
     * @param session - the Session object containing the credentials for the Subject
592
     * @param sysmeta - Copy of the CN held system metadata for the object
593
     * @param sourceNode - A reference to node from which the content should be 
594
     *                     retrieved. The reference should be resolved by 
595
     *                     checking the CN node registry.
596
     * 
597
     * @return true if the replication succeeds
598
     * 
599
     * @throws ServiceFailure
600
     * @throws NotAuthorized
601
     * @throws NotImplemented
602
     * @throws UnsupportedType
603
     * @throws InsufficientResources
604
     * @throws InvalidRequest
605
     */
606
    @Override
607
    public boolean replicate(Session session, SystemMetadata sysmeta,
608
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
609
            NotAuthorized, InvalidRequest, InsufficientResources,
610
            UnsupportedType {
611

    
612
        if (session != null && sysmeta != null && sourceNode != null) {
613
            logMetacat.info("MNodeService.replicate() called with parameters: \n" +
614
                            "\tSession.Subject      = "                           +
615
                            session.getSubject().getValue() + "\n"                +
616
                            "\tidentifier           = "                           + 
617
                            sysmeta.getIdentifier().getValue()                    +
618
                            "\n" + "\tSource NodeReference ="                     +
619
                            sourceNode.getValue());
620
        }
621
        boolean result = false;
622
        String nodeIdStr = null;
623
        NodeReference nodeId = null;
624

    
625
        // get the referenced object
626
        Identifier pid = sysmeta.getIdentifier();
627
        // verify the pid is valid format
628
        if (!isValidIdentifier(pid)) {
629
            throw new InvalidRequest("2153", "The provided identifier in the system metadata is invalid.");
630
        }
631

    
632
        // get from the membernode
633
        // TODO: switch credentials for the server retrieval?
634
        this.mn = D1Client.getMN(sourceNode);
635
        this.cn = D1Client.getCN();
636
        InputStream object = null;
637
        Session thisNodeSession = null;
638
        SystemMetadata localSystemMetadata = null;
639
        BaseException failure = null;
640
        String localId = null;
641
        
642
        // TODO: check credentials
643
        // cannot be called by public
644
        if (session == null || session.getSubject() == null) {
645
            String msg = "No session was provided to replicate identifier " +
646
            sysmeta.getIdentifier().getValue();
647
            logMetacat.error(msg);
648
            throw new NotAuthorized("2152", msg);
649
            
650
        }
651

    
652

    
653
        // get the local node id
654
        try {
655
            nodeIdStr = PropertyService.getProperty("dataone.nodeId");
656
            nodeId = new NodeReference();
657
            nodeId.setValue(nodeIdStr);
658

    
659
        } catch (PropertyNotFoundException e1) {
660
            String msg = "Couldn't get dataone.nodeId property: " + e1.getMessage();
661
            failure = new ServiceFailure("2151", msg);
662
            //setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
663
            logMetacat.error(msg);
664
            //return true;
665
            throw new ServiceFailure("2151", msg);
666

    
667
        }
668
        
669

    
670
        try {
671
            // do we already have a replica?
672
            try {
673
                localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
674
                // if we have a local id, get the local object
675
                try {
676
                    object = MetacatHandler.read(localId);
677
                } catch (Exception e) {
678
                	// NOTE: we may already know about this ID because it could be a data file described by a metadata file
679
                	// https://redmine.dataone.org/issues/2572
680
                	// TODO: fix this so that we don't prevent ourselves from getting replicas
681
                	
682
                    // let the CN know that the replication failed
683
                	logMetacat.warn("Object content not found on this node despite having localId: " + localId);
684
                	String msg = "Can't read the object bytes properly, replica is invalid.";
685
                    ServiceFailure serviceFailure = new ServiceFailure("2151", msg);
686
                    setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, serviceFailure);
687
                    logMetacat.warn(msg);
688
                    throw serviceFailure;
689
                    
690
                }
691

    
692
            } catch (McdbDocNotFoundException e) {
693
                logMetacat.info("No replica found. Continuing.");
694
                
695
            } catch (SQLException ee) {
696
                throw new ServiceFailure("2151", "Couldn't identify the local id of the object with the specified identifier "
697
                                        +pid.getValue()+" since - "+ee.getMessage());
698
            }
699
            
700
            // no local replica, get a replica
701
            if ( object == null ) {
702
                // session should be null to use the default certificate
703
                // location set in the Certificate manager
704
                object = mn.getReplica(thisNodeSession, pid);
705
                logMetacat.info("MNodeService.getReplica() called for identifier "
706
                                + pid.getValue());
707

    
708
            }
709

    
710
        } catch (InvalidToken e) {            
711
            String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
712
            failure = new ServiceFailure("2151", msg);
713
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
714
            logMetacat.error(msg);
715
            throw new ServiceFailure("2151", msg);
716

    
717
        } catch (NotFound e) {
718
            String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
719
            failure = new ServiceFailure("2151", msg);
720
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
721
            logMetacat.error(msg);
722
            throw new ServiceFailure("2151", msg);
723

    
724
        } catch (NotAuthorized e) {
725
            String msg = "Could not retrieve object to replicate (NotAuthorized): "+ e.getMessage();
726
            failure = new ServiceFailure("2151", msg);
727
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
728
            logMetacat.error(msg);
729
            throw new ServiceFailure("2151", msg);
730
        } catch (NotImplemented e) {
731
            String msg = "Could not retrieve object to replicate (mn.getReplica NotImplemented): "+ e.getMessage();
732
            failure = new ServiceFailure("2151", msg);
733
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
734
            logMetacat.error(msg);
735
            throw new ServiceFailure("2151", msg);
736
        } catch (ServiceFailure e) {
737
            String msg = "Could not retrieve object to replicate (ServiceFailure): "+ e.getMessage();
738
            failure = new ServiceFailure("2151", msg);
739
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
740
            logMetacat.error(msg);
741
            throw new ServiceFailure("2151", msg);
742
        } catch (InsufficientResources e) {
743
            String msg = "Could not retrieve object to replicate (InsufficientResources): "+ e.getMessage();
744
            failure = new ServiceFailure("2151", msg);
745
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
746
            logMetacat.error(msg);
747
            throw new ServiceFailure("2151", msg);
748
        }
749

    
750
        // verify checksum on the object, if supported
751
        if (object.markSupported()) {
752
            Checksum givenChecksum = sysmeta.getChecksum();
753
            Checksum computedChecksum = null;
754
            try {
755
                computedChecksum = ChecksumUtil.checksum(object, givenChecksum.getAlgorithm());
756
                object.reset();
757

    
758
            } catch (Exception e) {
759
                String msg = "Error computing checksum on replica: " + e.getMessage();
760
                logMetacat.error(msg);
761
                ServiceFailure sf = new ServiceFailure("2151", msg);
762
                sf.initCause(e);
763
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, sf);
764
                throw sf;
765
            }
766
            if (!givenChecksum.getValue().equals(computedChecksum.getValue())) {
767
                logMetacat.error("Given    checksum for " + pid.getValue() + 
768
                    "is " + givenChecksum.getValue());
769
                logMetacat.error("Computed checksum for " + pid.getValue() + 
770
                    "is " + computedChecksum.getValue());
771
                String msg = "Computed checksum does not match declared checksum";
772
                failure = new ServiceFailure("2151", msg);
773
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
774
                throw new ServiceFailure("2151", msg);
775
            }
776
        }
777

    
778
        // add it to local store
779
        Identifier retPid;
780
        try {
781
            // skip the MN.create -- this mutates the system metadata and we don't want it to
782
            if ( localId == null ) {
783
                // TODO: this will fail if we already "know" about the identifier
784
            	// FIXME: see https://redmine.dataone.org/issues/2572
785
                retPid = super.create(session, pid, object, sysmeta);
786
                result = (retPid.getValue().equals(pid.getValue()));
787
            }
788
            
789
        } catch (Exception e) {
790
            String msg = "Could not save object to local store (" + e.getClass().getName() + "): " + e.getMessage();
791
            failure = new ServiceFailure("2151", msg);
792
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
793
            logMetacat.error(msg);
794
            throw new ServiceFailure("2151", msg);
795
            
796
        }
797

    
798
        // finish by setting the replication status
799
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
800
        return result;
801

    
802
    }
803

    
804
    /**
805
     * Return the object identified by the given object identifier
806
     * 
807
     * @param session - the Session object containing the credentials for the Subject
808
     * @param pid - the object identifier for the given object
809
     * 
810
     * @return inputStream - the input stream of the given object
811
     * 
812
     * @throws InvalidToken
813
     * @throws ServiceFailure
814
     * @throws NotAuthorized
815
     * @throws InvalidRequest
816
     * @throws NotImplemented
817
     */
818
    @Override
819
    public InputStream get(Session session, Identifier pid) 
820
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
821

    
822
        return super.get(session, pid);
823

    
824
    }
825

    
826
    /**
827
     * Returns a Checksum for the specified object using an accepted hashing algorithm
828
     * 
829
     * @param session - the Session object containing the credentials for the Subject
830
     * @param pid - the object identifier for the given object
831
     * @param algorithm -  the name of an algorithm that will be used to compute 
832
     *                     a checksum of the bytes of the object
833
     * 
834
     * @return checksum - the checksum of the given object
835
     * 
836
     * @throws InvalidToken
837
     * @throws ServiceFailure
838
     * @throws NotAuthorized
839
     * @throws NotFound
840
     * @throws InvalidRequest
841
     * @throws NotImplemented
842
     */
843
    @Override
844
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
845
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
846
        InvalidRequest, NotImplemented {
847

    
848
        Checksum checksum = null;
849
        String serviceFailure = "1410";
850
        String notFound = "1420";
851
        //Checkum only handles the pid, not sid
852
        checkV1SystemMetaPidExist(pid, serviceFailure, "The checksum for the object specified by "+pid.getValue()+" couldn't be returned ",  notFound, 
853
                "The object specified by "+pid.getValue()+" does not exist at this node.");
854
        InputStream inputStream = get(session, pid);
855

    
856
        try {
857
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
858

    
859
        } catch (NoSuchAlgorithmException e) {
860
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
861
                    + e.getMessage());
862
        } catch (IOException e) {
863
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
864
                    + e.getMessage());
865
        }
866

    
867
        if (checksum == null) {
868
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
869
        }
870

    
871
        return checksum;
872
    }
873

    
874
    /**
875
     * Return the system metadata for a given object
876
     * 
877
     * @param session - the Session object containing the credentials for the Subject
878
     * @param pid - the object identifier for the given object
879
     * 
880
     * @return inputStream - the input stream of the given system metadata object
881
     * 
882
     * @throws InvalidToken
883
     * @throws ServiceFailure
884
     * @throws NotAuthorized
885
     * @throws NotFound
886
     * @throws InvalidRequest
887
     * @throws NotImplemented
888
     */
889
    @Override
890
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) 
891
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
892
        NotImplemented {
893

    
894
        return super.getSystemMetadata(session, pid);
895
    }
896

    
897
    /**
898
     * Retrieve the list of objects present on the MN that match the calling parameters
899
     * 
900
     * @param session - the Session object containing the credentials for the Subject
901
     * @param startTime - Specifies the beginning of the time range from which 
902
     *                    to return object (>=)
903
     * @param endTime - Specifies the beginning of the time range from which 
904
     *                  to return object (>=)
905
     * @param objectFormat - Restrict results to the specified object format
906
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
907
     * @param start - The zero-based index of the first value, relative to the 
908
     *                first record of the resultset that matches the parameters.
909
     * @param count - The maximum number of entries that should be returned in 
910
     *                the response. The Member Node may return less entries 
911
     *                than specified in this value.
912
     * 
913
     * @return objectList - the list of objects matching the criteria
914
     * 
915
     * @throws InvalidToken
916
     * @throws ServiceFailure
917
     * @throws NotAuthorized
918
     * @throws InvalidRequest
919
     * @throws NotImplemented
920
     */
921
    @Override
922
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Identifier identifier, Boolean replicaStatus, Integer start,
923
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
924

    
925
        return super.listObjects(session, startTime, endTime, objectFormatId, identifier, replicaStatus, start, count);
926
    }
927

    
928
    /**
929
     * Return a description of the node's capabilities and services.
930
     * 
931
     * @return node - the technical capabilities of the Member Node
932
     * 
933
     * @throws ServiceFailure
934
     * @throws NotAuthorized
935
     * @throws InvalidRequest
936
     * @throws NotImplemented
937
     */
938
    @Override
939
    public Node getCapabilities() 
940
        throws NotImplemented, ServiceFailure {
941

    
942
        String nodeName = null;
943
        String nodeId = null;
944
        String subject = null;
945
        String contactSubject = null;
946
        String nodeDesc = null;
947
        String nodeTypeString = null;
948
        NodeType nodeType = null;
949
        String mnCoreServiceVersion = null;
950
        String mnReadServiceVersion = null;
951
        String mnAuthorizationServiceVersion = null;
952
        String mnStorageServiceVersion = null;
953
        String mnReplicationServiceVersion = null;
954

    
955
        boolean nodeSynchronize = false;
956
        boolean nodeReplicate = false;
957
        boolean mnCoreServiceAvailable = false;
958
        boolean mnReadServiceAvailable = false;
959
        boolean mnAuthorizationServiceAvailable = false;
960
        boolean mnStorageServiceAvailable = false;
961
        boolean mnReplicationServiceAvailable = false;
962

    
963
        try {
964
            // get the properties of the node based on configuration information
965
            nodeName = PropertyService.getProperty("dataone.nodeName");
966
            nodeId = PropertyService.getProperty("dataone.nodeId");
967
            subject = PropertyService.getProperty("dataone.subject");
968
            contactSubject = PropertyService.getProperty("dataone.contactSubject");
969
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
970
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
971
            nodeType = NodeType.convert(nodeTypeString);
972
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
973
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
974

    
975
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
976
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
977
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
978
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
979
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
980

    
981
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
982
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
983
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
984
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
985
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
986

    
987
            // Set the properties of the node based on configuration information and
988
            // calls to current status methods
989
            String serviceName = SystemUtil.getSecureContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
990
            Node node = new Node();
991
            node.setBaseURL(serviceName + "/" + nodeTypeString);
992
            node.setDescription(nodeDesc);
993

    
994
            // set the node's health information
995
            node.setState(NodeState.UP);
996
            
997
            // set the ping response to the current value
998
            Ping canPing = new Ping();
999
            canPing.setSuccess(false);
1000
            try {
1001
            	Date pingDate = ping();
1002
                canPing.setSuccess(pingDate != null);
1003
            } catch (BaseException e) {
1004
                e.printStackTrace();
1005
                // guess it can't be pinged
1006
            }
1007
            
1008
            node.setPing(canPing);
1009

    
1010
            NodeReference identifier = new NodeReference();
1011
            identifier.setValue(nodeId);
1012
            node.setIdentifier(identifier);
1013
            Subject s = new Subject();
1014
            s.setValue(subject);
1015
            node.addSubject(s);
1016
            Subject contact = new Subject();
1017
            contact.setValue(contactSubject);
1018
            node.addContactSubject(contact);
1019
            node.setName(nodeName);
1020
            node.setReplicate(nodeReplicate);
1021
            node.setSynchronize(nodeSynchronize);
1022

    
1023
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
1024
            Services services = new Services();
1025

    
1026
            Service sMNCore = new Service();
1027
            sMNCore.setName("MNCore");
1028
            sMNCore.setVersion(mnCoreServiceVersion);
1029
            sMNCore.setAvailable(mnCoreServiceAvailable);
1030

    
1031
            Service sMNRead = new Service();
1032
            sMNRead.setName("MNRead");
1033
            sMNRead.setVersion(mnReadServiceVersion);
1034
            sMNRead.setAvailable(mnReadServiceAvailable);
1035

    
1036
            Service sMNAuthorization = new Service();
1037
            sMNAuthorization.setName("MNAuthorization");
1038
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
1039
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
1040

    
1041
            Service sMNStorage = new Service();
1042
            sMNStorage.setName("MNStorage");
1043
            sMNStorage.setVersion(mnStorageServiceVersion);
1044
            sMNStorage.setAvailable(mnStorageServiceAvailable);
1045

    
1046
            Service sMNReplication = new Service();
1047
            sMNReplication.setName("MNReplication");
1048
            sMNReplication.setVersion(mnReplicationServiceVersion);
1049
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
1050

    
1051
            services.addService(sMNRead);
1052
            services.addService(sMNCore);
1053
            services.addService(sMNAuthorization);
1054
            services.addService(sMNStorage);
1055
            services.addService(sMNReplication);
1056
            node.setServices(services);
1057

    
1058
            // Set the schedule for synchronization
1059
            Synchronization synchronization = new Synchronization();
1060
            Schedule schedule = new Schedule();
1061
            Date now = new Date();
1062
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
1063
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
1064
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
1065
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
1066
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
1067
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
1068
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
1069
            synchronization.setSchedule(schedule);
1070
            synchronization.setLastHarvested(now);
1071
            synchronization.setLastCompleteHarvest(now);
1072
            node.setSynchronization(synchronization);
1073

    
1074
            node.setType(nodeType);
1075
            return node;
1076

    
1077
        } catch (PropertyNotFoundException pnfe) {
1078
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
1079
            logMetacat.error(msg);
1080
            throw new ServiceFailure("2162", msg);
1081
        }
1082
    }
1083

    
1084
    
1085

    
1086
    /**
1087
     * A callback method used by a CN to indicate to a MN that it cannot 
1088
     * complete synchronization of the science metadata identified by pid.  Log
1089
     * the event in the metacat event log.
1090
     * 
1091
     * @param session
1092
     * @param syncFailed
1093
     * 
1094
     * @throws ServiceFailure
1095
     * @throws NotAuthorized
1096
     * @throws NotImplemented
1097
     */
1098
    @Override
1099
    public boolean synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
1100
        throws NotImplemented, ServiceFailure, NotAuthorized {
1101

    
1102
        String localId;
1103
        Identifier pid;
1104
        if ( syncFailed.getPid() != null ) {
1105
            pid = new Identifier();
1106
            pid.setValue(syncFailed.getPid());
1107
            boolean allowed;
1108
            
1109
            //are we allowed? only CNs
1110
            try {
1111
                allowed = isAdminAuthorized(session);
1112
                if ( !allowed ){
1113
                    throw new NotAuthorized("2162", 
1114
                            "Not allowed to call synchronizationFailed() on this node.");
1115
                }
1116
            } catch (InvalidToken e) {
1117
                throw new NotAuthorized("2162", 
1118
                        "Not allowed to call synchronizationFailed() on this node.");
1119

    
1120
            }
1121
            
1122
        } else {
1123
            throw new ServiceFailure("2161", "The identifier cannot be null.");
1124

    
1125
        }
1126
        
1127
        try {
1128
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1129
        } catch (McdbDocNotFoundException e) {
1130
            throw new ServiceFailure("2161", "The identifier specified by " + 
1131
                    syncFailed.getPid() + " was not found on this node.");
1132

    
1133
        } catch (SQLException e) {
1134
            throw new ServiceFailure("2161", "Couldn't identify the local id of the identifier specified by " + 
1135
                    syncFailed.getPid() + " since "+e.getMessage());
1136
        }
1137
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
1138
        // method is changed to include the URL as a parameter
1139
        logMetacat.debug("Synchronization for the object identified by " + 
1140
                pid.getValue() + " failed from " + syncFailed.getNodeId() + 
1141
                " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
1142
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
1143
        String principal = Constants.SUBJECT_PUBLIC;
1144
        if (session != null && session.getSubject() != null) {
1145
          principal = session.getSubject().getValue();
1146
        }
1147
        try {
1148
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
1149
        } catch (Exception e) {
1150
            throw new ServiceFailure("2161", "Could not log the error for: " + pid.getValue());
1151
        }
1152
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
1153
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
1154
        return true;
1155

    
1156
    }
1157

    
1158
    /**
1159
     * Essentially a get() but with different logging behavior
1160
     */
1161
    @Override
1162
    public InputStream getReplica(Session session, Identifier pid) 
1163
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
1164

    
1165
        logMetacat.info("MNodeService.getReplica() called.");
1166

    
1167
        // cannot be called by public
1168
        if (session == null) {
1169
        	throw new InvalidToken("2183", "No session was provided.");
1170
        }
1171
        
1172
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
1173
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
1174
             "\tIdentifier           = " + pid.getValue());
1175

    
1176
        InputStream inputStream = null; // bytes to be returned
1177
        handler = new MetacatHandler(new Timer());
1178
        boolean allowed = false;
1179
        String localId; // the metacat docid for the pid
1180

    
1181
        // get the local docid from Metacat
1182
        try {
1183
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1184
        } catch (McdbDocNotFoundException e) {
1185
            throw new ServiceFailure("2181", "The object specified by " + 
1186
                    pid.getValue() + " does not exist at this node.");
1187
            
1188
        } catch (SQLException e) {
1189
            throw new ServiceFailure("2181", "The local id of the object specified by " + 
1190
                    pid.getValue() + " couldn't be identified since "+e.getMessage());
1191
        }
1192

    
1193
        Subject targetNodeSubject = session.getSubject();
1194

    
1195
        // check for authorization to replicate, null session to act as this source MN
1196
        try {
1197
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1198
        } catch (InvalidToken e1) {
1199
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1200
                + e1.getMessage());
1201
            
1202
        } catch (NotFound e1) {
1203
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1204
                    + e1.getMessage());
1205

    
1206
        } catch (InvalidRequest e1) {
1207
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1208
                    + e1.getMessage());
1209

    
1210
        }
1211

    
1212
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1213
            " for identifier " + pid.getValue());
1214

    
1215
        // if the person is authorized, perform the read
1216
        if (allowed) {
1217
            try {
1218
                inputStream = MetacatHandler.read(localId);
1219
            } catch (Exception e) {
1220
                throw new ServiceFailure("1020", "The object specified by " + 
1221
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1222
            }
1223
        }
1224

    
1225
        // if we fail to set the input stream
1226
        if (inputStream == null) {
1227
            throw new ServiceFailure("2181", "The object specified by " + 
1228
                pid.getValue() + "does not exist at this node.");
1229
        }
1230

    
1231
        // log the replica event
1232
        String principal = null;
1233
        if (session.getSubject() != null) {
1234
            principal = session.getSubject().getValue();
1235
        }
1236
        EventLog.getInstance().log(request.getRemoteAddr(), 
1237
            request.getHeader("User-Agent"), principal, localId, "replicate");
1238

    
1239
        return inputStream;
1240
    }
1241

    
1242
    /**
1243
     * A method to notify the Member Node that the authoritative copy of 
1244
     * system metadata on the Coordinating Nodes has changed.
1245
     * 
1246
     * @param session   Session information that contains the identity of the 
1247
     *                  calling user as retrieved from the X.509 certificate 
1248
     *                  which must be traceable to the CILogon service.
1249
     * @param serialVersion   The serialVersion of the system metadata
1250
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1251
     * @throws NotImplemented
1252
     * @throws ServiceFailure
1253
     * @throws NotAuthorized
1254
     * @throws InvalidRequest
1255
     * @throws InvalidToken
1256
     */
1257
    public boolean systemMetadataChanged(Session session, Identifier pid,
1258
        long serialVersion, Date dateSysMetaLastModified) 
1259
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1260
        InvalidToken {
1261
        
1262
        // cannot be called by public
1263
        if (session == null) {
1264
        	throw new InvalidToken("1332", "No session was provided.");
1265
        }
1266

    
1267
        String serviceFailureCode = "1333";
1268
        Identifier sid = getPIDForSID(pid, serviceFailureCode);
1269
        if(sid != null) {
1270
            pid = sid;
1271
        }
1272
        
1273
        SystemMetadata currentLocalSysMeta = null;
1274
        SystemMetadata newSysMeta = null;
1275
        CNode cn = D1Client.getCN();
1276
        NodeList nodeList = null;
1277
        Subject callingSubject = null;
1278
        boolean allowed = false;
1279
        
1280
        // are we allowed to call this?
1281
        callingSubject = session.getSubject();
1282
        nodeList = cn.listNodes();
1283
        
1284
        for(Node node : nodeList.getNodeList()) {
1285
            // must be a CN
1286
            if ( node.getType().equals(NodeType.CN)) {
1287
               List<Subject> subjectList = node.getSubjectList();
1288
               // the calling subject must be in the subject list
1289
               if ( subjectList.contains(callingSubject)) {
1290
                   allowed = true;
1291
                   
1292
               }
1293
               
1294
            }
1295
        }
1296
        
1297
        if (!allowed ) {
1298
            String msg = "The subject identified by " + callingSubject.getValue() +
1299
              " is not authorized to call this service.";
1300
            throw new NotAuthorized("1331", msg);
1301
            
1302
        }
1303
        
1304
        // compare what we have locally to what is sent in the change notification
1305
        try {
1306
            currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1307
             
1308
        } catch (RuntimeException e) {
1309
            String msg = "SystemMetadata for pid " + pid.getValue() +
1310
              " couldn't be updated because it couldn't be found locally: " +
1311
              e.getMessage();
1312
            logMetacat.error(msg);
1313
            ServiceFailure sf = new ServiceFailure("1333", msg);
1314
            sf.initCause(e);
1315
            throw sf; 
1316
        }
1317
        
1318
        if (currentLocalSysMeta.getSerialVersion().longValue() < serialVersion ) {
1319
            try {
1320
                newSysMeta = cn.getSystemMetadata(null, pid);
1321
            } catch (NotFound e) {
1322
                // huh? you just said you had it
1323
            	String msg = "On updating the local copy of system metadata " + 
1324
                "for pid " + pid.getValue() +", the CN reports it is not found." +
1325
                " The error message was: " + e.getMessage();
1326
                logMetacat.error(msg);
1327
                ServiceFailure sf = new ServiceFailure("1333", msg);
1328
                sf.initCause(e);
1329
                throw sf;
1330
            }
1331
            
1332
            //check about the sid in the system metadata
1333
            Identifier newSID = newSysMeta.getSeriesId();
1334
            if(newSID != null) {
1335
                if (!isValidIdentifier(newSID)) {
1336
                    throw new InvalidRequest("1334", "The series identifier in the new system metadata is invalid.");
1337
                }
1338
                Identifier currentSID = currentLocalSysMeta.getSeriesId();
1339
                if( currentSID != null && currentSID.getValue() != null) {
1340
                    if(!newSID.getValue().equals(currentSID.getValue())) {
1341
                        //newSID doesn't match the currentSID. The newSID shouldn't be used.
1342
                        try {
1343
                            if(IdentifierManager.getInstance().identifierExists(newSID.getValue())) {
1344
                                throw new InvalidRequest("1334", "The series identifier "+newSID.getValue()+" in the new system metadata has been used by another object.");
1345
                            }
1346
                        } catch (SQLException sql) {
1347
                            throw new ServiceFailure("1333", "Couldn't determine if the SID "+newSID.getValue()+" in the system metadata exists in the node since "+sql.getMessage());
1348
                        }
1349
                        
1350
                    }
1351
                } else {
1352
                    //newSID shouldn't be used
1353
                    try {
1354
                        if(IdentifierManager.getInstance().identifierExists(newSID.getValue())) {
1355
                            throw new InvalidRequest("1334", "The series identifier "+newSID.getValue()+" in the new system metadata has been used by another object.");
1356
                        }
1357
                    } catch (SQLException sql) {
1358
                        throw new ServiceFailure("1333", "Couldn't determine if the SID "+newSID.getValue()+" in the system metadata exists in the node since "+sql.getMessage());
1359
                    }
1360
                }
1361
            }
1362
            // update the local copy of system metadata for the pid
1363
            try {
1364
                HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1365
                logMetacat.info("Updated local copy of system metadata for pid " +
1366
                    pid.getValue() + " after change notification from the CN.");
1367
                
1368
                // TODO: consider inspecting the change for archive
1369
                // see: https://projects.ecoinformatics.org/ecoinfo/issues/6417
1370
//                if (newSysMeta.getArchived() != null && newSysMeta.getArchived().booleanValue()) {
1371
//                	try {
1372
//						this.archive(session, newSysMeta.getIdentifier());
1373
//					} catch (NotFound e) {
1374
//						// do we care? nothing to do about it now
1375
//						logMetacat.error(e.getMessage(), e);
1376
//					}
1377
//                }
1378
                
1379
            } catch (RuntimeException e) {
1380
                String msg = "SystemMetadata for pid " + pid.getValue() +
1381
                  " couldn't be updated: " +
1382
                  e.getMessage();
1383
                logMetacat.error(msg);
1384
                ServiceFailure sf = new ServiceFailure("1333", msg);
1385
                sf.initCause(e);
1386
                throw sf;
1387
            }
1388
            
1389
            // submit for indexing
1390
            try {
1391
				MetacatSolrIndex.getInstance().submit(newSysMeta.getIdentifier(), newSysMeta, null, true);
1392
			} catch (Exception e) {
1393
                logMetacat.error("Could not submit changed systemMetadata for indexing, pid: " + newSysMeta.getIdentifier().getValue(), e);
1394
			}
1395
        }
1396
        
1397
        return true;
1398
        
1399
    }
1400
    
1401
    /*
1402
     * Set the replication status for the object on the Coordinating Node
1403
     * 
1404
     * @param session - the session for the this target node
1405
     * @param pid - the identifier of the object being updated
1406
     * @param nodeId - the identifier of this target node
1407
     * @param status - the replication status to set
1408
     * @param failure - the exception to include, if any
1409
     */
1410
    private void setReplicationStatus(Session session, Identifier pid, 
1411
        NodeReference nodeId, ReplicationStatus status, BaseException failure) 
1412
        throws ServiceFailure, NotImplemented, NotAuthorized, 
1413
        InvalidRequest {
1414
        
1415
        // call the CN as the MN to set the replication status
1416
        try {
1417
            this.cn = D1Client.getCN();
1418
            this.cn.setReplicationStatus(session, pid, nodeId,
1419
                    status, failure);
1420
            
1421
        } catch (InvalidToken e) {
1422
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (InvalidToken): " + e.getMessage();
1423
            logMetacat.error(msg);
1424
        	throw new ServiceFailure("2151",
1425
                    msg);
1426
            
1427
        } catch (NotFound e) {
1428
        	String msg = "Could not set the replication status for " + pid.getValue() + " on the CN (NotFound): " + e.getMessage();
1429
            logMetacat.error(msg);
1430
        	throw new ServiceFailure("2151",
1431
                    msg);
1432
            
1433
        }
1434
    }
1435
    
1436
    private SystemMetadata makePublicIfNot(SystemMetadata sysmeta, Identifier pid) throws ServiceFailure, InvalidToken, NotFound, NotImplemented, InvalidRequest {
1437
    	// check if it is publicly readable
1438
		boolean isPublic = false;
1439
		Subject publicSubject = new Subject();
1440
		publicSubject.setValue(Constants.SUBJECT_PUBLIC);
1441
		Session publicSession = new Session();
1442
		publicSession.setSubject(publicSubject);
1443
		AccessRule publicRule = new AccessRule();
1444
		publicRule.addPermission(Permission.READ);
1445
		publicRule.addSubject(publicSubject);
1446
		
1447
		// see if we need to add the rule
1448
		try {
1449
			isPublic = this.isAuthorized(publicSession, pid, Permission.READ);
1450
		} catch (NotAuthorized na) {
1451
			// well, certainly not authorized for public read!
1452
		}
1453
		if (!isPublic) {
1454
			sysmeta.getAccessPolicy().addAllow(publicRule);
1455
		}
1456
		
1457
		return sysmeta;
1458
    }
1459

    
1460
	@Override
1461
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
1462
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1463
			InvalidRequest {
1464
		
1465
		// check for null session
1466
        if (session == null) {
1467
          throw new InvalidToken("2190", "Session is required to generate an Identifier at this Node.");
1468
        }
1469
		
1470
		Identifier identifier = new Identifier();
1471
		
1472
		// handle different schemes
1473
		if (scheme.equalsIgnoreCase(UUID_SCHEME)) {
1474
			// UUID
1475
			UUID uuid = UUID.randomUUID();
1476
            identifier.setValue(UUID_PREFIX + uuid.toString());
1477
		} else if (scheme.equalsIgnoreCase(DOI_SCHEME)) {
1478
			// generate a DOI
1479
			try {
1480
				identifier = DOIService.getInstance().generateDOI();
1481
			} catch (EZIDException e) {
1482
				ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage());
1483
				sf.initCause(e);
1484
				throw sf;
1485
			}
1486
		} else {
1487
			// default if we don't know the scheme
1488
			if (fragment != null) {
1489
				// for now, just autogen with fragment
1490
				String autogenId = DocumentUtil.generateDocumentId(fragment, 0);
1491
				identifier.setValue(autogenId);			
1492
			} else {
1493
				// autogen with no fragment
1494
				String autogenId = DocumentUtil.generateDocumentId(0);
1495
				identifier.setValue(autogenId);
1496
			}
1497
		}
1498
		
1499
		// TODO: reserve the identifier with the CN. We can only do this when
1500
		// 1) the MN is part of a CN cluster
1501
		// 2) the request is from an authenticated user
1502
		
1503
		return identifier;
1504
	}
1505

    
1506
	
1507

    
1508
	@Override
1509
	public QueryEngineDescription getQueryEngineDescription(Session session, String engine)
1510
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
1511
			NotFound {
1512
	    if(engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1513
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1514
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1515
            }
1516
	        QueryEngineDescription qed = new QueryEngineDescription();
1517
	        qed.setName(EnabledQueryEngines.PATHQUERYENGINE);
1518
	        qed.setQueryEngineVersion("1.0");
1519
	        qed.addAdditionalInfo("This is the traditional structured query for Metacat");
1520
	        Vector<String> pathsForIndexing = null;
1521
	        try {
1522
	            pathsForIndexing = SystemUtil.getPathsForIndexing();
1523
	        } catch (MetacatUtilException e) {
1524
	            logMetacat.warn("Could not get index paths", e);
1525
	        }
1526
	        for (String fieldName: pathsForIndexing) {
1527
	            QueryField field = new QueryField();
1528
	            field.addDescription("Indexed field for path '" + fieldName + "'");
1529
	            field.setName(fieldName);
1530
	            field.setReturnable(true);
1531
	            field.setSearchable(true);
1532
	            field.setSortable(false);
1533
	            // TODO: determine type and multivaluedness
1534
	            field.setType(String.class.getName());
1535
	            //field.setMultivalued(true);
1536
	            qed.addQueryField(field);
1537
	        }
1538
	        return qed;
1539
	    } else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1540
	        if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1541
                throw new NotImplemented("0000", "MNodeService.getQueryEngineDescription - the query engine "+engine +" hasn't been implemented or has been disabled.");
1542
            }
1543
	        try {
1544
	            QueryEngineDescription qed = MetacatSolrEngineDescriptionHandler.getInstance().getQueryEngineDescritpion();
1545
	            return qed;
1546
	        } catch (Exception e) {
1547
	            e.printStackTrace();
1548
	            throw new ServiceFailure("Solr server error", e.getMessage());
1549
	        }
1550
	    } else {
1551
	        throw new NotFound("404", "The Metacat member node can't find the query engine - "+engine);
1552
	    }
1553
		
1554
	}
1555

    
1556
	@Override
1557
	public QueryEngineList listQueryEngines(Session session) throws InvalidToken,
1558
			ServiceFailure, NotAuthorized, NotImplemented {
1559
		QueryEngineList qel = new QueryEngineList();
1560
		//qel.addQueryEngine(EnabledQueryEngines.PATHQUERYENGINE);
1561
		//qel.addQueryEngine(EnabledQueryEngines.SOLRENGINE);
1562
		List<String> enables = EnabledQueryEngines.getInstance().getEnabled();
1563
		for(String name : enables) {
1564
		    qel.addQueryEngine(name);
1565
		}
1566
		return qel;
1567
	}
1568

    
1569
	@Override
1570
	public InputStream query(Session session, String engine, String query) throws InvalidToken,
1571
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
1572
			NotFound {
1573
	    String user = Constants.SUBJECT_PUBLIC;
1574
        String[] groups= null;
1575
        Set<Subject> subjects = null;
1576
        if (session != null) {
1577
            user = session.getSubject().getValue();
1578
            subjects = AuthUtils.authorizedClientSubjects(session);
1579
            if (subjects != null) {
1580
                List<String> groupList = new ArrayList<String>();
1581
                for (Subject subject: subjects) {
1582
                    groupList.add(subject.getValue());
1583
                }
1584
                groups = groupList.toArray(new String[0]);
1585
            }
1586
        } else {
1587
            //add the public user subject to the set 
1588
            Subject subject = new Subject();
1589
            subject.setValue(Constants.SUBJECT_PUBLIC);
1590
            subjects = new HashSet<Subject>();
1591
            subjects.add(subject);
1592
        }
1593
        //System.out.println("====== user is "+user);
1594
        //System.out.println("====== groups are "+groups);
1595
		if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) {
1596
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) {
1597
                throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1598
            }
1599
			try {
1600
				DBQuery queryobj = new DBQuery();
1601
				
1602
				String results = queryobj.performPathquery(query, user, groups);
1603
				ContentTypeByteArrayInputStream ctbais = new ContentTypeByteArrayInputStream(results.getBytes(MetaCatServlet.DEFAULT_ENCODING));
1604
				ctbais.setContentType("text/xml");
1605
				return ctbais;
1606

    
1607
			} catch (Exception e) {
1608
				throw new ServiceFailure("Pathquery error", e.getMessage());
1609
			}
1610
			
1611
		} else if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) {
1612
		    if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) {
1613
		        throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled.");
1614
		    }
1615
		    logMetacat.info("The query is ==================================== \n"+query);
1616
		    try {
1617
		        
1618
                return MetacatSolrIndex.getInstance().query(query, subjects);
1619
            } catch (Exception e) {
1620
                // TODO Auto-generated catch block
1621
                throw new ServiceFailure("Solr server error", e.getMessage());
1622
            } 
1623
		}
1624
		return null;
1625
	}
1626
	
1627
	/**
1628
	 * Given an existing Science Metadata PID, this method mints a DOI
1629
	 * and updates the original object "publishing" the update with the DOI.
1630
	 * This includes updating the ORE map that describes the Science Metadata+data.
1631
	 * TODO: ensure all referenced objects allow public read
1632
	 * 
1633
	 * @see https://projects.ecoinformatics.org/ecoinfo/issues/6014
1634
	 * 
1635
	 * @param originalIdentifier
1636
	 * @param request
1637
	 * @throws InvalidRequest 
1638
	 * @throws NotImplemented 
1639
	 * @throws NotAuthorized 
1640
	 * @throws ServiceFailure 
1641
	 * @throws InvalidToken 
1642
	 * @throws NotFound
1643
	 * @throws InvalidSystemMetadata 
1644
	 * @throws InsufficientResources 
1645
	 * @throws UnsupportedType 
1646
	 * @throws IdentifierNotUnique 
1647
	 */
1648
	public Identifier publish(Session session, Identifier originalIdentifier) throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata {
1649
		
1650
		
1651
		// get the original SM
1652
		SystemMetadata originalSystemMetadata = this.getSystemMetadata(session, originalIdentifier);
1653

    
1654
		// make copy of it using the marshaller to ensure DEEP copy
1655
		SystemMetadata sysmeta = null;
1656
		try {
1657
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
1658
			TypeMarshaller.marshalTypeToOutputStream(originalSystemMetadata, baos);
1659
			sysmeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1660
		} catch (Exception e) {
1661
			// report as service failure
1662
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1663
			sf.initCause(e);
1664
			throw sf;
1665
		}
1666

    
1667
		// mint a DOI for the new revision
1668
		Identifier newIdentifier = this.generateIdentifier(session, MNodeService.DOI_SCHEME, null);
1669
				
1670
		// set new metadata values
1671
		sysmeta.setIdentifier(newIdentifier);
1672
		sysmeta.setObsoletes(originalIdentifier);
1673
		sysmeta.setObsoletedBy(null);
1674
		
1675
		// ensure it is publicly readable
1676
		sysmeta = makePublicIfNot(sysmeta, originalIdentifier);
1677
		
1678
		// get the bytes
1679
		InputStream inputStream = this.get(session, originalIdentifier);
1680
		
1681
		// update the object
1682
		this.update(session, originalIdentifier, inputStream, newIdentifier, sysmeta);
1683
		
1684
		// update ORE that references the scimeta
1685
		// first try the naive method, then check the SOLR index
1686
		try {
1687
			String localId = IdentifierManager.getInstance().getLocalId(originalIdentifier.getValue());
1688
			
1689
			Identifier potentialOreIdentifier = new Identifier();
1690
			potentialOreIdentifier.setValue(SystemMetadataFactory.RESOURCE_MAP_PREFIX + localId);
1691
			
1692
			InputStream oreInputStream = null;
1693
			try {
1694
				oreInputStream = this.get(session, potentialOreIdentifier);
1695
			} catch (NotFound nf) {
1696
				// this is probably okay for many sci meta data docs
1697
				logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1698
				// try the SOLR index
1699
				List<Identifier> potentialOreIdentifiers = this.lookupOreFor(originalIdentifier, false);
1700
				if (potentialOreIdentifiers != null) {
1701
					potentialOreIdentifier = potentialOreIdentifiers.get(0);
1702
					try {
1703
						oreInputStream = this.get(session, potentialOreIdentifier);
1704
					} catch (NotFound nf2) {
1705
						// this is probably okay for many sci meta data docs
1706
						logMetacat.warn("No potential ORE map found for: " + potentialOreIdentifier.getValue());
1707
					}
1708
				}
1709
			}
1710
			if (oreInputStream != null) {
1711
				Identifier newOreIdentifier = MNodeService.getInstance(request).generateIdentifier(session, MNodeService.UUID_SCHEME, null);
1712
	
1713
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1714
				Map<Identifier, List<Identifier>> sciMetaMap = resourceMapStructure.get(potentialOreIdentifier);
1715
				List<Identifier> dataIdentifiers = sciMetaMap.get(originalIdentifier);
1716
					
1717
				// reconstruct the ORE with the new identifiers
1718
				sciMetaMap.remove(originalIdentifier);
1719
				sciMetaMap.put(newIdentifier, dataIdentifiers);
1720
				
1721
				ResourceMap resourceMap = ResourceMapFactory.getInstance().createResourceMap(newOreIdentifier, sciMetaMap);
1722
				String resourceMapString = ResourceMapFactory.getInstance().serializeResourceMap(resourceMap);
1723
				
1724
				// get the original ORE SM and update the values
1725
				SystemMetadata originalOreSysMeta = this.getSystemMetadata(session, potentialOreIdentifier);
1726
				SystemMetadata oreSysMeta = new SystemMetadata();
1727
				try {
1728
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
1729
					TypeMarshaller.marshalTypeToOutputStream(originalOreSysMeta, baos);
1730
					oreSysMeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, new ByteArrayInputStream(baos.toByteArray()));
1731
				} catch (Exception e) {
1732
					// report as service failure
1733
					ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1734
					sf.initCause(e);
1735
					throw sf;
1736
				}
1737

    
1738
				oreSysMeta.setIdentifier(newOreIdentifier);
1739
				oreSysMeta.setObsoletes(potentialOreIdentifier);
1740
				oreSysMeta.setObsoletedBy(null);
1741
				oreSysMeta.setSize(BigInteger.valueOf(resourceMapString.getBytes("UTF-8").length));
1742
				oreSysMeta.setChecksum(ChecksumUtil.checksum(resourceMapString.getBytes("UTF-8"), oreSysMeta.getChecksum().getAlgorithm()));
1743
				
1744
				// ensure ORE is publicly readable
1745
				oreSysMeta = makePublicIfNot(oreSysMeta, potentialOreIdentifier);
1746
				
1747
				// ensure all data objects allow public read
1748
				List<String> pidsToSync = new ArrayList<String>();
1749
				for (Identifier dataId: dataIdentifiers) {
1750
					SystemMetadata dataSysMeta = this.getSystemMetadata(session, dataId);
1751
					dataSysMeta = makePublicIfNot(dataSysMeta, dataId);
1752
					this.updateSystemMetadata(dataSysMeta);
1753
					pidsToSync.add(dataId.getValue());
1754
				}
1755
				SyncAccessPolicy sap = new SyncAccessPolicy();
1756
				try {
1757
					sap.sync(pidsToSync);
1758
				} catch (Exception e) {
1759
					// ignore
1760
					logMetacat.warn("Error attempting to sync access for data objects when publishing package");
1761
				}
1762
				
1763
				// save the updated ORE
1764
				this.update(
1765
						session, 
1766
						potentialOreIdentifier, 
1767
						new ByteArrayInputStream(resourceMapString.getBytes("UTF-8")), 
1768
						newOreIdentifier, 
1769
						oreSysMeta);
1770
				
1771
			} else {
1772
				// create a new ORE for them
1773
				// https://projects.ecoinformatics.org/ecoinfo/issues/6194
1774
				try {
1775
					// find the local id for the NEW package.
1776
					String newLocalId = IdentifierManager.getInstance().getLocalId(newIdentifier.getValue());
1777
	
1778
					@SuppressWarnings("unused")
1779
					SystemMetadata extraSysMeta = SystemMetadataFactory.createSystemMetadata(newLocalId, true, false);
1780
					// should be done generating the ORE here, and the same permissions were used from the metadata object
1781
					
1782
				} catch (Exception e) {
1783
					// oops, guess there was a problem - no package for you
1784
					logMetacat.error("Could not generate new ORE for published object: " + newIdentifier.getValue(), e);
1785
				}
1786
			}
1787
		} catch (McdbDocNotFoundException e) {
1788
			// report as service failure
1789
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1790
			sf.initCause(e);
1791
			throw sf;
1792
		} catch (UnsupportedEncodingException e) {
1793
			// report as service failure
1794
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1795
			sf.initCause(e);
1796
			throw sf;
1797
		} catch (OREException e) {
1798
			// report as service failure
1799
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1800
			sf.initCause(e);
1801
			throw sf;
1802
		} catch (URISyntaxException e) {
1803
			// report as service failure
1804
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1805
			sf.initCause(e);
1806
			throw sf;
1807
		} catch (OREParserException e) {
1808
			// report as service failure
1809
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1810
			sf.initCause(e);
1811
			throw sf;
1812
		} catch (ORESerialiserException e) {
1813
			// report as service failure
1814
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1815
			sf.initCause(e);
1816
			throw sf;
1817
		} catch (NoSuchAlgorithmException e) {
1818
			// report as service failure
1819
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1820
			sf.initCause(e);
1821
			throw sf;
1822
		} catch (SQLException e) {
1823
            // report as service failure
1824
            ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
1825
            sf.initCause(e);
1826
            throw sf;
1827
        }
1828
		
1829
		return newIdentifier;
1830
	}
1831
	
1832
	/**
1833
	 * Determines if we already have registered an ORE map for this package
1834
	 * NOTE: uses a solr query to locate OREs for the object
1835
	 * @param guid of the EML/packaging object
1836
	 * @return list of resource map identifiers for the given pid
1837
	 */
1838
	public List<Identifier> lookupOreFor(Identifier guid, boolean includeObsolete) {
1839
		// Search for the ORE if we can find it
1840
		String pid = guid.getValue();
1841
		List<Identifier> retList = null;
1842
		try {
1843
			String query = "fl=id,resourceMap&wt=xml&q=-obsoletedBy:[* TO *]+resourceMap:[* TO *]+id:\"" + pid + "\"";
1844
			if (includeObsolete) {
1845
				query = "fl=id,resourceMap&wt=xml&q=resourceMap:[* TO *]+id:\"" + pid + "\"";
1846
			}
1847
			
1848
			InputStream results = this.query(null, "solr", query);
1849
			org.w3c.dom.Node rootNode = XMLUtilities.getXMLReaderAsDOMTreeRootNode(new InputStreamReader(results, "UTF-8"));
1850
			//String resultString = XMLUtilities.getDOMTreeAsString(rootNode);
1851
			org.w3c.dom.NodeList nodeList = XMLUtilities.getNodeListWithXPath(rootNode, "//arr[@name=\"resourceMap\"]/str");
1852
			if (nodeList != null && nodeList.getLength() > 0) {
1853
				retList = new ArrayList<Identifier>();
1854
				for (int i = 0; i < nodeList.getLength(); i++) {
1855
					String found = nodeList.item(i).getFirstChild().getNodeValue();
1856
					Identifier oreId = new Identifier();
1857
					oreId.setValue(found);
1858
					retList.add(oreId);
1859
				}
1860
			}
1861
		} catch (Exception e) {
1862
			logMetacat.error("Error checking for resourceMap[s] on pid " + pid + ". " + e.getMessage(), e);
1863
		}
1864
		
1865
		return retList;
1866
	}
1867
	
1868

    
1869
	@Override
1870
	public InputStream getPackage(Session session, ObjectFormatIdentifier formatId,
1871
			Identifier pid) throws InvalidToken, ServiceFailure,
1872
			NotAuthorized, InvalidRequest, NotImplemented, NotFound {
1873
		InputStream bagInputStream = null;
1874
		BagFactory bagFactory = new BagFactory();
1875
		Bag bag = bagFactory.createBag();
1876
		
1877
		// track the temp files we use so we can delete them when finished
1878
		List<File> tempFiles = new ArrayList<File>();
1879
		
1880
		// the pids to include in the package
1881
		List<Identifier> packagePids = new ArrayList<Identifier>();
1882
		
1883
		// catch non-D1 service errors and throw as ServiceFailures
1884
		try {
1885
			//Create a map of dataone ids and file names
1886
			Map<Identifier, String> fileNames = new HashMap<Identifier, String>();
1887
			
1888
			// track the pid-to-file mapping
1889
			StringBuffer pidMapping = new StringBuffer();
1890
			
1891
			// find the package contents
1892
			SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
1893
			if (ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId()).getFormatType().equals("RESOURCE")) {
1894
				//Get the resource map as a map of Identifiers
1895
				InputStream oreInputStream = this.get(session, pid);
1896
				Map<Identifier, Map<Identifier, List<Identifier>>> resourceMapStructure = ResourceMapFactory.getInstance().parseResourceMap(oreInputStream);
1897
				packagePids.addAll(resourceMapStructure.keySet());
1898
				//Loop through each object in this resource map
1899
				for (Map<Identifier, List<Identifier>> entries: resourceMapStructure.values()) {
1900
					//Loop through each metadata object in this entry
1901
					Set<Identifier> metadataIdentifiers = entries.keySet();
1902
					for(Identifier metadataID: metadataIdentifiers){
1903
						try{
1904
							//Get the system metadata for this metadata object
1905
							SystemMetadata metadataSysMeta = this.getSystemMetadata(session, metadataID);
1906
							
1907
							// include user-friendly metadata
1908
							if (ObjectFormatCache.getInstance().getFormat(metadataSysMeta.getFormatId()).getFormatType().equals("METADATA")) {
1909
								InputStream metadataStream = this.get(session, metadataID);
1910
							
1911
								try {
1912
									// transform
1913
						            String format = "default";
1914

    
1915
									DBTransform transformer = new DBTransform();
1916
						            String documentContent = IOUtils.toString(metadataStream, "UTF-8");
1917
						            String sourceType = metadataSysMeta.getFormatId().getValue();
1918
						            String targetType = "-//W3C//HTML//EN";
1919
						            ByteArrayOutputStream baos = new ByteArrayOutputStream();
1920
						            Writer writer = new OutputStreamWriter(baos , "UTF-8");
1921
						            // TODO: include more params?
1922
						            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
1923
						            String localId = null;
1924
									try {
1925
										localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
1926
									} catch (McdbDocNotFoundException e) {
1927
										throw new NotFound("1020", e.getMessage());
1928
									}
1929
									params.put("qformat", new String[] {format});	            
1930
						            params.put("docid", new String[] {localId});
1931
						            params.put("pid", new String[] {pid.getValue()});
1932
						            params.put("displaymodule", new String[] {"printall"});
1933
						            
1934
						            transformer.transformXMLDocument(
1935
						                    documentContent , 
1936
						                    sourceType, 
1937
						                    targetType , 
1938
						                    format, 
1939
						                    writer, 
1940
						                    params, 
1941
						                    null //sessionid
1942
						                    );
1943
						            
1944
						            // finally, get the HTML back
1945
						            ContentTypeByteArrayInputStream resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
1946
						            
1947
						            // write to temp file with correct css path
1948
						            File tmpDir = File.createTempFile("package_", "_dir");
1949
						            tmpDir.delete();
1950
						            tmpDir.mkdir();
1951
						            File htmlFile = File.createTempFile("metadata", ".html", tmpDir);
1952
						            File cssDir = new File(tmpDir, format);
1953
						            cssDir.mkdir();
1954
						            File cssFile = new File(tmpDir, format + "/" + format + ".css");
1955
						            String pdfFileName = metadataID.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-METADATA.pdf";
1956
						            File pdfFile = new File(tmpDir, pdfFileName);
1957
						            //File pdfFile = File.createTempFile("metadata", ".pdf", tmpDir);
1958
						            
1959
						            // put the CSS file in place for the html to find it
1960
						            String originalCssPath = SystemUtil.getContextDir() + "/style/skins/" + format + "/" + format + ".css";
1961
						            IOUtils.copy(new FileInputStream(originalCssPath), new FileOutputStream(cssFile));
1962
						            
1963
						            // write the HTML file
1964
						            IOUtils.copy(resultInputStream, new FileOutputStream(htmlFile));
1965
						            
1966
						            // convert to PDF
1967
						            HtmlToPdf.export(htmlFile.getAbsolutePath(), pdfFile.getAbsolutePath());
1968
						            
1969
						            //add to the package
1970
						            bag.addFileToPayload(pdfFile);
1971
									pidMapping.append(metadataID.getValue() + " (pdf)" +  "\t" + "data/" + pdfFile.getName() + "\n");
1972
						            
1973
						            // mark for clean up after we are done
1974
									htmlFile.delete();
1975
									cssFile.delete();
1976
									cssDir.delete();
1977
						            tempFiles.add(tmpDir);
1978
									tempFiles.add(pdfFile); // delete this first later on
1979
						            
1980
								} catch (Exception e) {
1981
									logMetacat.warn("Could not transform metadata", e);
1982
								}
1983
							}
1984

    
1985
							
1986
							//If this is in eml format, extract the filename and GUID from each entity in its package
1987
							if (metadataSysMeta.getFormatId().getValue().startsWith("eml://")) {
1988
								//Get the package
1989
								DataPackageParserInterface parser = new Eml200DataPackageParser();
1990
								InputStream emlStream = this.get(session, metadataID);
1991
								parser.parse(emlStream);
1992
								DataPackage dataPackage = parser.getDataPackage();
1993
								
1994
								//Get all the entities in this package and loop through each to extract its ID and file name
1995
								Entity[] entities = dataPackage.getEntityList();
1996
								for(Entity entity: entities){
1997
									try{
1998
										//Get the file name from the metadata
1999
										String fileNameFromMetadata = entity.getName();
2000
										
2001
										//Get the ecogrid URL from the metadata
2002
										String ecogridIdentifier = entity.getEntityIdentifier();
2003
										//Parse the ecogrid URL to get the local id
2004
										String idFromMetadata = DocumentUtil.getAccessionNumberFromEcogridIdentifier(ecogridIdentifier);
2005
										
2006
										//Get the docid and rev pair
2007
										String docid = DocumentUtil.getDocIdFromString(idFromMetadata);
2008
										String rev = DocumentUtil.getRevisionStringFromString(idFromMetadata);
2009
										
2010
										//Get the GUID
2011
										String guid = IdentifierManager.getInstance().getGUID(docid, Integer.valueOf(rev));
2012
										Identifier dataIdentifier = new Identifier();
2013
										dataIdentifier.setValue(guid);
2014
										
2015
										//Add the GUID to our GUID & file name map
2016
										fileNames.put(dataIdentifier, fileNameFromMetadata);
2017
									}
2018
									catch(Exception e){
2019
										//Prevent just one entity error
2020
										e.printStackTrace();
2021
										logMetacat.debug(e.getMessage(), e);
2022
									}
2023
								}
2024
							}
2025
						}
2026
						catch(Exception e){
2027
							//Catch errors that would prevent package download
2028
							logMetacat.debug(e.toString());
2029
						}
2030
					}
2031
					packagePids.addAll(entries.keySet());
2032
					for (List<Identifier> dataPids: entries.values()) {
2033
						packagePids.addAll(dataPids);
2034
					}
2035
				}
2036
			} else {
2037
				// just the lone pid in this package
2038
				packagePids.add(pid);
2039
			}
2040
			
2041
			//Create a temp file, then delete it and make a directory with that name
2042
			File tempDir = File.createTempFile("temp", Long.toString(System.nanoTime()));
2043
			tempDir.delete();
2044
			tempDir = new File(tempDir.getPath() + "_dir");
2045
			tempDir.mkdir();			
2046
			tempFiles.add(tempDir);
2047
			File pidMappingFile = new File(tempDir, "pid-mapping.txt");
2048
			
2049
			// loop through the package contents
2050
			for (Identifier entryPid: packagePids) {
2051
				//Get the system metadata for each item
2052
				SystemMetadata entrySysMeta = this.getSystemMetadata(session, entryPid);					
2053
				
2054
				String objectFormatType = ObjectFormatCache.getInstance().getFormat(entrySysMeta.getFormatId()).getFormatType();
2055
				String fileName = null;
2056
				
2057
				//TODO: Be more specific of what characters to replace. Make sure periods arent replaced for the filename from metadata
2058
				//Our default file name is just the ID + format type (e.g. walker.1.1-DATA)
2059
				fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + objectFormatType;
2060

    
2061
				if(fileNames.containsKey(entryPid)){
2062
					//Let's use the file name and extension from the metadata is we have it
2063
					fileName = entryPid.getValue().replaceAll("[^a-zA-Z0-9\\-\\.]", "_") + "-" + fileNames.get(entryPid).replaceAll("[^a-zA-Z0-9\\-\\.]", "_");
2064
				}
2065
				else{
2066
					//If we couldn't find a given file name, use the system metadata extension
2067
					String extension = ObjectFormatInfo.instance().getExtension(entrySysMeta.getFormatId().getValue());
2068
					fileName += extension;
2069
				}
2070
				
2071
		        //Create a new file for this item and add to the list
2072
				File tempFile = new File(tempDir, fileName);
2073
				tempFiles.add(tempFile);
2074
				
2075
				InputStream entryInputStream = this.get(session, entryPid);			
2076
				IOUtils.copy(entryInputStream, new FileOutputStream(tempFile));
2077
				bag.addFileToPayload(tempFile);
2078
				pidMapping.append(entryPid.getValue() + "\t" + "data/" + tempFile.getName() + "\n");
2079
			}
2080
			
2081
			//add the the pid to data file map
2082
			IOUtils.write(pidMapping.toString(), new FileOutputStream(pidMappingFile));
2083
			bag.addFileAsTag(pidMappingFile);
2084
			tempFiles.add(pidMappingFile);
2085
			
2086
			bag = bag.makeComplete();
2087
			
2088
			///Now create the zip file
2089
			//Use the pid as the file name prefix, replacing all non-word characters
2090
			String zipName = pid.getValue().replaceAll("\\W", "_");
2091
			
2092
			File bagFile = new File(tempDir, zipName+".zip");
2093
			
2094
			bag.setFile(bagFile);
2095
			ZipWriter zipWriter = new ZipWriter(bagFactory);
2096
			bag.write(zipWriter, bagFile);
2097
			bagFile = bag.getFile();
2098
			// use custom FIS that will delete the file when closed
2099
			bagInputStream = new DeleteOnCloseFileInputStream(bagFile);
2100
			// also mark for deletion on shutdown in case the stream is never closed
2101
			bagFile.deleteOnExit();
2102
			tempFiles.add(bagFile);
2103
			
2104
			// clean up other temp files
2105
			for (int i=tempFiles.size()-1; i>=0; i--){
2106
				tempFiles.get(i).delete();
2107
			}
2108
			
2109
		} catch (IOException e) {
2110
			// report as service failure
2111
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2112
			sf.initCause(e);
2113
			throw sf;
2114
		} catch (OREException e) {
2115
			// report as service failure
2116
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2117
			sf.initCause(e);
2118
			throw sf;
2119
		} catch (URISyntaxException e) {
2120
			// report as service failure
2121
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2122
			sf.initCause(e);
2123
			throw sf;
2124
		} catch (OREParserException e) {
2125
			// report as service failure
2126
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2127
			sf.initCause(e);
2128
			throw sf;
2129
		}
2130
		
2131
		return bagInputStream;
2132
	}
2133

    
2134
	@Override
2135
	public OptionList listViews(Session arg0) throws InvalidToken,
2136
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented {
2137
		OptionList views = new OptionList();
2138
		Vector<String> skinNames = null;
2139
		try {
2140
			skinNames = SkinUtil.getSkinNames();
2141
		} catch (PropertyNotFoundException e) {
2142
			throw new ServiceFailure("2841", e.getMessage());
2143
		}
2144
		for (String skinName: skinNames) {
2145
			views.addOption(skinName);
2146
		}
2147
		return views;
2148
	}
2149

    
2150
	@Override
2151
	public InputStream view(Session session, String format, Identifier pid)
2152
			throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
2153
			NotImplemented, NotFound {
2154
		InputStream resultInputStream = null;
2155
		
2156
		SystemMetadata sysMeta = this.getSystemMetadata(session, pid);
2157
		InputStream object = this.get(session, pid);
2158

    
2159
		try {
2160
			// can only transform metadata, really
2161
			ObjectFormat objectFormat = ObjectFormatCache.getInstance().getFormat(sysMeta.getFormatId());
2162
			if (objectFormat.getFormatType().equals("METADATA")) {
2163
				// transform
2164
				DBTransform transformer = new DBTransform();
2165
	            String documentContent = IOUtils.toString(object, "UTF-8");
2166
	            String sourceType = objectFormat.getFormatId().getValue();
2167
	            String targetType = "-//W3C//HTML//EN";
2168
	            ByteArrayOutputStream baos = new ByteArrayOutputStream();
2169
	            Writer writer = new OutputStreamWriter(baos , "UTF-8");
2170
	            // TODO: include more params?
2171
	            Hashtable<String, String[]> params = new Hashtable<String, String[]>();
2172
	            String localId = null;
2173
				try {
2174
					localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
2175
				} catch (McdbDocNotFoundException e) {
2176
					throw new NotFound("1020", e.getMessage());
2177
				}
2178
	            params.put("qformat", new String[] {format});	            
2179
	            params.put("docid", new String[] {localId});
2180
	            params.put("pid", new String[] {pid.getValue()});
2181
	            transformer.transformXMLDocument(
2182
	                    documentContent , 
2183
	                    sourceType, 
2184
	                    targetType , 
2185
	                    format, 
2186
	                    writer, 
2187
	                    params, 
2188
	                    null //sessionid
2189
	                    );
2190
	            
2191
	            // finally, get the HTML back
2192
	            resultInputStream = new ContentTypeByteArrayInputStream(baos.toByteArray());
2193
	            ((ContentTypeByteArrayInputStream) resultInputStream).setContentType("text/html");
2194
	
2195
			} else {
2196
				// just return the raw bytes
2197
				resultInputStream = object;
2198
			}
2199
		} catch (IOException e) {
2200
			// report as service failure
2201
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2202
			sf.initCause(e);
2203
			throw sf;
2204
		} catch (PropertyNotFoundException e) {
2205
			// report as service failure
2206
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2207
			sf.initCause(e);
2208
			throw sf;
2209
		} catch (SQLException e) {
2210
			// report as service failure
2211
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2212
			sf.initCause(e);
2213
			throw sf;
2214
		} catch (ClassNotFoundException e) {
2215
			// report as service failure
2216
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
2217
			sf.initCause(e);
2218
			throw sf;
2219
		}
2220
		
2221
		return resultInputStream;
2222
	}	
2223
    
2224
}
(4-4/7)