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.File;
26
import java.io.FileNotFoundException;
27
import java.io.FileOutputStream;
28
import java.io.IOException;
29
import java.io.InputStream;
30
import java.io.OutputStream;
31
import java.sql.SQLException;
32
import java.util.Date;
33
import java.util.Enumeration;
34
import java.util.Hashtable;
35
import java.util.Timer;
36

    
37
import javax.servlet.ServletContext;
38
import javax.servlet.http.HttpServletRequest;
39
import javax.servlet.http.HttpServletResponse;
40

    
41
import org.apache.commons.io.IOUtils;
42
import org.apache.log4j.Logger;
43
import org.dataone.service.exceptions.IdentifierNotUnique;
44
import org.dataone.service.exceptions.InsufficientResources;
45
import org.dataone.service.exceptions.InvalidRequest;
46
import org.dataone.service.exceptions.InvalidSystemMetadata;
47
import org.dataone.service.exceptions.InvalidToken;
48
import org.dataone.service.exceptions.NotAuthorized;
49
import org.dataone.service.exceptions.NotFound;
50
import org.dataone.service.exceptions.NotImplemented;
51
import org.dataone.service.exceptions.ServiceFailure;
52
import org.dataone.service.exceptions.UnsupportedType;
53
import org.dataone.service.mn.MemberNodeCrud;
54
import org.dataone.service.types.AuthToken;
55
import org.dataone.service.types.Checksum;
56
import org.dataone.service.types.DescribeResponse;
57
import org.dataone.service.types.Identifier;
58
import org.dataone.service.types.LogRecordSet;
59
import org.dataone.service.types.SystemMetadata;
60

    
61
import com.gc.iotools.stream.is.InputStreamFromOutputStream;
62

    
63
import edu.ucsb.nceas.metacat.AccessionNumberException;
64
import edu.ucsb.nceas.metacat.DocumentImpl;
65
import edu.ucsb.nceas.metacat.EventLog;
66
import edu.ucsb.nceas.metacat.IdentifierManager;
67
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
68
import edu.ucsb.nceas.metacat.McdbException;
69
import edu.ucsb.nceas.metacat.MetacatHandler;
70
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
71
import edu.ucsb.nceas.metacat.properties.PropertyService;
72
import edu.ucsb.nceas.metacat.replication.ForceReplicationHandler;
73
import edu.ucsb.nceas.metacat.service.SessionService;
74
import edu.ucsb.nceas.metacat.util.SessionData;
75
import edu.ucsb.nceas.utilities.ParseLSIDException;
76
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
77

    
78
/**
79
 * 
80
 * Implements DataONE MemberNode CRUD API for Metacat. 
81
 * 
82
 * @author Matthew Jones
83
 */
84
public class CrudService implements MemberNodeCrud {
85

    
86
    private ServletContext servletContext;
87
    private HttpServletRequest request;
88
    private HttpServletResponse response;
89

    
90
//    private Logger logMetacat;
91
    private MetacatHandler handler;
92
//    private String username;
93
//    private String password;
94
//    private String sessionId;
95
//    private String[] groupNames;
96
    private Hashtable<String, String[]> params;
97
    Logger logMetacat = null;
98

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

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

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

    
122
        String name = null;
123
        String[] value = null;
124
        params = new Hashtable<String, String[]>();
125
        Enumeration paramlist = request.getParameterNames();
126
        while (paramlist.hasMoreElements()) {
127
            name = (String) paramlist.nextElement();
128
            value = request.getParameterValues(name);
129
            params.put(name, value);
130
        }
131
    }
132
    
133
    /**
134
     * 
135
     * Load user details of metacat session from the request 
136
     * 
137
     */
138
//    private void loadSessionData() {
139
//        SessionData sessionData = RequestUtil.getSessionData(request);
140
//
141
//        // TODO: validate the session before allowing these values to be set
142
//        username = sessionData.getUserName();
143
//        password = sessionData.getPassword();
144
//        groupNames = sessionData.getGroupNames();
145
//        sessionId = sessionData.getId();
146
//
147
//        if (username == null) {
148
//            username = "public";
149
//        }
150
//    }
151
    
152
    /*
153
     * Look up the information on the session using the token provided in
154
     * the AuthToken.  The Session should have all relevant user information.
155
     * If the session has expired or is invalid, the 'public' session will
156
     * be returned, giving the user anonymous access.
157
     */
