Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2011 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author: Serhan AKIN $'
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.restservice;
24

    
25
import java.io.File;
26
import java.io.FileInputStream;
27
import java.io.FileNotFoundException;
28
import java.io.IOException;
29
import java.io.InputStream;
30
import java.io.OutputStream;
31
import java.io.PrintWriter;
32
import java.text.DateFormat;
33
import java.text.ParseException;
34
import java.text.SimpleDateFormat;
35
import java.util.Date;
36
import java.util.Enumeration;
37
import java.util.Hashtable;
38
import java.util.Iterator;
39
import java.util.List;
40
import java.util.Map;
41
import java.util.TimeZone;
42
import java.util.Timer;
43

    
44
import javax.servlet.ServletContext;
45
import javax.servlet.http.HttpServletRequest;
46
import javax.servlet.http.HttpServletResponse;
47

    
48
import org.apache.commons.fileupload.FileUploadException;
49
import org.apache.commons.io.IOUtils;
50
import org.apache.log4j.Logger;
51
import org.dataone.client.auth.CertificateManager;
52
import org.dataone.mimemultipart.MultipartRequest;
53
import org.dataone.mimemultipart.MultipartRequestResolver;
54
import org.dataone.service.exceptions.BaseException;
55
import org.dataone.service.exceptions.InvalidRequest;
56
import org.dataone.service.exceptions.ServiceFailure;
57
import org.dataone.service.types.Session;
58
import org.dataone.service.types.SystemMetadata;
59
import org.dataone.service.types.util.ServiceTypeUtil;
60
import org.jibx.runtime.JiBXException;
61

    
62
import edu.ucsb.nceas.metacat.MetacatHandler;
63
import edu.ucsb.nceas.metacat.properties.PropertyService;
64
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
65
/**
66
 * 
67
 * Base class for handling D1 REST calls in Metacat
68
 * 
69
 * @author leinfelder
70
 */
