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

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

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

    
67
import com.gc.iotools.stream.is.InputStreamFromOutputStream;
68

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

    
85
/**
86
 * 
87
 * Implements DataONE MemberNode CRUD API for Metacat. 
88
 * 
89
 * @author Matthew Jones
90
 */
91
public class CrudService implements MemberNodeCrud {
92

    
93
    private ServletContext servletContext;
94
    private HttpServletRequest request;
95
    private HttpServletResponse response;
96

    
97
    private MetacatHandler handler;
98
    private Hashtable<String, String[]> params;
99
    Logger logMetacat = null;
100

    
101
    /**
102
     * Initializes new instance by setting servlet context,request and response.
103
     * TODO: remove dependency on Servlet infrastructure
104
     * TODO: Make this a real service, and make it a Singleton
105
     */
106
    public CrudService(ServletContext servletContext,
107
            HttpServletRequest request, HttpServletResponse response) {
108
        this.servletContext = servletContext;
109
        this.request = request;
110
        this.response = response;
111
        logMetacat = Logger.getLogger(CrudService.class);
112

    
113
        handler = new MetacatHandler(this.servletContext, new Timer());
114
        initParams();
115
//        loadSessionData();
116
    }
117

    
118
    /**
119
     *  copies request parameters to a Hashtable which is given as argument to 
120
     *  native MetacatHandler functions  
121
     */
122
    private void initParams() {
123

    
124
        String name = null;
125
        String[] value = null;
126
        params = new Hashtable<String, String[]>();
127
        Enumeration paramlist = request.getParameterNames();
128
        while (paramlist.hasMoreElements()) {
129
            name = (String) paramlist.nextElement();
130
            value = request.getParameterValues(name);
131
            params.put(name, value);
132
        }
133
    }
134
    
135
    public Identifier create(AuthToken token, Identifier guid, 
136
            InputStream object, SystemMetadata sysmeta) throws InvalidToken, 
137
            ServiceFailure, NotAuthorized, IdentifierNotUnique, UnsupportedType, 
138
            InsufficientResources, InvalidSystemMetadata, NotImplemented {
139

    
140
        logMetacat.debug("Starting CrudService.create()...");
141
        
142
        // authenticate & get user info
143
        SessionData sessionData = getSessionData(token);
144
        String username = sessionData.getUserName();
145
        String[] groups = sessionData.getGroupNames();
146

    
147
        // verify that guid == SystemMetadata.getIdentifier()
148
        logMetacat.debug("Comparing guid|sysmeta_guid: " + guid.getValue() + "|" + sysmeta.getIdentifier().getValue());
149
        if (!guid.getValue().equals(sysmeta.getIdentifier().getValue())) {
150
            throw new InvalidSystemMetadata(1180, 
151
                "GUID in method call does not match GUID in system metadata.");
152
        }
153

    
154
        logMetacat.debug("Checking if identifier exists...");
155
        // Check that the identifier does not already exist
156
        IdentifierManager im = IdentifierManager.getInstance();
157
        if (im.identifierExists(guid.getValue())) {
158
            throw new IdentifierNotUnique(1120, 
159
                "GUID is already in use by an existing object.");
160
        }
161

    
162
        // Check if we are handling metadata or data
163
        boolean isScienceMetadata = isScienceMetadata(sysmeta);
164
        
165
        if (isScienceMetadata) {
166
            // CASE METADATA:
167
            try {
168
                String xml = IOUtils.toString(object);
169
                this.insertDocument(xml, guid, sessionData);
170
            } catch (IOException e) {
171
                String msg = "Could not create string from XML stream: " +
172
                    " " + e.getMessage();
173
                logMetacat.debug(msg);
174
                throw new ServiceFailure(1190, msg);
175
            }
176

    
177
        } else {
178
            // DEFAULT CASE: DATA (needs to be checked and completed)
179
            insertDataObject(object, guid, sessionData);
180
            
181
        }
182

    
183
        // For Metadata and Data, insert the system metadata into the object store too
184
        insertSystemMetadata(sysmeta, sessionData);
185

    
186
        logMetacat.debug("Returning from CrudService.create()");
187
        return guid;
188
    }
189

    
190
    public Identifier delete(AuthToken token, Identifier guid)
191
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
192
            NotImplemented {
193
        throw new NotImplemented(1000, "This method not yet implemented.");
194
    }
195

    
196
    public DescribeResponse describe(AuthToken token, Identifier guid)
197
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
198
            NotImplemented {
199
        throw new NotImplemented(1000, "This method not yet implemented.");
200
    }
201
    
202
    public InputStream get(AuthToken token, Identifier guid)
203
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
204
            NotImplemented {
205
        
206
        // Retrieve the session information from the AuthToken
207
        // If the session is expired, then the user is 'public'
208
        final SessionData sessionData = getSessionData(token);
209
        
210
        // Look up the localId for this global identifier
211
        IdentifierManager im = IdentifierManager.getInstance();
212
        try {
213
            final String localId = im.getLocalId(guid.getValue());
214

    
215
            final InputStreamFromOutputStream<String> objectStream = 
216
                new InputStreamFromOutputStream<String>() {
217
                
218
                @Override
219
                public String produce(final OutputStream dataSink) throws Exception {
220

    
221
                    try {
222
                        handler.readFromMetacat(request.getRemoteAddr(), null, 
223
                                dataSink, localId, "xml",
224
                                sessionData.getUserName(), 
225
                                sessionData.getGroupNames(), true, params);
226
                    } catch (PropertyNotFoundException e) {
227
                        throw new ServiceFailure(1030, e.getMessage());
228
                    } catch (ClassNotFoundException e) {
229
                        throw new ServiceFailure(1030, e.getMessage());
230
                    } catch (IOException e) {
231
                        throw new ServiceFailure(1030, e.getMessage());
232
                    } catch (SQLException e) {
233
                        throw new ServiceFailure(1030, e.getMessage());
234
                    } catch (McdbException e) {
235
                        throw new ServiceFailure(1030, e.getMessage());
236
                    } catch (ParseLSIDException e) {
237
                        throw new NotFound(1020, e.getMessage());
238
                    } catch (InsufficientKarmaException e) {
239
                        throw new NotAuthorized(1000, "Not authorized for get().");
240
                    }
241

    
242
                    return "Completed";
243
                }
244
            };
245
            return objectStream;
246

    
247
        } catch (McdbDocNotFoundException e) {
248
            throw new NotFound(1020, e.getMessage());
249
        }
250
    }
251

    
252
    public Checksum getChecksum(AuthToken token, Identifier guid)
253
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
254
            InvalidRequest, NotImplemented {
255
        throw new NotImplemented(1000, "This method not yet implemented.");
256
    }
257

    
258
    public Checksum getChecksum(AuthToken token, Identifier guid, 
259
            String checksumAlgorithm) throws InvalidToken, ServiceFailure, 
260
            NotAuthorized, NotFound, InvalidRequest, NotImplemented {
261
        throw new NotImplemented(1000, "This method not yet implemented.");
262
    }
263

    
264
    public LogRecordSet getLogRecords(AuthToken token, Date fromDate, Date toDate)
265
            throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest, 
266
            NotImplemented {
267
        throw new NotImplemented(1000, "This method not yet implemented.");
268
    }
269

    
270
    public SystemMetadata getSystemMetadata(AuthToken token, Identifier guid)
271
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
272
            InvalidRequest, NotImplemented {
273
        
274
        // TODO: Look up ID of system metadata based on guid
275
            // TODO: Initially from document, later from entry in table?
276
        
277
        // TODO: Read system metadata from disk and create SystemMetadata object
278
            // TODO: Follows same implementation plan as get()
279
        
280
        // TODO: return it
281
        throw new NotImplemented(1000, "This method not yet implemented.");
282
    }
283

    
284
    public Identifier update(AuthToken token, Identifier guid, 
285
            InputStream object, Identifier obsoletedGuid, SystemMetadata sysmeta) 
286
            throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
287
            UnsupportedType, InsufficientResources, NotFound, InvalidSystemMetadata, 
288
            NotImplemented {
289
        throw new NotImplemented(1000, "This method not yet implemented.");
290
    }
291

    
292
    /*
293
     * Look up the information on the session using the token provided in
294
     * the AuthToken.  The Session should have all relevant user information.
295
     * If the session has expired or is invalid, the 'public' session will
296
     * be returned, giving the user anonymous access.
297
     */
298
    private static SessionData getSessionData(AuthToken token) {
299
        SessionData sessionData = null;
300
        String sessionId = "PUBLIC";
301
        if (token != null) {
302
            sessionId = token.getToken();
303
        }
304
        
305
        // if the session id is registered in SessionService, get the
306
        // SessionData for it. Otherwise, use the public session.
307
        if (SessionService.isSessionRegistered(sessionId)) {
308
            sessionData = SessionService.getRegisteredSession(sessionId);
309
        } else {
310
            sessionData = SessionService.getPublicSession();
311
        }
312
        
313
        return sessionData;
314
    }
315

    
316
    /** 
317
     * Determine if a given object should be treated as an XML science metadata
318
     * object. 
319
     * 
320
     * TODO: This test should be externalized in a configuration dictionary rather than being hardcoded.
321
     * 
322
     * @param sysmeta the SystemMetadata describig the object
323
     * @return true if the object should be treated as science metadata
324
     */
325
    private boolean isScienceMetadata(SystemMetadata sysmeta) {
326
        boolean scimeta = false;
327
        switch (sysmeta.getObjectFormat()) {
328
            case EML_2_1_0: scimeta = true; break;
329
            case EML_2_0_1: scimeta = true; break;
330
            case EML_2_0_0: scimeta = true; break;
331
            case FGDC_STD_001_1_1999: scimeta = true; break;
332
            case FGDC_STD_001_1998: scimeta = true; break;
333
            case NCML_2_2: scimeta = true; break;
334
        }
335
        
336
        return scimeta;
337
    }
338

    
339
    private void insertDataObject(InputStream object, Identifier guid, 
340
            SessionData sessionData) throws ServiceFailure {
341
        
342
        String username = sessionData.getUserName();
343
        String[] groups = sessionData.getGroupNames();
344

    
345
        // generate guid/localId pair for object
346
        logMetacat.debug("Generating a guid/localId mapping");
347
        IdentifierManager im = IdentifierManager.getInstance();
348
        String localId = im.generateLocalId(guid.getValue(), 1);
349

    
350
        try {
351
            logMetacat.debug("Case DATA: starting to write to disk.");
352
            if (DocumentImpl.getDataFileLockGrant(localId)) {
353
    
354
                // Save the data file to disk using "localId" as the name
355
                try {
356
                    String datafilepath = PropertyService.getProperty("application.datafilepath");
357
    
358
                    File dataDirectory = new File(datafilepath);
359
                    dataDirectory.mkdirs();
360
    
361
                    File newFile = writeStreamToFile(dataDirectory, localId, object);
362
    
363
                    // TODO: Check that the file size matches SystemMetadata
364
                    //                        long size = newFile.length();
365
                    //                        if (size == 0) {
366
                    //                            throw new IOException("Uploaded file is 0 bytes!");
367
                    //                        }
368
    
369
                    // Register the file in the database (which generates an exception
370
                    // if the localId is not acceptable or other untoward things happen
371
                    try {
372
                        logMetacat.debug("Registering document...");
373
                        DocumentImpl.registerDocument(localId, "BIN", localId,
374
                                username, groups);
375
                        logMetacat.debug("Registration step completed.");
376
                    } catch (SQLException e) {
377
                        //newFile.delete();
378
                        logMetacat.debug("SQLE: " + e.getMessage());
379
                        e.printStackTrace(System.out);
380
                        throw new ServiceFailure(1190, "Registration failed: " + e.getMessage());
381
                    } catch (AccessionNumberException e) {
382
                        //newFile.delete();
383
                        logMetacat.debug("ANE: " + e.getMessage());
384
                        e.printStackTrace(System.out);
385
                        throw new ServiceFailure(1190, "Registration failed: " + e.getMessage());
386
                    } catch (Exception e) {
387
                        //newFile.delete();
388
                        logMetacat.debug("Exception: " + e.getMessage());
389
                        e.printStackTrace(System.out);
390
                        throw new ServiceFailure(1190, "Registration failed: " + e.getMessage());
391
                    }
392
    
393
                    logMetacat.debug("Logging the creation event.");
394
                    EventLog.getInstance().log(request.getRemoteAddr(),
395
                            username, localId, "create");
396
    
397
                    // Schedule replication for this data file
398
                    logMetacat.debug("Scheduling replication.");
399
                    ForceReplicationHandler frh = new ForceReplicationHandler(
400
                            localId, "insert", false, null);
401
    
402
                } catch (PropertyNotFoundException e) {
403
                    throw new ServiceFailure(1190, "Could not lock file for writing:" + e.getMessage());
404
                }
405
    
406
            }
407
        } catch (Exception e) {
408
            // Could not get a lock on the document, so we can not update the file now
409
            throw new ServiceFailure(1190, "Failed to lock file: " + e.getMessage());
410
        }
411
    }
412

    
413
    private File writeStreamToFile(File dir, String fileName, InputStream data) 
414
        throws ServiceFailure {
415
        
416
        File newFile = new File(dir, fileName);
417
        logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
418

    
419
        try {
420
            if (newFile.createNewFile()) {
421
                // write data stream to desired file
422
                OutputStream os = new FileOutputStream(newFile);
423
                long length = IOUtils.copyLarge(data, os);
424
                os.flush();
425
                os.close();
426
            } else {
427
                logMetacat.debug("File creation failed, or file already exists.");
428
                throw new ServiceFailure(1190, "File already exists: " + fileName);
429
            }
430
        } catch (FileNotFoundException e) {
431
            logMetacat.debug("FNF: " + e.getMessage());
432
            throw new ServiceFailure(1190, "File not found: " + fileName + " " 
433
                    + e.getMessage());
434
        } catch (IOException e) {
435
            logMetacat.debug("IOE: " + e.getMessage());
436
            throw new ServiceFailure(1190, "File was not written: " + fileName 
437
                    + " " + e.getMessage());
438
        }
439

    
440
        return newFile;
441
    }
442

    
443
    private void insertSystemMetadata(SystemMetadata sysmeta, SessionData sessionData) 
444
        throws ServiceFailure {
445
        logMetacat.debug("Starting to insert SystemMetadata...");
446
        IdentifierManager im = IdentifierManager.getInstance();
447
    
448
        // generate guid/localId pair for sysmeta
449
        Identifier sysMetaGuid = new Identifier();
450
        sysMetaGuid.setValue(DocumentUtil.generateDocumentId(1));
451
        sysmeta.setDateSysMetadataModified(new Date());
452

    
453
        String xml = new String(serializeSystemMetadata(sysmeta).toByteArray());
454
        insertDocument(xml, sysMetaGuid, sessionData);
455
    }
456
    
457
    private void insertDocument(String xml, Identifier guid, SessionData sessionData) 
458
        throws ServiceFailure {
459
        logMetacat.debug("Starting to insert xml document...");
460
        IdentifierManager im = IdentifierManager.getInstance();
461

    
462
        // generate guid/localId pair for sysmeta
463
        String localId = im.generateLocalId(guid.getValue(), 1);
464
        logMetacat.debug("Metadata guid|localId: " + guid.getValue() + "|" +
465
                localId);
466

    
467
        String[] action = new String[1];
468
        action[0] = "insert";
469
        params.put("action", action);
470
        String[] docid = new String[1];
471
        docid[0] = localId;
472
        params.put("docid", docid);
473
        String[] doctext = new String[1];
474
        doctext[0] = xml;
475
        logMetacat.debug(doctext[0]);
476
        params.put("doctext", doctext);
477
        
478
        // TODO: refactor handleInsertOrUpdateAction() to not output XML directly
479
        // onto output stream, or alternatively, capture that and parse it to 
480
        // generate the right exceptions
481
        ByteArrayOutputStream output = new ByteArrayOutputStream();
482
        PrintWriter pw = new PrintWriter(output);
483
        handler.handleInsertOrUpdateAction(request.getRemoteAddr(), response, 
484
                pw, params, sessionData.getUserName(),
485
                sessionData.getGroupNames());
486
        logMetacat.debug(new String(output.toByteArray()));
487
        logMetacat.debug("Finsished inserting xml document.");
488
    }
489
    
490
    private ByteArrayOutputStream serializeSystemMetadata(SystemMetadata sysmeta) 
491
        throws ServiceFailure {
492
        IBindingFactory bfact;
493
        ByteArrayOutputStream sysmetaOut = null;
494
        try {
495
            bfact = BindingDirectory.getFactory(SystemMetadata.class);
496
            IMarshallingContext mctx = bfact.createMarshallingContext();
497
            sysmetaOut = new ByteArrayOutputStream();
498
            mctx.marshalDocument(sysmeta, "UTF-8", null, sysmetaOut);
499
        } catch (JiBXException e) {
500
            throw new ServiceFailure(1190, "Failed to serialize and insert SystemMetadata: " + e.getMessage());
501
        }
502
        
503
        return sysmetaOut;
504
    }
505
}
    (1-1/1)