Project

General

Profile

1 5298 jones
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6 5299 jones
 *   '$Author: $'
7 5298 jones
 *     '$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 5329 jones
import java.io.ByteArrayOutputStream;
26 5319 jones
import java.io.File;
27
import java.io.FileNotFoundException;
28
import java.io.FileOutputStream;
29 5298 jones
import java.io.IOException;
30
import java.io.InputStream;
31
import java.io.OutputStream;
32 5329 jones
import java.io.PrintWriter;
33 5298 jones
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 5319 jones
import org.apache.commons.io.IOUtils;
44
import org.apache.log4j.Logger;
45 5298 jones
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 5320 jones
import org.dataone.service.types.Identifier;
60 5298 jones
import org.dataone.service.types.LogRecordSet;
61
import org.dataone.service.types.SystemMetadata;
62 5329 jones
import org.jibx.runtime.BindingDirectory;
63
import org.jibx.runtime.IBindingFactory;
64
import org.jibx.runtime.IMarshallingContext;
65
import org.jibx.runtime.JiBXException;
66 5298 jones
67
import com.gc.iotools.stream.is.InputStreamFromOutputStream;
68
69 5319 jones
import edu.ucsb.nceas.metacat.AccessionNumberException;
70
import edu.ucsb.nceas.metacat.DocumentImpl;
71
import edu.ucsb.nceas.metacat.EventLog;
72 5298 jones
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 5319 jones
import edu.ucsb.nceas.metacat.properties.PropertyService;
78
import edu.ucsb.nceas.metacat.replication.ForceReplicationHandler;
79 5298 jones
import edu.ucsb.nceas.metacat.service.SessionService;
80 5329 jones
import edu.ucsb.nceas.metacat.util.DocumentUtil;
81 5298 jones
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 Logger logMetacat;
98
    private MetacatHandler handler;
99
//    private String username;
100
//    private String password;
101
//    private String sessionId;
102
//    private String[] groupNames;
103
    private Hashtable<String, String[]> params;
104 5319 jones
    Logger logMetacat = null;
105 5298 jones
106
    /**
107
     * Initializes new instance by setting servlet context,request and response.
108
     * TODO: remove dependency on Servlet infrastructure
109
     * TODO: Make this a real service, and make it a Singleton
110
     */
111
    public CrudService(ServletContext servletContext,
112
            HttpServletRequest request, HttpServletResponse response) {
113
        this.servletContext = servletContext;
114
        this.request = request;
115
        this.response = response;
116 5319 jones
        logMetacat = Logger.getLogger(CrudService.class);
117 5298 jones
118
        handler = new MetacatHandler(this.servletContext, new Timer());
119
        initParams();
120
//        loadSessionData();
121
    }
122
123
    /**
124
     *  copies request parameters to a Hashtable which is given as argument to
125
     *  native MetacatHandler functions
126
     */
127
    private void initParams() {
128
129
        String name = null;
130
        String[] value = null;
131
        params = new Hashtable<String, String[]>();
132
        Enumeration paramlist = request.getParameterNames();
133
        while (paramlist.hasMoreElements()) {
134
            name = (String) paramlist.nextElement();
135
            value = request.getParameterValues(name);
136
            params.put(name, value);
137
        }
138
    }
139
140
    /**
141
     *
142
     * Load user details of metacat session from the request
143
     *
144
     */
145
//    private void loadSessionData() {
146
//        SessionData sessionData = RequestUtil.getSessionData(request);
147
//
148
//        // TODO: validate the session before allowing these values to be set
149
//        username = sessionData.getUserName();
150
//        password = sessionData.getPassword();
151
//        groupNames = sessionData.getGroupNames();
152
//        sessionId = sessionData.getId();
153
//
154
//        if (username == null) {
155
//            username = "public";
156
//        }
157
//    }
158
159
    /*
160
     * Look up the information on the session using the token provided in
161
     * the AuthToken.  The Session should have all relevant user information.
162
     * If the session has expired or is invalid, the 'public' session will
163
     * be returned, giving the user anonymous access.
164
     */