71
public class D1ResourceHandler {
72

    
73
    /**HTTP Verb GET*/
74
    public static final byte GET = 1;
75
    /**HTTP Verb POST*/
76
    public static final byte POST = 2;
77
    /**HTTP Verb PUT*/
78
    public static final byte PUT = 3;
79
    /**HTTP Verb DELETE*/
80
    public static final byte DELETE = 4;
81
    /**HTTP Verb HEAD*/
82
    public static final byte HEAD = 5;
83

    
84
    /*
85
     * API Resources
86
     */
87
    protected static final String RESOURCE_OBJECTS = "object";
88
    protected static final String RESOURCE_FORMATS = "formats";
89
    protected static final String RESOURCE_META = "meta";
90
    protected static final String RESOURCE_SESSION = "session";
91
    protected static final String RESOURCE_IDENTIFIER = "identifier";
92
    protected static final String RESOURCE_LOG = "log";
93
    protected static final String RESOURCE_CHECKSUM = "checksum";
94
    protected static final String RESOURCE_MONITOR = "monitor";
95
    protected static final String RESOURCE_BASE_URL = "d1";
96
    protected static final String RESOURCE_REPLICATE = "replicate";
97
    
98
    protected static final String RESOURCE_IS_AUTHORIZED = "isAuthorized";
99
    protected static final String RESOURCE_ACCESS_RULES = "accessRules";
100

    
101
    /*
102
     * API Functions used as URL parameters
103
     */
104
    protected static final String FUNCTION_KEYWORD = "op";
105
    protected static final String FUNCTION_NAME_LOGIN = "login";
106
    protected static final String FUNCTION_NAME_LOGOUT = "logout";
107
    protected static final String FUNCTION_NAME_SET_ACCESS = "setaccess";
108
    protected static final String FUNCTION_NAME_ISREGISTERED = "isregistered";
109
    protected static final String FUNCTION_NAME_GETALLDOCS = "getalldocids";
110
    protected static final String FUNCTION_NAME_GETNEXTREV = "getnextrevision";
111
    protected static final String FUNCTION_NAME_GETNEXTOBJ = "getnextobject";
112
    protected static final String FUNCTION_NAME_INSERT = "insert";
113
    protected static final String FUNCTION_NAME_UPDATE = "update";
114
    protected static final String FUNCTION_NAME_GENERATE_MISSING_SYSTEM_METADATA = "generatemissingsystemmetadata";
115

    
116
    protected static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
117
    
118
    protected ServletContext servletContext;
119
    protected Logger logMetacat;
120
    protected MetacatHandler handler;
121
    protected HttpServletRequest request;
122
    protected HttpServletResponse response;
123
    protected String username;
124
    protected String password;
125
    protected String sessionId;
126
    protected String[] groupNames;
127

    
128
    protected Hashtable<String, String[]> params;
129
    protected Map<String, List<String>> multipartparams;
130
    
131
    // D1 certificate-based authentication
132
    protected Session session;
133

    
134
    /**Initializes new instance by setting servlet context,request and response*/
135
    public D1ResourceHandler(ServletContext servletContext,
136
            HttpServletRequest request, HttpServletResponse response) {
137
        this.servletContext = servletContext;
138
        this.request = request;
139
        this.response = response;
140
    }
141

    
142
    /**
143
     * This function is called from REST API servlet and handles each request 
144
     * 
145
     * @param httpVerb (GET, POST, PUT or DELETE)
146
     */
147
    public void handle(byte httpVerb) {
148
        logMetacat = Logger.getLogger(D1ResourceHandler.class);
149
        try {
150
  
151
            // load session from certificate in request
152
            session = CertificateManager.getInstance().getSession(request);
153

    
154
            // initialize the parameters
155
            params = new Hashtable<String, String[]>();
156
            initParams();
157

    
158
            // create the handler for interacting with Metacat
159
            Timer timer = new Timer();
160
            handler = new MetacatHandler(timer);
161

    
162
        } catch (Exception e) {
163
        	// TODO: more D1 structure when failing here
164
        	response.setStatus(400);
165
            printError("Incorrect resource!", response);
166
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
167
        }
168
    }
169
    
170
    protected SystemMetadata collectSystemMetadata() throws IOException, FileUploadException, ServiceFailure, InvalidRequest, JiBXException  {
171
		
172
		// Read the incoming data from its Mime Multipart encoding
173
		logMetacat.debug("Disassembling MIME multipart form");
174
		InputStream sysmeta = null;
175

    
176
		// handle MMP inputs
177
		File tmpDir = getTempDirectory();
178
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
179
		MultipartRequestResolver mrr = 
180
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
181
		MultipartRequest mr = null;
182
		try {
183
			mr = mrr.resolveMultipart(request);
184
		} catch (Exception e) {
185
			throw new ServiceFailure("1202", 
186
					"Could not resolve multipart: " + e.getMessage());
187
		}
188
		logMetacat.debug("resolved multipart request");
189
		Map<String, File> files = mr.getMultipartFiles();
190
		if (files == null) {
191
			throw new ServiceFailure("1202",
192
					"register meta must have multipart file with name 'sysmeta'");
193
		}
194
		logMetacat.debug("got multipart files");
195

    
196
		if (files.keySet() == null) {
197
			logMetacat.error("No file keys in MMP request.");
198
			throw new ServiceFailure(
199
					"1202",
200
					"No file keys found in MMP.  "
201
							+ "register meta must have multipart file with name 'sysmeta'");
202
		}
203

    
204
		// for logging purposes, dump out the key-value pairs that
205
		// constitute the request
206
		// 3 types exist: request params, multipart params, and
207
		// multipart files
208
		Iterator it = files.keySet().iterator();
209
		logMetacat.debug("iterating through request parts: " + it);
210
		while (it.hasNext()) {
211
			String key = (String) it.next();
212
			logMetacat.debug("files key: " + key);
213
			logMetacat.debug("files value: " + files.get(key));
214
		}
215

    
216
		multipartparams = mr.getMultipartParameters();
217
		it = multipartparams.keySet().iterator();
218
		while (it.hasNext()) {
219
			String key = (String) it.next();
220
			logMetacat.debug("multipartparams key: " + key);
221
			logMetacat.debug("multipartparams value: " + multipartparams.get(key));
222
		}
223

    
224
		it = params.keySet().iterator();
225
		while (it.hasNext()) {
226
			String key = (String) it.next();
227
			logMetacat.debug("param key: " + key);
228
			logMetacat.debug("param value: " + params.get(key));
229
		}
230
		logMetacat.debug("done iterating the request...");
231

    
232
		File smFile = files.get("sysmeta");
233
		if (smFile == null) {
234
			throw new InvalidRequest("1102",
235
					"Missing the required file-part 'sysmeta' from the multipart request.");
236
		}
237
		logMetacat.debug("smFile: " + smFile.getAbsolutePath());
238
		sysmeta = new FileInputStream(smFile);
239
	
240
		logMetacat.debug("Commence creation...");
241
		SystemMetadata systemMetadata = (SystemMetadata) deserializeServiceType(SystemMetadata.class, sysmeta);
242
		return systemMetadata;
243
	}
244
    
245
    protected Map<String, File> collectMultipartFiles() throws ServiceFailure, InvalidRequest {
246
    	
247
        // Read the incoming data from its Mime Multipart encoding
248
        logMetacat.debug("Disassembling MIME multipart form");
249
        InputStream object = null;
250
        InputStream sysmeta = null;
251
        
252
        
253
        // handle MMP inputs
254
        File tmpDir = getTempDirectory();
255
        System.out.println("temp dir: " + tmpDir.getAbsolutePath());
256
        MultipartRequestResolver mrr = 
257
        	new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
258
        MultipartRequest mr = null;
259
		try {
260
			mr = mrr.resolveMultipart(request);
261
		} catch (Exception e) {
262
            throw new ServiceFailure("1202", 
263
            		"Could not resolve multipart files: " + e.getMessage());
264
		}
265
        System.out.println("resolved multipart request");
266
        Map<String, File> files = mr.getMultipartFiles();
267
        if (files == null) {
268
            throw new ServiceFailure("1202", "create/update must have multipart files with names 'object' and 'sysmeta'");
269
        }
270
        System.out.println("got multipart files");
271
        
272
        if (files.keySet() == null) {
273
            System.out.println("No file keys in MMP request.");
274
            throw new ServiceFailure("1202", "No file keys found in MMP.  " +
275
                    "create/update must have multipart files with names 'object' and 'sysmeta'");
276
        }
277

    
278
		// for logging purposes, dump out the key-value pairs that constitute the request
279
		// 3 types exist: request params, multipart params, and multipart files
280
        Iterator it = files.keySet().iterator();
281
        System.out.println("iterating through files");
282
        while (it.hasNext()) {
283
            String key = (String)it.next();
284
            System.out.println("files key: " + key);
285
            System.out.println("files value: " + files.get(key));
286
        }
287
        
288
        multipartparams = mr.getMultipartParameters();
289
        it = multipartparams.keySet().iterator();
290
        System.out.println("iterating through multipartparams");
291
        while (it.hasNext()) {
292
            String key = (String)it.next();
293
            System.out.println("multipartparams key: " + key);
294
            System.out.println("multipartparams value: " + multipartparams.get(key));
295
        }
296
        
297
        it = params.keySet().iterator();
298
        System.out.println("iterating through params");
299
        while (it.hasNext()) {
300
            String key = (String)it.next();
301
            System.out.println("param key: " + key);
302
            System.out.println("param value: " + params.get(key));
303
        }
304
        System.out.println("done iterating the request...");
305

    
306
        File smFile = files.get("sysmeta");
307
		if (smFile == null) {
308
		    throw new InvalidRequest("1102", "Missing the required file-part 'sysmeta' from the multipart request.");
309
		}
310
        System.out.println("smFile: " + smFile.getAbsolutePath());
311
        File objFile = files.get("object");
312
		if (objFile == null) {
313
		    throw new InvalidRequest("1102", "Missing the required file-part 'object' from the multipart request.");
314
		}
315
        System.out.println("objectfile: " + objFile.getAbsolutePath());
316
        
317
        return files;
318
    }
319
    
320
		/**
321
     *  copies request parameters to a hashtable which is given as argument to native metacathandler functions  
322
     */
323
    protected void initParams() {
324

    
325
        String name = null;
326
        String[] value = null;
327
        Enumeration paramlist = request.getParameterNames();
328
        while (paramlist.hasMoreElements()) {
329
            name = (String) paramlist.nextElement();
330
            value = request.getParameterValues(name);
331
            params.put(name, value);
332
        }
333
    }
334

    
335
    /**
336
     * parse a date and return it in GMT if it ends with a 'Z'
337
     * @param date
338
     * @return
339
     * @throws ParseException
340
     */
341
    protected Date parseDateAndConvertToGMT(String date) throws ParseException
342
    {
343
        try
344
        {   //the format we want
345
            return dateFormat.parse(date);
346
        }
347
        catch(java.text.ParseException pe)
348
        {   //try another legacy format
349
            DateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
350
            dateFormat2.setTimeZone(TimeZone.getTimeZone("GMT-0"));
351
            return dateFormat2.parse(date);
352
        }    
353
    }
354

    
355
    /**
356
     * serialize an object of type to out
357
     * @param type the class of the object to serialize (i.e. SystemMetadata.class)
358
     * @param object the object to serialize
359
     * @param out the stream to serialize it to
360
     * @throws JiBXException
361
     */
362
    protected void serializeServiceType(Class type, Object object, OutputStream out)
363
      throws JiBXException {
364
        ServiceTypeUtil.serializeServiceType(type, object, out);
365
    }
366
    
367
    /**
368
     * deserialize an object of type from is
369
     * @param type the class of the object to serialize (i.e. SystemMetadata.class)
370
     * @param is the stream to deserialize from
371
     * @throws JiBXException
372
     */
373
    protected Object deserializeServiceType(Class type, InputStream is)
374
      throws JiBXException {
375
        return ServiceTypeUtil.deserializeServiceType(type, is);
376
    }
377
        
378
    /**
379
     * locate the boundary marker for an MMP
380
     * @param is
381
     * @return
382
     * @throws IOException
383
     */
384
    protected static String[] findBoundaryString(InputStream is)
385
        throws IOException {
386
        String[] endResult = new String[2];
387
        String boundary = "";
388
        String searchString = "boundary=";
389
        byte[] b = new byte[1024];
390
        int numbytes = is.read(b, 0, 1024);
391

    
392
        while(numbytes != -1)
393
        {
394
            String s = new String(b, 0, numbytes);
395
            int searchStringIndex = s.indexOf(searchString);
396
            
397
            if(s.indexOf("\"", searchStringIndex + searchString.length() + 1) == -1)
398
            { //the end of the boundary is in the next byte array
399
                boundary = s.substring(searchStringIndex + searchString.length() + 1, s.length());
400
            }
401
            else if(!boundary.startsWith("--"))
402
            { //we can read the whole boundary from this byte array
403
                boundary = s.substring(searchStringIndex + searchString.length() + 1, 
404
                    s.indexOf("\"", searchStringIndex + searchString.length() + 1));
405
                boundary = "--" + boundary;
406
                endResult[0] = boundary;
407
                endResult[1] = s.substring(s.indexOf("\"", searchStringIndex + searchString.length() + 1) + 1,
408
                        s.length());
409
                break;
410
            }
411
            else
412
            { //we're now reading the 2nd byte array to get the rest of the boundary
413
                searchString = "\"";
414
                searchStringIndex = s.indexOf(searchString);
415
                boundary += s.substring(0, searchStringIndex);
416
                boundary = "--" + boundary;
417
                endResult[0] = boundary;
418
                endResult[1] = s.substring(s.indexOf("\"", searchStringIndex + searchString.length() + 1) + 1,
419
                        s.length());
420
                break;
421
            }
422
        }
423
        System.out.println("boundary is: '" + boundary + "'");
424
        return endResult;
425
    }
426
    
427
    /**
428
     * return the directory where temp files are stored
429
     * @return
430
     */
431
    protected static File getTempDirectory()
432
    {
433
        File tmpDir = null;
434
        Logger logMetacat = Logger.getLogger(D1ResourceHandler.class);
435
        try {
436
            tmpDir = new File(PropertyService.getProperty("application.tempDir"));
437
        }
438
        catch(PropertyNotFoundException pnfe) {
439
            logMetacat.error("D1ResourceHandler.writeMMPPartstoFiles: " +
440
                    "application.tmpDir not found.  Using /tmp instead.");
441
            tmpDir = new File("/tmp");
442
        }
443
        return tmpDir;
444
    }
445
    
446
    /**
447
     * Prints xml response
448
     * @param message Message to be displayed
449
     * @param response Servlet response that xml message will be printed 
450
     * */
451
    protected void printError(String message, HttpServletResponse response) {
452
        try {
453
            logMetacat.error("D1ResourceHandler: Printing error to servlet response: " + message);
454
            PrintWriter out = response.getWriter();
455
            response.setContentType("text/xml");
456
            out.println("<?xml version=\"1.0\"?>");
457
            out.println("<error>");
458
            out.println(message);
459
            out.println("</error>");
460
            out.close();
461
        } catch (IOException e) {
462
            e.printStackTrace();
463
        }
464
    }
465
    
466
    /**
467
     * serialize a D1 exception using jibx
468
     * @param e
469
     * @param out
470
     */
471
    protected void serializeException(BaseException e, OutputStream out) {
472
        // TODO: Use content negotiation to determine which return format to use
473
        response.setContentType("text/xml");
474
        response.setStatus(e.getCode());
475
        
476
        logMetacat.error("D1ResourceHandler: Serializing exception with code " + e.getCode() + ": " + e.getMessage());
477
        e.printStackTrace();
478
        
479
        try {
480
            IOUtils.write(e.serialize(BaseException.FMT_XML), out);
481
        } catch (IOException e1) {
482
            logMetacat.error("Error writing exception to stream. " 
483
                    + e1.getMessage());
484
        }
485
    }
486

    
487
}
(4-4/11)