Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author: $'
7
 *     '$Date: 2009-06-13 15:28:13 +0300  $'
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
package edu.ucsb.nceas.metacat.dataone;
24

    
25
import java.io.ByteArrayOutputStream;
26
import java.io.File;
27
import java.io.FileNotFoundException;
28
import java.io.FileOutputStream;
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.io.OutputStream;
32
import java.io.PrintWriter;
33
import java.sql.SQLException;
34
import java.util.Date;
35
import java.util.Enumeration;
36
import java.util.Hashtable;
37
import java.util.Timer;
38
import java.util.List;
39

    
40
import javax.servlet.ServletContext;
41
import javax.servlet.http.HttpServletRequest;
42
import javax.servlet.http.HttpServletResponse;
43

    
44
import org.apache.commons.io.IOUtils;
45
import org.apache.log4j.Logger;
46
import org.dataone.service.exceptions.IdentifierNotUnique;
47
import org.dataone.service.exceptions.InsufficientResources;
48
import org.dataone.service.exceptions.InvalidRequest;
49
import org.dataone.service.exceptions.InvalidSystemMetadata;
50
import org.dataone.service.exceptions.InvalidToken;
51
import org.dataone.service.exceptions.NotAuthorized;
52
import org.dataone.service.exceptions.NotFound;
53
import org.dataone.service.exceptions.NotImplemented;
54
import org.dataone.service.exceptions.ServiceFailure;
55
import org.dataone.service.exceptions.UnsupportedType;
56
import org.dataone.service.mn.MemberNodeCrud;
57
import org.dataone.service.types.AuthToken;
58
import org.dataone.service.types.Checksum;
59
import org.dataone.service.types.DescribeResponse;
60
import org.dataone.service.types.Identifier;
61
import org.dataone.service.types.LogRecordSet;
62
import org.dataone.service.types.SystemMetadata;
63
import org.dataone.service.types.ObjectList;
64
import org.dataone.service.types.ObjectFormat;
65
import org.jibx.runtime.BindingDirectory;
66
import org.jibx.runtime.IBindingFactory;
67
import org.jibx.runtime.IMarshallingContext;
68
import org.jibx.runtime.IUnmarshallingContext;
69
import org.jibx.runtime.JiBXException;
70

    
71
import com.gc.iotools.stream.is.InputStreamFromOutputStream;
72

    
73
import edu.ucsb.nceas.metacat.AccessionNumberException;
74
import edu.ucsb.nceas.metacat.DocumentImpl;
75
import edu.ucsb.nceas.metacat.EventLog;
76
import edu.ucsb.nceas.metacat.IdentifierManager;
77
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
78
import edu.ucsb.nceas.metacat.McdbException;
79
import edu.ucsb.nceas.metacat.MetacatHandler;
80
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
81
import edu.ucsb.nceas.metacat.properties.PropertyService;
82
import edu.ucsb.nceas.metacat.replication.ForceReplicationHandler;
83
import edu.ucsb.nceas.metacat.service.SessionService;
84
import edu.ucsb.nceas.metacat.util.DocumentUtil;
85
import edu.ucsb.nceas.metacat.util.SessionData;
86
import edu.ucsb.nceas.utilities.ParseLSIDException;
87
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
88

    
89
/**
90
 * 
91
 * Implements DataONE MemberNode CRUD API for Metacat. 
92
 * 
93
 * @author Matthew Jones
94
 */