165
    private static SessionData getSessionData(AuthToken token) {
166
        SessionData sessionData = null;
167 5299 jones
        String sessionId = "PUBLIC";
168
        if (token != null) {
169
            sessionId = token.getToken();
170
        }
171 5298 jones
172
        // if the session id is registered in SessionService, get the
173
        // SessionData for it. Otherwise, use the public session.
174
        if (SessionService.isSessionRegistered(sessionId)) {
175
            sessionData = SessionService.getRegisteredSession(sessionId);
176
        } else {
177
            sessionData = SessionService.getPublicSession();
178
        }
179
180
        return sessionData;
181
    }
182
183 5320 jones
    public Identifier create(AuthToken token, Identifier guid,
184 5298 jones
            InputStream object, SystemMetadata sysmeta) throws InvalidToken,
185
            ServiceFailure, NotAuthorized, IdentifierNotUnique, UnsupportedType,
186
            InsufficientResources, InvalidSystemMetadata, NotImplemented {
187 5319 jones
188
        logMetacat.debug("Starting CrudService.create()...");
189
190 5329 jones
        // authenticate & get user info
191
        SessionData sessionData = getSessionData(token);
192
        String username = sessionData.getUserName();
193
        String[] groups = sessionData.getGroupNames();
194 5319 jones
195
        // verify that guid == SystemMetadata.getIdentifier()
196 5320 jones
        logMetacat.debug("Comparing guid|sysmeta_guid: " + guid.getValue() + "|" + sysmeta.getIdentifier().getValue());
197 5329 jones
        if (!guid.getValue().equals(sysmeta.getIdentifier().getValue())) {
198
            throw new InvalidSystemMetadata(1180,
199
                "GUID in method call does not match GUID in system metadata.");
200
        }
201 5319 jones
202
        logMetacat.debug("Checking if identifier exists...");
203
        // Check that the identifier does not already exist
204
        IdentifierManager im = IdentifierManager.getInstance();
205 5320 jones
        if (im.identifierExists(guid.getValue())) {
206 5319 jones
            throw new IdentifierNotUnique(1120,
207
                "GUID is already in use by an existing object.");
208
        }
209
210
        // generate guid/localId pair for object
211
        logMetacat.debug("Generating a guid/localId mapping");
212 5320 jones
        String localId = im.generateLocalId(guid.getValue(), 1);
213 5319 jones
214 5329 jones
        // Check if we are handling metadata or data
215
        boolean isScienceMetadata = isScienceMetadata(sysmeta);
216 5319 jones
217 5329 jones
        if (isScienceMetadata) {
218
            // TODO: CASE METADATA:
219
            // Setup and call handleInsertOrUpdate()
220 5319 jones
221 5329 jones
        } else {
222
            // TODO: DEFAULT CASE: DATA (needs to be checked and completed)
223
            try {
224
                logMetacat.debug("Case DATA: starting to write to disk.");
225
                if (DocumentImpl.getDataFileLockGrant(localId)) {
226 5319 jones
227 5329 jones
                    // Save the data file to disk using "localId" as the name
228
                    try {
229
                        String datafilepath = PropertyService.getProperty("application.datafilepath");
230 5319 jones
231 5329 jones
                        File dataDirectory = new File(datafilepath);
232
                        dataDirectory.mkdirs();
233 5319 jones
234 5329 jones
                        File newFile = writeStreamToFile(dataDirectory, localId, object);
235 5319 jones
236 5329 jones
                        // TODO: Check that the file size matches SystemMetadata
237
                        //                        long size = newFile.length();
238
                        //                        if (size == 0) {
239
                        //                            throw new IOException("Uploaded file is 0 bytes!");
240
                        //                        }
241 5319 jones
242 5329 jones
                        //register the file in the database (which generates an exception
243
                        // if the docid is not acceptable or other untoward things happen
244
                        try {
245
                            logMetacat.debug("Registering document...");
246
                            DocumentImpl.registerDocument(localId, "BIN", localId,
247
                                    username, groups);
248
                            logMetacat.debug("Registrtion step completed.");
249
                        } catch (SQLException e) {
250
                            //newFile.delete();
251
                            logMetacat.debug("SQLE: " + e.getMessage());
252
                            e.printStackTrace(System.out);
253
                            throw new ServiceFailure(1190, "Registration failed: " + e.getMessage());
254
                        } catch (AccessionNumberException e) {
255
                            //newFile.delete();
256
                            logMetacat.debug("ANE: " + e.getMessage());
257
                            e.printStackTrace(System.out);
258
                            throw new ServiceFailure(1190, "Registration failed: " + e.getMessage());
259
                        } catch (Exception e) {
260
                            //newFile.delete();
261
                            logMetacat.debug("Exception: " + e.getMessage());
262
                            e.printStackTrace(System.out);
263
                            throw new ServiceFailure(1190, "Registration failed: " + e.getMessage());
264
                        }
265
266
                        logMetacat.debug("Logging the creation event.");
267
                        EventLog.getInstance().log(request.getRemoteAddr(),
268
                                username, localId, "create");
269
270
                        // Force replication this data file
271
                        // To data file, "insert" and update is same
272
                        // The fourth parameter is null. Because it is notification
273
                        // server and this method is in MetaCatServerlet. It is
274
                        // original command, not get force replication info from
275
                        // another metacat
276
                        // TODO: note that GUID mapping is not being replicated
277
                        logMetacat.debug("Scheduling replication.");
278
                        ForceReplicationHandler frh = new ForceReplicationHandler(
279
                                localId, "insert", false, null);
280
281
                    } catch (PropertyNotFoundException e) {
282
                        throw new ServiceFailure(1190, "Could not lock file for writing:" + e.getMessage());
283 5319 jones
                    }
284
285
                }
286 5329 jones
            } catch (Exception e) {
287
                // Could not get a lock on the document, so we can not update the file now
288
                throw new ServiceFailure(1190, "Failed to lock file: " + e.getMessage());
289 5319 jones
            }
290 5329 jones
291
            // Insert the system metadata into the object store
292
            insertSystemMetadata(sysmeta, sessionData);
293 5319 jones
        }
294
295
        logMetacat.debug("Returning from CrudService.create()");
296
        return guid;
297 5298 jones
    }