158
    private static SessionData getSessionData(AuthToken token) {
159
        SessionData sessionData = null;
160
        String sessionId = "PUBLIC";
161
        if (token != null) {
162
            sessionId = token.getToken();
163
        }
164
        
165
        // if the session id is registered in SessionService, get the
166
        // SessionData for it. Otherwise, use the public session.
167
        if (SessionService.isSessionRegistered(sessionId)) {
168
            sessionData = SessionService.getRegisteredSession(sessionId);
169
        } else {
170
            sessionData = SessionService.getPublicSession();
171
        }
172
        
173
        return sessionData;
174
    }
175
    
176
    public Identifier create(AuthToken token, Identifier guid, 
177
            InputStream object, SystemMetadata sysmeta) throws InvalidToken, 
178
            ServiceFailure, NotAuthorized, IdentifierNotUnique, UnsupportedType, 
179
            InsufficientResources, InvalidSystemMetadata, NotImplemented {
180

    
181
        logMetacat.debug("Starting CrudService.create()...");
182
        
183
        // TODO: authenticate & get user info
184
        String username = "GARBAGETOBEREPLACED";
185
        String[] groups = null;
186

    
187
        // verify that guid == SystemMetadata.getIdentifier()
188
        logMetacat.debug("Comparing guid|sysmeta_guid: " + guid.getValue() + "|" + sysmeta.getIdentifier().getValue());
189
//        if (!guid.getIdentifier().equals(sysmeta.getIdentifier().getIdentifier())) {
190
//            throw new InvalidSystemMetadata(1180, 
191
//                "GUID in method call does not match GUID in system metadata.");
192
//        }
193

    
194
        logMetacat.debug("Checking if identifier exists...");
195
        // Check that the identifier does not already exist
196
        IdentifierManager im = IdentifierManager.getInstance();
197
        if (im.identifierExists(guid.getValue())) {
198
            throw new IdentifierNotUnique(1120, 
199
                "GUID is already in use by an existing object.");
200
        }
201

    
202
        // generate guid/localId pair for object
203
        logMetacat.debug("Generating a guid/localId mapping");
204
        String localId = im.generateLocalId(guid.getValue(), 1);
205

    
206
        // TODO: generate guid/localId pair for sysmeta
207
        // TODO: update system metadata fields
208
        // TODO: insert system metadata to metacat (probably at end)
209

    
210
        // TODO: Check if we are handling metadata or data
211
        
212
        // TODO: CASE METADATA:
213
        // Setup and call handleInsertOrUpdate()
214

    
215
        // TODO: DEFAULT CASE: DATA
216
        try {
217
            logMetacat.debug("Case DATA: starting to write to disk.");
218
            if (DocumentImpl.getDataFileLockGrant(localId)) {
219

    
220
                // Save the data file to disk using "localId" as the name
221
                try {
222
                    String datafilepath = PropertyService.getProperty("application.datafilepath");
223

    
224
                    File dataDirectory = new File(datafilepath);
225
                    dataDirectory.mkdirs();
226

    
227
                    File newFile = writeStreamToFile(dataDirectory, localId, object);
228

    
229
                    // TODO: Check that the file size matches SystemMetadata
230
                    //                        long size = newFile.length();
231
                    //                        if (size == 0) {
232
                    //                            throw new IOException("Uploaded file is 0 bytes!");
233
                    //                        }
234

    
235
                    //register the file in the database (which generates an exception
236
                    // if the docid is not acceptable or other untoward things happen
237
                    try {
238
                        logMetacat.debug("Registering document...");
239
                        DocumentImpl.registerDocument(localId, "BIN", localId,
240
                                username, groups);
241
                        logMetacat.debug("Registrtion step completed.");
242
                    } catch (SQLException e) {
243
                        //newFile.delete();
244
                        logMetacat.debug("SQLE: " + e.getMessage());
245
                        e.printStackTrace(System.out);
246
                        throw new ServiceFailure(1190, "Registration failed: " + e.getMessage());
247
                    } catch (AccessionNumberException e) {
248
                        //newFile.delete();
249
                        logMetacat.debug("ANE: " + e.getMessage());
250
                        e.printStackTrace(System.out);
251
                        throw new ServiceFailure(1190, "Registration failed: " + e.getMessage());
252
                    } catch (Exception e) {
253
                        //newFile.delete();
254
                        logMetacat.debug("Exception: " + e.getMessage());
255
                        e.printStackTrace(System.out);
256
                        throw new ServiceFailure(1190, "Registration failed: " + e.getMessage());
257
                    }
258

    
259
                    logMetacat.debug("Logging the creation event.");
260
                    EventLog.getInstance().log(request.getRemoteAddr(),
261
                            username, localId, "create");
262
                    
263
                    // Force replication this data file
264
                    // To data file, "insert" and update is same
265
                    // The fourth parameter is null. Because it is notification 
266
                    // server and this method is in MetaCatServerlet. It is
267
                    // original command, not get force replication info from 
268
                    // another metacat
269
                    // TODO: note that GUID mapping is not being replicated
270
                    logMetacat.debug("Scheduling replication.");
271
                    ForceReplicationHandler frh = new ForceReplicationHandler(
272
                            localId, "insert", false, null);
273

    
274
                } catch (PropertyNotFoundException e) {
275
                    throw new ServiceFailure(1190, "Could not lock file for writing:" + e.getMessage());
276
                }
277

    
278
            }
279
        } catch (Exception e) {
280
            // Could not get a lock on the document, so we can not update the file now
281
            throw new ServiceFailure(1190, "Failed to lock file: " + e.getMessage());
282
        }
283
        
284
        logMetacat.debug("Returning from CrudService.create()");
285
        return guid;
286
    }