95
public class CrudService implements MemberNodeCrud {
96

    
97
    /*private ServletContext servletContext;
98
    private HttpServletRequest request;
99
    private HttpServletResponse response;*/
100
    
101
    private static CrudService crudService = null;
102

    
103
    private MetacatHandler handler;
104
    private Hashtable<String, String[]> params;
105
    Logger logMetacat = null;
106
    
107
    private String metacatUrl;
108

    
109
    /**
110
     * singleton accessor
111
     */
112
    public static CrudService getInstance()
113
    {
114
      if(crudService == null)
115
      {
116
        crudService = new CrudService();
117
      }
118
      
119
      return crudService;
120
    }
121
    
122
    /**
123
     * Initializes new instance by setting servlet context,request and response.
124
     * TODO: remove dependency on Servlet infrastructure
125
     * TODO: Make this a real service, and make it a Singleton
126
     */
127
    public CrudService() {
128
    //change crud service into a singleton.  dont pass servlet data structures here
129
        logMetacat = Logger.getLogger(CrudService.class);
130
        try
131
        {
132
            String server = PropertyService.getProperty("server.name");
133
            String port = PropertyService.getProperty("server.httpPort");
134
            String context = PropertyService.getProperty("application.context");
135
            metacatUrl = "http://" + server + ":" + port + "/" + context;
136
            logMetacat.debug("Initializing CrudService with url " + metacatUrl);
137
        }
138
        catch(Exception e)
139
        {
140
            logMetacat.error("Could not find servlet url in CrudService: " + e.getMessage());
141
            e.printStackTrace();
142
            throw new RuntimeException("Error getting servlet url in CrudService: " + e.getMessage());
143
        }
144
        
145
        /*this.servletContext = servletContext;
146
        this.request = request;
147
        this.response = response;*/
148
        
149
        params = new Hashtable<String, String[]>();
150

    
151
        handler = new MetacatHandler(new Timer());
152

    
153
    }
154
    
155
    /**
156
     * return the context url CrudService is using.
157
     */
158
    public String getContextUrl()
159
    {
160
        return metacatUrl;
161
    }
162
    
163
    /**
164
     * set the params for this service from an HttpServletRequest param list
165
     */
166
    public void setParamsFromRequest(HttpServletRequest request)
167
    {
168
        Enumeration paramlist = request.getParameterNames();
169
        while (paramlist.hasMoreElements()) {
170
            String name = (String) paramlist.nextElement();
171
            String[] value = (String[])request.getParameterValues(name);
172
            params.put(name, value);
173
        }
174
    }
175
    
176
    /**
177
     * set the parameter values needed for this request
178
     */
179
    public void setParameter(String name, String[] value)
180
    {
181
        params.put(name, value);
182
    }
183
    
184
    public Identifier create(AuthToken token, Identifier guid, 
185
            InputStream object, SystemMetadata sysmeta) throws InvalidToken, 
186
            ServiceFailure, NotAuthorized, IdentifierNotUnique, UnsupportedType, 
187
            InsufficientResources, InvalidSystemMetadata, NotImplemented {
188

    
189
        logMetacat.debug("Starting CrudService.create()...");
190
        
191
        // authenticate & get user info
192
        SessionData sessionData = getSessionData(token);
193
        String username = sessionData.getUserName();
194
        String[] groups = sessionData.getGroupNames();
195

    
196
        // verify that guid == SystemMetadata.getIdentifier()
197
        logMetacat.debug("Comparing guid|sysmeta_guid: " + guid.getValue() + "|" + sysmeta.getIdentifier().getValue());
198
        if (!guid.getValue().equals(sysmeta.getIdentifier().getValue())) {
199
            throw new InvalidSystemMetadata("1180", 
200
                "GUID in method call does not match GUID in system metadata.");
201
        }
202

    
203
        logMetacat.debug("Checking if identifier exists...");
204
        // Check that the identifier does not already exist
205
        IdentifierManager im = IdentifierManager.getInstance();
206
        if (im.identifierExists(guid.getValue())) {
207
            throw new IdentifierNotUnique("1120", 
208
                "GUID is already in use by an existing object.");
209
        }
210

    
211
        // Check if we are handling metadata or data
212
        boolean isScienceMetadata = isScienceMetadata(sysmeta);
213
        
214
        if (isScienceMetadata) {
215
            // CASE METADATA:
216
            try {
217
                this.insertDocument(object, guid, sessionData);
218
            } catch (IOException e) {
219
                String msg = "Could not create string from XML stream: " +
220
                    " " + e.getMessage();
221
                logMetacat.debug(msg);
222
                throw new ServiceFailure("1190", msg);
223
            }
224

    
225
        } else {
226
            // DEFAULT CASE: DATA (needs to be checked and completed)
227
            insertDataObject(object, guid, sessionData);
228
            
229
        }
230

    
231
        // For Metadata and Data, insert the system metadata into the object store too
232
        insertSystemMetadata(sysmeta, sessionData);
233

    
234
        logMetacat.debug("Returning from CrudService.create()");
235
        return guid;
236
    }
237
    
238
    /**
239
     * update an existing object with a new object.  Change the system metadata
240
     * to reflect the changes and update it as well.
241
     */
242
    public Identifier update(AuthToken token, Identifier guid, 
243
            InputStream object, Identifier obsoletedGuid, SystemMetadata sysmeta) 
244
            throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
245
            UnsupportedType, InsufficientResources, NotFound, InvalidSystemMetadata, 
246
            NotImplemented {
247
        try
248
        {
249
            SessionData sessionData = getSessionData(token);
250
            
251
            //find the old systemmetadata (sm.old) document id (the one linked to obsoletedGuid)
252
            SystemMetadata sm = getSystemMetadata(token, obsoletedGuid);
253
            //change sm.old's obsoletedBy field 
254
            List l = sm.getObsoletedByList();
255
            l.add(guid);
256
            sm.setObsoletedByList(l);
257
            //update sm.old
258
            updateSystemMetadata(sm, sessionData);
259
            
260
            //change the obsoletes field of the new systemMetadata (sm.new) to point to the id of the old one
261
            sysmeta.addObsolete(obsoletedGuid);
262
            //insert sm.new
263
            insertSystemMetadata(sysmeta, sessionData);
264
            
265
            boolean isScienceMetadata = isScienceMetadata(sysmeta);
266
            if(isScienceMetadata)
267
            {
268
                //update the doc
269
                updateDocument(object, obsoletedGuid, guid, sessionData);
270
            }
271
            else
272
            {
273
                //update a data file, not xml
274
                insertDataObject(object, guid, sessionData);
275
            }
276
            return guid;
277
        }
278
        catch(Exception e)
279
        {
280
            throw new ServiceFailure("1030", "Error updating document in CrudService: " + e.getMessage());
281
        }
282
    }
283
    
284
    /**
285
     *  Retrieve the list of objects present on the MN that match the calling 
286
     *  parameters. This method is required to support the process of Member 
287
     *  Node synchronization. At a minimum, this method should be able to 
288
     *  return a list of objects that match:
289
     *  startTime <= SystemMetadata.dateSysMetadataModified
290
     *  but is expected to also support date range (by also specifying endTime), 
291
     *  and should also support slicing of the matching set of records by 
292
     *  indicating the starting index of the response (where 0 is the index 
293
     *  of the first item) and the count of elements to be returned.
294
     *  
295
     * @see http://mule1.dataone.org/ArchitectureDocs/mn_api_replication.html#MN_replication.listObjects
296
     * @param token
297
     * @param startTime
298
     * @param endTime
299
     * @param objectFormat
300
     * @param replicaStatus
301
     * @param start
302
     * @param count
303
     * @return ObjectList
304
     * @throws NotAuthorized
305
     * @throws InvalidRequest
306
     * @throws NotImplemented
307
     * @throws ServiceFailure
308
     * @throws InvalidToken
309
     */
310
    public ObjectList listObjects(AuthToken token, Date startTime, Date endTime, 
311
        ObjectFormat objectFormat, boolean replicaStatus, int start, int count)
312
      throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken
313
    {
314
      ObjectList ol = new ObjectList();
315
      //do an squery on metacat to return systemMetadata docs where 
316
      //sm.dateSysMetadataModified >= startTime
317
      //sm.dateSysMetadataModified <= endTime
318
      //further slice the returned dataset if replicaStatus is false.
319
      //if start != 0, return only the documents with index >= start
320
      //if the number of results is > count, return the first count docs
321
      return ol;
322
    }
323
    
324
    /**
325
     * Call listObjects with the default values for replicaStatus (true), start (0),
326
     * and count (1000).
327
     * @param token
328
     * @param startTime
329
     * @param endTime
330
     * @param objectFormat
331
     * @return
332
     * @throws NotAuthorized
333
     * @throws InvalidRequest
334
     * @throws NotImplemented
335
     * @throws ServiceFailure
336
     * @throws InvalidToken
337
     */
338
    public ObjectList listObjects(AuthToken token, Date startTime, Date endTime, 
339
        ObjectFormat objectFormat)
340
      throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken
341
    {
342
       return listObjects(token, startTime, endTime, objectFormat, true, 0, 1000);
343
    }
344

    
345
    /**
346
     * Delete a document.  NOT IMPLEMENTED
347
     */
348
    public Identifier delete(AuthToken token, Identifier guid)
349
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
350
            NotImplemented {
351
        throw new NotImplemented("1000", "This method not yet implemented.");
352
    }
353

    
354
    /**
355
     * describe a document.  NOT IMPLEMENTED
356
     */
357
    public DescribeResponse describe(AuthToken token, Identifier guid)
358
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
359
            NotImplemented {
360
        throw new NotImplemented("1000", "This method not yet implemented.");
361
    }
362
    
363
    /**
364
     * get a document with a specified guid.
365
     */
366
    public InputStream get(AuthToken token, Identifier guid)
367
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
368
            NotImplemented {
369
        
370
        // Retrieve the session information from the AuthToken
371
        // If the session is expired, then the user is 'public'
372
        final SessionData sessionData = getSessionData(token);
373
        
374
        // Look up the localId for this global identifier
375
        IdentifierManager im = IdentifierManager.getInstance();
376
        try {
377
            final String localId = im.getLocalId(guid.getValue());
378

    
379
            final InputStreamFromOutputStream<String> objectStream = 
380
                new InputStreamFromOutputStream<String>() {
381
                
382
                @Override
383
                public String produce(final OutputStream dataSink) throws Exception {
384

    
385
                    try {
386
                        handler.readFromMetacat(metacatUrl, null, 
387
                                dataSink, localId, "xml",
388
                                sessionData.getUserName(), 
389
                                sessionData.getGroupNames(), true, params);
390
                    } catch (PropertyNotFoundException e) {
391
                        throw new ServiceFailure("1030", e.getMessage());
392
                    } catch (ClassNotFoundException e) {
393
                        throw new ServiceFailure("1030", e.getMessage());
394
                    } catch (IOException e) {
395
                        throw new ServiceFailure("1030", e.getMessage());
396
                    } catch (SQLException e) {
397
                        throw new ServiceFailure("1030", e.getMessage());
398
                    } catch (McdbException e) {
399
                        throw new ServiceFailure("1030", e.getMessage());
400
                    } catch (ParseLSIDException e) {
401
                        throw new NotFound("1020", e.getMessage());
402
                    } catch (InsufficientKarmaException e) {
403
                        throw new NotAuthorized("1000", "Not authorized for get().");
404
                    }
405

    
406
                    return "Completed";
407
                }
408
            };
409
            return objectStream;
410

    
411
        } catch (McdbDocNotFoundException e) {
412
            throw new NotFound("1020", e.getMessage());
413
        }
414
    }
415

    
416
    /**
417
     * get the checksum for a document.  NOT IMPLEMENTED
418
     */
419
    public Checksum getChecksum(AuthToken token, Identifier guid)
420
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
421
            InvalidRequest, NotImplemented {
422
        throw new NotImplemented("1000", "This method not yet implemented.");
423
    }
424

    
425
    /**
426
     * get the checksum for a document.  NOT IMPLEMENTED
427
     */
428
    public Checksum getChecksum(AuthToken token, Identifier guid, 
429
            String checksumAlgorithm) throws InvalidToken, ServiceFailure, 
430
            NotAuthorized, NotFound, InvalidRequest, NotImplemented {
431
        throw new NotImplemented("1000", "This method not yet implemented.");
432
    }
433

    
434
    /**
435
     * get log records.  NOT IMPLEMENTED
436
     */
437
    public LogRecordSet getLogRecords(AuthToken token, Date fromDate, Date toDate)
438
            throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest, 
439
            NotImplemented {
440
        throw new NotImplemented("1000", "This method not yet implemented.");
441
    }
442

    
443
    /**
444
     * get the system metadata for a document with a specified guid.
445
     */
446
    public SystemMetadata getSystemMetadata(AuthToken token, Identifier guid)
447
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
448
            InvalidRequest, NotImplemented {
449
        
450
        logMetacat.debug("CrudService.getSystemMetadata - for guid: " + guid.getValue());
451
        
452
        // Retrieve the session information from the AuthToken
453
        // If the session is expired, then the user is 'public'
454
        final SessionData sessionData = getSessionData(token);
455

    
456
        // TODO: Check access control rules
457
                
458
        try {
459
            IdentifierManager im = IdentifierManager.getInstance();
460
            final String localId = im.getSystemMetadataId(guid.getValue());
461
            
462
            // Read system metadata from metacat's db
463
            final InputStreamFromOutputStream<String> objectStream = 
464
                new InputStreamFromOutputStream<String>() {
465
                
466
                @Override
467
                public String produce(final OutputStream dataSink) throws Exception {
468
                    try {
469
                        handler.readFromMetacat(metacatUrl, null, 
470
                                dataSink, localId, "xml",
471
                                sessionData.getUserName(), 
472
                                sessionData.getGroupNames(), true, params);
473
                    } catch (PropertyNotFoundException e) {
474
                        throw new ServiceFailure("1030", e.getMessage());
475
                    } catch (ClassNotFoundException e) {
476
                        throw new ServiceFailure("1030", e.getMessage());
477
                    } catch (IOException e) {
478
                        throw new ServiceFailure("1030", e.getMessage());
479
                    } catch (SQLException e) {
480
                        throw new ServiceFailure("1030", e.getMessage());
481
                    } catch (McdbException e) {
482
                        throw new ServiceFailure("1030", e.getMessage());
483
                    } catch (ParseLSIDException e) {
484
                        throw new NotFound("1020", e.getMessage());
485
                    } catch (InsufficientKarmaException e) {
486
                        throw new NotAuthorized("1000", "Not authorized for get().");
487
                    }
488

    
489
                    return "Completed";
490
                }
491
            };
492
            
493
            // Deserialize the xml to create a SystemMetadata object
494
            SystemMetadata sysmeta = deserializeSystemMetadata(objectStream);
495
            return sysmeta;
496
            
497
        } catch (McdbDocNotFoundException e) {
498
            //e.printStackTrace();
499
            throw new NotFound("1000", e.getMessage());
500
        }                
501
    }
502

    
503
    /*
504
     * Look up the information on the session using the token provided in
505
     * the AuthToken.  The Session should have all relevant user information.
506
     * If the session has expired or is invalid, the 'public' session will
507
     * be returned, giving the user anonymous access.
508
     */
509
    private static SessionData getSessionData(AuthToken token) {
510
        SessionData sessionData = null;
511
        String sessionId = "PUBLIC";
512
        if (token != null) {
513
            sessionId = token.getToken();
514
        }
515
        
516
        // if the session id is registered in SessionService, get the
517
        // SessionData for it. Otherwise, use the public session.
518
        if (SessionService.isSessionRegistered(sessionId)) {
519
            sessionData = SessionService.getRegisteredSession(sessionId);
520
        } else {
521
            sessionData = SessionService.getPublicSession();
522
        }
523
        
524
        return sessionData;
525
    }
526

    
527
    /** 
528
     * Determine if a given object should be treated as an XML science metadata
529
     * object. 
530
     * 
531
     * TODO: This test should be externalized in a configuration dictionary rather than being hardcoded.
532
     * 
533
     * @param sysmeta the SystemMetadata describig the object
534
     * @return true if the object should be treated as science metadata
535
     */
536
    private boolean isScienceMetadata(SystemMetadata sysmeta) {
537
        boolean scimeta = false;
538
        switch (sysmeta.getObjectFormat()) {
539
            case EML_2_1_0: scimeta = true; break;
540
            case EML_2_0_1: scimeta = true; break;
541
            case EML_2_0_0: scimeta = true; break;
542
            case FGDC_STD_001_1_1999: scimeta = true; break;
543
            case FGDC_STD_001_1998: scimeta = true; break;
544
            case NCML_2_2: scimeta = true; break;
545
        }
546
        
547
        return scimeta;
548
    }
549

    
550
    /**
551
     * insert a data doc
552
     * @param object
553
     * @param guid
554
     * @param sessionData
555
     * @throws ServiceFailure
556
     */
557
    private void insertDataObject(InputStream object, Identifier guid, 
558
            SessionData sessionData) throws ServiceFailure {
559
        
560
        String username = sessionData.getUserName();
561
        String[] groups = sessionData.getGroupNames();
562

    
563
        // generate guid/localId pair for object
564
        logMetacat.debug("Generating a guid/localId mapping");
565
        IdentifierManager im = IdentifierManager.getInstance();
566
        String localId = im.generateLocalId(guid.getValue(), 1);
567

    
568
        try {
569
            logMetacat.debug("Case DATA: starting to write to disk.");
570
            if (DocumentImpl.getDataFileLockGrant(localId)) {
571
    
572
                // Save the data file to disk using "localId" as the name
573
                try {
574
                    String datafilepath = PropertyService.getProperty("application.datafilepath");
575
    
576
                    File dataDirectory = new File(datafilepath);
577
                    dataDirectory.mkdirs();
578
    
579
                    File newFile = writeStreamToFile(dataDirectory, localId, object);
580
    
581
                    // TODO: Check that the file size matches SystemMetadata
582
                    //                        long size = newFile.length();
583
                    //                        if (size == 0) {
584
                    //                            throw new IOException("Uploaded file is 0 bytes!");
585
                    //                        }
586
    
587
                    // Register the file in the database (which generates an exception
588
                    // if the localId is not acceptable or other untoward things happen
589
                    try {
590
                        logMetacat.debug("Registering document...");
591
                        System.out.println("inserting data object: localId: " + localId);
592
                        DocumentImpl.registerDocument(localId, "BIN", localId,
593
                                username, groups);
594
                        logMetacat.debug("Registration step completed.");
595
                    } catch (SQLException e) {
596
                        //newFile.delete();
597
                        logMetacat.debug("SQLE: " + e.getMessage());
598
                        e.printStackTrace(System.out);
599
                        throw new ServiceFailure("1190", "Registration failed: " + e.getMessage());
600
                    } catch (AccessionNumberException e) {
601
                        //newFile.delete();
602
                        logMetacat.debug("ANE: " + e.getMessage());
603
                        e.printStackTrace(System.out);
604
                        throw new ServiceFailure("1190", "Registration failed: " + e.getMessage());
605
                    } catch (Exception e) {
606
                        //newFile.delete();
607
                        logMetacat.debug("Exception: " + e.getMessage());
608
                        e.printStackTrace(System.out);
609
                        throw new ServiceFailure("1190", "Registration failed: " + e.getMessage());
610
                    }
611
    
612
                    logMetacat.debug("Logging the creation event.");
613
                    EventLog.getInstance().log(metacatUrl,
614
                            username, localId, "create");
615
    
616
                    // Schedule replication for this data file
617
                    logMetacat.debug("Scheduling replication.");
618
                    ForceReplicationHandler frh = new ForceReplicationHandler(
619
                            localId, "insert", false, null);
620
    
621
                } catch (PropertyNotFoundException e) {
622
                    throw new ServiceFailure("1190", "Could not lock file for writing:" + e.getMessage());
623
                }
624
    
625
            }
626
        } catch (Exception e) {
627
            // Could not get a lock on the document, so we can not update the file now
628
            throw new ServiceFailure("1190", "Failed to lock file: " + e.getMessage());
629
        }
630
    }
631

    
632
    /**
633
     * write a file to a stream
634
     * @param dir
635
     * @param fileName
636
     * @param data
637
     * @return
638
     * @throws ServiceFailure
639
     */
640
    private File writeStreamToFile(File dir, String fileName, InputStream data) 
641
        throws ServiceFailure {
642
        
643
        File newFile = new File(dir, fileName);
644
        logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
645

    
646
        try {
647
            if (newFile.createNewFile()) {
648
                // write data stream to desired file
649
                OutputStream os = new FileOutputStream(newFile);
650
                long length = IOUtils.copyLarge(data, os);
651
                os.flush();
652
                os.close();
653
            } else {
654
                logMetacat.debug("File creation failed, or file already exists.");
655
                throw new ServiceFailure("1190", "File already exists: " + fileName);
656
            }
657
        } catch (FileNotFoundException e) {
658
            logMetacat.debug("FNF: " + e.getMessage());
659
            throw new ServiceFailure("1190", "File not found: " + fileName + " " 
660
                    + e.getMessage());
661
        } catch (IOException e) {
662
            logMetacat.debug("IOE: " + e.getMessage());
663
            throw new ServiceFailure("1190", "File was not written: " + fileName 
664
                    + " " + e.getMessage());
665
        }
666

    
667
        return newFile;
668
    }
669

    
670
    /**
671
     * insert a systemMetadata doc
672
     */
673
    private void insertSystemMetadata(SystemMetadata sysmeta, SessionData sessionData) 
674
        throws ServiceFailure {
675
        logMetacat.debug("Starting to insert SystemMetadata...");
676
    
677
        // generate guid/localId pair for sysmeta
678
        Identifier sysMetaGuid = new Identifier();
679
        sysMetaGuid.setValue(DocumentUtil.generateDocumentId(1));
680
        sysmeta.setDateSysMetadataModified(new Date());
681

    
682
        String xml = new String(serializeSystemMetadata(sysmeta).toByteArray());
683
        String localId = insertDocument(xml, sysMetaGuid, sessionData);
684
        //insert the system metadata doc id into the identifiers table to 
685
        //link it to the data or metadata document
686
        IdentifierManager.getInstance().createSystemMetadataMapping(sysmeta.getIdentifier().getValue(), sysMetaGuid.getValue());
687
    }
688
    
689
    /**
690
     * update a systemMetadata doc
691
     */
692
    private void updateSystemMetadata(SystemMetadata sm, SessionData sessionData)
693
      throws ServiceFailure
694
    {
695
        try
696
        {
697
            String smId = IdentifierManager.getInstance().getSystemMetadataId(sm.getIdentifier().getValue());
698
            sm.setDateSysMetadataModified(new Date());
699
            String xml = new String(serializeSystemMetadata(sm).toByteArray());
700
            Identifier id = new Identifier();
701
            id.setValue(smId);
702
            String localId = updateDocument(xml, id, null, sessionData);
703
            IdentifierManager.getInstance().updateSystemMetadataMapping(sm.getIdentifier().getValue(), localId);
704
        }
705
        catch(Exception e)
706
        {
707
            throw new ServiceFailure("1030", "Error updating system metadata: " + e.getMessage());
708
        }
709
    }
710
    
711
    /**
712
     * insert a document
713
     * NOTE: this method shouldn't be used from the update or create() methods.  
714
     * we shouldn't be putting the science metadata or data objects into memory.
715
     */
716
    private String insertDocument(String xml, Identifier guid, SessionData sessionData)
717
        throws ServiceFailure
718
    {
719
        return insertOrUpdateDocument(xml, guid, sessionData, "insert");
720
    }
721
    
722
    /**
723
     * insert a document from a stream
724
     */
725
    private String insertDocument(InputStream is, Identifier guid, SessionData sessionData)
726
      throws IOException, ServiceFailure
727
    {
728
        //HACK: change this eventually.  we should not be converting the stream to a string
729
        String xml = IOUtils.toString(is);
730
        return insertDocument(xml, guid, sessionData);
731
    }
732
    
733
    /**
734
     * update a document
735
     * NOTE: this method shouldn't be used from the update or create() methods.  
736
     * we shouldn't be putting the science metadata or data objects into memory.
737
     */
738
    private String updateDocument(String xml, Identifier obsoleteGuid, Identifier guid, SessionData sessionData)
739
        throws ServiceFailure
740
    {
741
        return insertOrUpdateDocument(xml, obsoleteGuid, sessionData, "update");
742
    }
743
    
744
    /**
745
     * update a document from a stream
746
     */
747
    private String updateDocument(InputStream is, Identifier obsoleteGuid, Identifier guid, SessionData sessionData)
748
      throws IOException, ServiceFailure
749
    {
750
        //HACK: change this eventually.  we should not be converting the stream to a string
751
        String xml = IOUtils.toString(is);
752
        String localId = updateDocument(xml, obsoleteGuid, guid, sessionData);
753
        IdentifierManager im = IdentifierManager.getInstance();
754
        if(guid != null)
755
        {
756
          im.createMapping(guid.getValue(), localId);
757
        }
758
        return localId;
759
    }
760
    
761
    /**
762
     * insert a document, return the id of the document that was inserted
763
     */
764
    private String insertOrUpdateDocument(String xml, Identifier guid, SessionData sessionData, String insertOrUpdate) 
765
        throws ServiceFailure {
766
        logMetacat.debug("Starting to insert xml document...");
767
        IdentifierManager im = IdentifierManager.getInstance();
768

    
769
        // generate guid/localId pair for sysmeta
770
        String localId = null;
771
        if(insertOrUpdate.equals("insert"))
772
        {
773
            localId = im.generateLocalId(guid.getValue(), 1);
774
        }
775
        else
776
        {
777
            //localid should already exist in the identifier table, so just find it
778
            try
779
            {
780
                localId = im.getLocalId(guid.getValue());
781
                //increment the revision
782
                String docid = localId.substring(0, localId.lastIndexOf("."));
783
                String revS = localId.substring(localId.lastIndexOf(".") + 1, localId.length());
784
                int rev = new Integer(revS).intValue();
785
                rev++;
786
                docid = docid + "." + rev;
787
                localId = docid;
788
            }
789
            catch(McdbDocNotFoundException e)
790
            {
791
                throw new ServiceFailure("1030", "CrudService.insertOrUpdateDocument(): " +
792
                    "guid " + guid.getValue() + " should have been in the identifier table, but it wasn't: " + e.getMessage());
793
            }
794
        }
795
        logMetacat.debug("Metadata guid|localId: " + guid.getValue() + "|" +
796
                localId);
797

    
798
        String[] action = new String[1];
799
        action[0] = insertOrUpdate;
800
        params.put("action", action);
801
        String[] docid = new String[1];
802
        docid[0] = localId;
803
        params.put("docid", docid);
804
        String[] doctext = new String[1];
805
        doctext[0] = xml;
806
        logMetacat.debug(doctext[0]);
807
        params.put("doctext", doctext);
808
        
809
        // TODO: refactor handleInsertOrUpdateAction() to not output XML directly
810
        // onto output stream, or alternatively, capture that and parse it to 
811
        // generate the right exceptions
812
        //ByteArrayOutputStream output = new ByteArrayOutputStream();
813
        //PrintWriter pw = new PrintWriter(output);
814
        String result = handler.handleInsertOrUpdateAction(metacatUrl, null, 
815
                            null, params, sessionData.getUserName(), sessionData.getGroupNames());
816
        //String outputS = new String(output.toByteArray());
817
        logMetacat.debug("CrudService.insertDocument - Metacat returned: " + result);
818
//        if (!(outputS.indexOf("<success>") > 0 && outputS.indexOf(localId) > 0)) {
819
//            throw new ServiceFailure(1190, outputS);
820
//        }
821
        logMetacat.debug("Finsished inserting xml document with id " + localId);
822
        return localId;
823
    }
824
    
825
    /**
826
     * serialize a system metadata doc
827
     * @param sysmeta
828
     * @return
829
     * @throws ServiceFailure
830
     */
831
    public static ByteArrayOutputStream serializeSystemMetadata(SystemMetadata sysmeta) 
832
        throws ServiceFailure {
833
        IBindingFactory bfact;
834
        ByteArrayOutputStream sysmetaOut = null;
835
        try {
836
            bfact = BindingDirectory.getFactory(SystemMetadata.class);
837
            IMarshallingContext mctx = bfact.createMarshallingContext();
838
            sysmetaOut = new ByteArrayOutputStream();
839
            mctx.marshalDocument(sysmeta, "UTF-8", null, sysmetaOut);
840
        } catch (JiBXException e) {
841
            throw new ServiceFailure("1190", "Failed to serialize and insert SystemMetadata: " + e.getMessage());
842
        }
843
        
844
        return sysmetaOut;
845
    }
846
    
847
    /**
848
     * deserialize a system metadata doc
849
     * @param xml
850
     * @return
851
     * @throws ServiceFailure
852
     */
853
    public static SystemMetadata deserializeSystemMetadata(InputStream xml) 
854
        throws ServiceFailure {
855
        try {
856
            IBindingFactory bfact = BindingDirectory.getFactory(SystemMetadata.class);
857
            IUnmarshallingContext uctx = bfact.createUnmarshallingContext();
858
            SystemMetadata sysmeta = (SystemMetadata) uctx.unmarshalDocument(xml, null);
859
            return sysmeta;
860
        } catch (JiBXException e) {
861
            throw new ServiceFailure("1190", "Failed to serialize and insert SystemMetadata: " + e.getMessage());
862
        }    
863
    }
864
}
    (1-1/1)