298
299 5320 jones
    public Identifier delete(AuthToken token, Identifier guid)
300 5298 jones
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
301
            NotImplemented {
302
        throw new NotImplemented(1000, "This method not yet implemented.");
303
    }
304
305 5320 jones
    public DescribeResponse describe(AuthToken token, Identifier guid)
306 5298 jones
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
307
            NotImplemented {
308
        throw new NotImplemented(1000, "This method not yet implemented.");
309
    }
310
311 5320 jones
    public InputStream get(AuthToken token, Identifier guid)
312 5298 jones
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
313
            NotImplemented {
314
315
        // Retrieve the session information from the AuthToken
316
        // If the session is expired, then the user is 'public'
317
        final SessionData sessionData = getSessionData(token);
318
319
        // Look up the localId for this global identifier
320
        IdentifierManager im = IdentifierManager.getInstance();
321
        try {
322 5320 jones
            final String localId = im.getLocalId(guid.getValue());
323 5298 jones
324
            final InputStreamFromOutputStream<String> objectStream =
325
                new InputStreamFromOutputStream<String>() {
326
327
                @Override
328
                public String produce(final OutputStream dataSink) throws Exception {
329
330
                    try {
331
                        handler.readFromMetacat(request.getRemoteAddr(), null,
332
                                dataSink, localId, "xml",
333
                                sessionData.getUserName(),
334
                                sessionData.getGroupNames(), true, params);
335
                    } catch (PropertyNotFoundException e) {
336
                        throw new ServiceFailure(1030, e.getMessage());
337
                    } catch (ClassNotFoundException e) {
338
                        throw new ServiceFailure(1030, e.getMessage());
339
                    } catch (IOException e) {
340
                        throw new ServiceFailure(1030, e.getMessage());
341
                    } catch (SQLException e) {
342
                        throw new ServiceFailure(1030, e.getMessage());
343
                    } catch (McdbException e) {
344
                        throw new ServiceFailure(1030, e.getMessage());
345
                    } catch (ParseLSIDException e) {
346
                        throw new NotFound(1020, e.getMessage());
347
                    } catch (InsufficientKarmaException e) {
348
                        throw new NotAuthorized(1000, "Not authorized for get().");
349
                    }
350
351
                    return "Completed";
352
                }
353
            };
354
            return objectStream;
355
356
        } catch (McdbDocNotFoundException e) {
357
            throw new NotFound(1020, e.getMessage());
358
        }
359
    }