287

    
288
    public Identifier delete(AuthToken token, Identifier guid)
289
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
290
            NotImplemented {
291
        throw new NotImplemented(1000, "This method not yet implemented.");
292
    }
293

    
294
    public DescribeResponse describe(AuthToken token, Identifier guid)
295
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
296
            NotImplemented {
297
        throw new NotImplemented(1000, "This method not yet implemented.");
298
    }
299
    
300
    public InputStream get(AuthToken token, Identifier guid)
301
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
302
            NotImplemented {
303
        
304
        // Retrieve the session information from the AuthToken
305
        // If the session is expired, then the user is 'public'
306
        final SessionData sessionData = getSessionData(token);
307
        
308
        // Look up the localId for this global identifier
309
        IdentifierManager im = IdentifierManager.getInstance();
310
        try {
311
            final String localId = im.getLocalId(guid.getValue());
312

    
313
            final InputStreamFromOutputStream<String> objectStream = 
314
                new InputStreamFromOutputStream<String>() {
315
                
316
                @Override
317
                public String produce(final OutputStream dataSink) throws Exception {
318

    
319
                    try {
320
                        handler.readFromMetacat(request.getRemoteAddr(), null, 
321
                                dataSink, localId, "xml",
322
                                sessionData.getUserName(), 
323
                                sessionData.getGroupNames(), true, params);
324
                    } catch (PropertyNotFoundException e) {
325
                        throw new ServiceFailure(1030, e.getMessage());
326
                    } catch (ClassNotFoundException e) {
327
                        throw new ServiceFailure(1030, e.getMessage());
328
                    } catch (IOException e) {
329
                        throw new ServiceFailure(1030, e.getMessage());
330
                    } catch (SQLException e) {
331
                        throw new ServiceFailure(1030, e.getMessage());
332
                    } catch (McdbException e) {
333
                        throw new ServiceFailure(1030, e.getMessage());
334
                    } catch (ParseLSIDException e) {
335
                        throw new NotFound(1020, e.getMessage());
336
                    } catch (InsufficientKarmaException e) {
337
                        throw new NotAuthorized(1000, "Not authorized for get().");
338
                    }
339

    
340
                    return "Completed";
341
                }
342
            };
343
            return objectStream;
344

    
345
        } catch (McdbDocNotFoundException e) {
346
            throw new NotFound(1020, e.getMessage());
347
        }
348
        
349
/*    
350
 *      Alternative approach that uses Piped streams, but requires thread handling
351
 *      which makes exception handling difficult (because exceptions fall off the
352
 *      calling thread, terminating the thread.
353
 *      
354
        // Look up the localId for this global identifier
355
        IdentifierManager im = IdentifierManager.getInstance();
356
        try {
357
            final String localId = im.getLocalId(guid.getIdentifier());
358

    
359
            // Now use that localId to read the object and return it
360
            params.put("docid", new String[] { localId });        
361
            final PipedInputStream in = new PipedInputStream();
362
            new Thread(
363
                    new Runnable() {
364
                        public void run() {
365
                            try {
366
                                PipedOutputStream out = new PipedOutputStream(in);
367
                                handler.readFromMetacat(request.getRemoteAddr(), null, 
368
                                        out, localId, "xml",
369
                                        username, groupNames, true, params);
370
                            } catch (PropertyNotFoundException e) {
371
                                throw new ServiceFailure(1030, e.getMessage());
372
                            } catch (ClassNotFoundException e) {
373
                                throw new ServiceFailure(1030, e.getMessage());
374
                            } catch (IOException e) {
375
                                throw new ServiceFailure(1030, e.getMessage());
376
                            } catch (SQLException e) {
377
                                throw new ServiceFailure(1030, e.getMessage());
378
                            } catch (McdbException e) {
379
                                throw new ServiceFailure(1030, e.getMessage());
380
                            } catch (ParseLSIDException e) {
381
                                throw new NotFound(1020, e.getMessage());
382
                            } catch (InsufficientKarmaException e) {
383
                                throw new NotAuthorized(1000, "Not authorized for get().");
384
                            }
385
                        }
386
                    }
387
            ).start();
388
            return in;
389
        } catch (McdbDocNotFoundException e) {
390
            throw new NotFound(1020, e.getMessage());
391
        }        
392
*/
393
    }