360
361 5320 jones
    public Checksum getChecksum(AuthToken token, Identifier guid)
362 5298 jones
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
363
            InvalidRequest, NotImplemented {
364
        throw new NotImplemented(1000, "This method not yet implemented.");
365
    }
366
367 5320 jones
    public Checksum getChecksum(AuthToken token, Identifier guid,
368 5298 jones
            String checksumAlgorithm) throws InvalidToken, ServiceFailure,
369
            NotAuthorized, NotFound, InvalidRequest, NotImplemented {
370
        throw new NotImplemented(1000, "This method not yet implemented.");
371
    }
372
373
    public LogRecordSet getLogRecords(AuthToken token, Date fromDate, Date toDate)
374
            throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
375
            NotImplemented {
376
        throw new NotImplemented(1000, "This method not yet implemented.");
377
    }
378
379 5320 jones
    public SystemMetadata getSystemMetadata(AuthToken token, Identifier guid)
380 5298 jones
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
381
            InvalidRequest, NotImplemented {
382 5319 jones
383 5329 jones
        // TODO: Look up ID of system metadata based on guid
384
            // TODO: Initially from document, later from entry in table?
385 5319 jones
386 5329 jones
        // TODO: Read system metadata from disk and create SystemMetadata object
387
            // TODO: Follows same implementation plan as get()
388 5319 jones
389 5329 jones
        // TODO: return it
390 5298 jones
        throw new NotImplemented(1000, "This method not yet implemented.");
391
    }
392
393 5320 jones
    public Identifier update(AuthToken token, Identifier guid,
394
            InputStream object, Identifier obsoletedGuid, SystemMetadata sysmeta)
395 5298 jones
            throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique,
396
            UnsupportedType, InsufficientResources, NotFound, InvalidSystemMetadata,
397
            NotImplemented {
398
        throw new NotImplemented(1000, "This method not yet implemented.");
399
    }
400
401 5319 jones
    private File writeStreamToFile(File dir, String fileName, InputStream data)
402
        throws ServiceFailure {
403
//        logMetacat.debug("***********************************************");
404
//        logMetacat.debug("** FILE TEST FROM CS                         **");
405
//        logMetacat.debug("***********************************************");
406
407
        File newFile = new File(dir, fileName);
408
        logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
409
410
        try {
411
            if (newFile.createNewFile()) {
412
//                logMetacat.debug("File doesn't yet exist, so write to it.");
413
//                logMetacat.debug("Creating file stream...");
414
415
                // write data stream to desired file
416
                OutputStream os = new FileOutputStream(newFile);
417
//                logMetacat.debug("Copying file stream...");
418
                long length = IOUtils.copyLarge(data, os);
419
                os.flush();
420
//                logMetacat.debug("Closing file stream...");
421
                os.close();
422
//                logMetacat.debug("Done with file stream...");
423
            } else {
424
                logMetacat.debug("File creation failed, or file already exists.");
425
                throw new ServiceFailure(1190, "File already exists: " + fileName);
426
            }
427
        } catch (FileNotFoundException e) {
428
            logMetacat.debug("FNF: " + e.getMessage());
429
            throw new ServiceFailure(1190, "File not found: " + fileName + " "
430
                    + e.getMessage());
431
        } catch (IOException e) {
432
            logMetacat.debug("IOE: " + e.getMessage());
433
            throw new ServiceFailure(1190, "File was not written: " + fileName
434
                    + " " + e.getMessage());
435
        }
436
437
//        logMetacat.debug("***********************************************");
438
//        logMetacat.debug("** END FILE TEST                             **");
439
//        logMetacat.debug("***********************************************");
440
441
        return newFile;
442 5329 jones
    }