394

    
395
    public Checksum getChecksum(AuthToken token, Identifier guid)
396
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
397
            InvalidRequest, NotImplemented {
398
        throw new NotImplemented(1000, "This method not yet implemented.");
399
    }
400

    
401
    public Checksum getChecksum(AuthToken token, Identifier guid, 
402
            String checksumAlgorithm) throws InvalidToken, ServiceFailure, 
403
            NotAuthorized, NotFound, InvalidRequest, NotImplemented {
404
        throw new NotImplemented(1000, "This method not yet implemented.");
405
    }
406

    
407
    public LogRecordSet getLogRecords(AuthToken token, Date fromDate, Date toDate)
408
            throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest, 
409
            NotImplemented {
410
        throw new NotImplemented(1000, "This method not yet implemented.");
411
    }
412

    
413
    public SystemMetadata getSystemMetadata(AuthToken token, Identifier guid)
414
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
415
            InvalidRequest, NotImplemented {
416
        
417
        // Look up ID of system metadata based on guid
418
            // Initially from document, later from entry in table?
419
        
420
        // Read system metadata from disk and create SystemMetadata object
421
            // Follows same implementation plan as get()
422
        
423
        // return it
424
        throw new NotImplemented(1000, "This method not yet implemented.");
425
    }
426

    
427
    public Identifier update(AuthToken token, Identifier guid, 
428
            InputStream object, Identifier obsoletedGuid, SystemMetadata sysmeta) 
429
            throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
430
            UnsupportedType, InsufficientResources, NotFound, InvalidSystemMetadata, 
431
            NotImplemented {
432
        throw new NotImplemented(1000, "This method not yet implemented.");
433
    }
434

    
435
    private File writeStreamToFile(File dir, String fileName, InputStream data) 
436
        throws ServiceFailure {
437
//        logMetacat.debug("***********************************************");
438
//        logMetacat.debug("** FILE TEST FROM CS                         **");
439
//        logMetacat.debug("***********************************************");
440
        
441
        File newFile = new File(dir, fileName);
442
        logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
443

    
444
        try {
445
            if (newFile.createNewFile()) {
446
//                logMetacat.debug("File doesn't yet exist, so write to it.");
447
//                logMetacat.debug("Creating file stream...");
448
                
449
                // write data stream to desired file
450
                OutputStream os = new FileOutputStream(newFile);
451
//                logMetacat.debug("Copying file stream...");
452
                long length = IOUtils.copyLarge(data, os);
453
                os.flush();
454
//                logMetacat.debug("Closing file stream...");
455
                os.close();
456
//                logMetacat.debug("Done with file stream...");
457
            } else {
458
                logMetacat.debug("File creation failed, or file already exists.");
459
                throw new ServiceFailure(1190, "File already exists: " + fileName);
460
            }
461
        } catch (FileNotFoundException e) {
462
            logMetacat.debug("FNF: " + e.getMessage());
463
            throw new ServiceFailure(1190, "File not found: " + fileName + " " 
464
                    + e.getMessage());
465
        } catch (IOException e) {
466
            logMetacat.debug("IOE: " + e.getMessage());
467
            throw new ServiceFailure(1190, "File was not written: " + fileName 
468
                    + " " + e.getMessage());
469
        }
470

    
471
//        logMetacat.debug("***********************************************");
472
//        logMetacat.debug("** END FILE TEST                             **");
473
//        logMetacat.debug("***********************************************");
474
        
475
        return newFile;
476
    }   
477
}
    (1-1/1)