443
444
    /**
445
     * Determine if a given object should be treated as an XML science metadata
446
     * object.
447
     *
448
     * TODO: This test should be externalized in a configuration dictionary rather than being hardcoded.
449
     *
450
     * @param sysmeta the SystemMetadata describig the object
451
     * @return true if the object should be treated as science metadata
452
     */
453
    private boolean isScienceMetadata(SystemMetadata sysmeta) {
454
        boolean scimeta = false;
455
        switch (sysmeta.getObjectFormat()) {
456
            case EML_2_1_0: scimeta = true; break;
457
            case EML_2_0_1: scimeta = true; break;
458
            case EML_2_0_0: scimeta = true; break;
459
            case FGDC_STD_001_1_1999: scimeta = true; break;
460
            case FGDC_STD_001_1998: scimeta = true; break;
461
            case NCML_2_2: scimeta = true; break;
462
        }
463
464
        return scimeta;
465
    }
466
467
    private void insertSystemMetadata(SystemMetadata sysmeta, SessionData sessionData)
468
        throws ServiceFailure {
469
        logMetacat.debug("Starting to insert SystemMetadata...");
470
        IdentifierManager im = IdentifierManager.getInstance();
471
472
        // generate guid/localId pair for sysmeta
473
        String sysMetaGuid = DocumentUtil.generateDocumentId(1);
474
        sysmeta.setDateSysMetadataModified(new Date());
475
476
        String xml = new String(serializeSystemMetadata(sysmeta).toByteArray());
477
        insertDocument(xml, sysMetaGuid, sessionData);
478
    }
479
480
    private void insertDocument(String xml, String guid, SessionData sessionData)
481
        throws ServiceFailure {
482
        logMetacat.debug("Starting to insert xml document...");
483
        IdentifierManager im = IdentifierManager.getInstance();
484
485
        // generate guid/localId pair for sysmeta
486
        String localId = im.generateLocalId(guid, 1);
487
        logMetacat.debug("Metadata guid|localId: " + guid + "|" +
488
                localId);
489
490
        // TODO: insert system metadata to metacat (probably at end)
491
        // TODO: can't pass in the out PrintWriter, as we don't want to write
492
        // error info on this stream -- need to be able to return to process
493
        // the real object.  So, need to:
494
        // TODO: refactor handleInsertOrUpdateAction() to not output XML directly
495
        // onto output stream, or alternatively, capture that and parse it to
496
        // generate the right exceptions
497
        String[] action = new String[1];
498
        action[0] = "insert";
499
        params.put("action", action);
500
        String[] docid = new String[1];
501
        docid[0] = localId;
502
        params.put("docid", docid);
503
        String[] doctext = new String[1];
504
        doctext[0] = xml;
505
        logMetacat.debug(doctext[0]);
506
        params.put("doctext", doctext);
507
        ByteArrayOutputStream output = new ByteArrayOutputStream();
508
        PrintWriter pw = new PrintWriter(output);
509
        handler.handleInsertOrUpdateAction(request.getRemoteAddr(), response,
510
                pw, params, sessionData.getUserName(),
511
                sessionData.getGroupNames());
512
        logMetacat.debug(new String(output.toByteArray()));
513
        logMetacat.debug("Finsihed inserting xml document.");
514
    }
515
516
    private ByteArrayOutputStream serializeSystemMetadata(SystemMetadata sysmeta)
517
        throws ServiceFailure {
518
        IBindingFactory bfact;
519
        ByteArrayOutputStream sysmetaOut = null;
520
        try {
521
            bfact = BindingDirectory.getFactory(SystemMetadata.class);
522
            IMarshallingContext mctx = bfact.createMarshallingContext();
523
            sysmetaOut = new ByteArrayOutputStream();
524
            mctx.marshalDocument(sysmeta, "UTF-8", null, sysmetaOut);
525
        } catch (JiBXException e) {
526
            throw new ServiceFailure(1190, "Failed to serialize and insert SystemMetadata: " + e.getMessage());
527
        }
528
529
        return sysmetaOut;
530
    }
531 5298 jones
}