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.v1.Session;
58
import org.dataone.service.types.v1.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_BASE_URL = "d1";
88

    
89
    protected static final String RESOURCE_OBJECTS = "object";
90
    protected static final String RESOURCE_META = "meta";
91
    protected static final String RESOURCE_LOG = "log";
92
    protected static final String RESOURCE_CHECKSUM = "checksum";
93
    
94
    protected static final String RESOURCE_IS_AUTHORIZED = "isAuthorized";
95
    protected static final String RESOURCE_ACCESS_RULES = "accessRules";
96

    
97
    /*
98
     * API Functions used as URL parameters
99
     */
100
    protected static final String FUNCTION_NAME_INSERT = "insert";
101
    protected static final String FUNCTION_NAME_UPDATE = "update";
102

    
103
    protected static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
104
    
105
    protected ServletContext servletContext;
106
    protected Logger logMetacat;
107
    protected MetacatHandler handler;
108
    protected HttpServletRequest request;
109
    protected HttpServletResponse response;
110

    
111
    protected Hashtable<String, String[]> params;
112
    protected Map<String, List<String>> multipartparams;
113
    
114
    // D1 certificate-based authentication
115
    protected Session session;
116

    
117
    /**Initializes new instance by setting servlet context,request and response*/
118
    public D1ResourceHandler(ServletContext servletContext,
119
            HttpServletRequest request, HttpServletResponse response) {
120
        this.servletContext = servletContext;
121
        this.request = request;
122
        this.response = response;
123
    }
124

    
125
    /**
126
     * This function is called from REST API servlet and handles each request 
127
     * 
128
     * @param httpVerb (GET, POST, PUT or DELETE)
129
     */
130
    public void handle(byte httpVerb) {
131
        logMetacat = Logger.getLogger(D1ResourceHandler.class);
132
        try {
133
  
134
            // load session from certificate in request
135
            session = CertificateManager.getInstance().getSession(request);
136

    
137
            // initialize the parameters
138
            params = new Hashtable<String, String[]>();
139
            initParams();
140

    
141
            // create the handler for interacting with Metacat
142
            Timer timer = new Timer();
143
            handler = new MetacatHandler(timer);
144

    
145
        } catch (Exception e) {
146
        	// TODO: more D1 structure when failing here
147
        	response.setStatus(400);
148
            printError("Incorrect resource!", response);
149
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
150
        }
151
    }
152
    
153
    protected SystemMetadata collectSystemMetadata() throws IOException, FileUploadException, ServiceFailure, InvalidRequest, JiBXException  {
154
		
155
		// Read the incoming data from its Mime Multipart encoding
156
		logMetacat.debug("Disassembling MIME multipart form");
157
		InputStream sysmeta = null;
158

    
159
		// handle MMP inputs
160
		File tmpDir = getTempDirectory();
161
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
162
		MultipartRequestResolver mrr = 
163
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
164
		MultipartRequest mr = null;
165
		try {
166
			mr = mrr.resolveMultipart(request);
167
		} catch (Exception e) {
168
			throw new ServiceFailure("1202", 
169
					"Could not resolve multipart: " + e.getMessage());
170
		}
171
		logMetacat.debug("resolved multipart request");
172
		Map<String, File> files = mr.getMultipartFiles();
173
		if (files == null) {
174
			throw new ServiceFailure("1202",
175
					"register meta must have multipart file with name 'sysmeta'");
176
		}
177
		logMetacat.debug("got multipart files");
178

    
179
		if (files.keySet() == null) {
180
			logMetacat.error("No file keys in MMP request.");
181
			throw new ServiceFailure(
182
					"1202",
183
					"No file keys found in MMP.  "
184
							+ "register meta must have multipart file with name 'sysmeta'");
185
		}
186

    
187
		// for logging purposes, dump out the key-value pairs that
188
		// constitute the request
189
		// 3 types exist: request params, multipart params, and
190
		// multipart files
191
		Iterator it = files.keySet().iterator();
192
		logMetacat.debug("iterating through request parts: " + it);
193
		while (it.hasNext()) {
194
			String key = (String) it.next();
195
			logMetacat.debug("files key: " + key);
196
			logMetacat.debug("files value: " + files.get(key));
197
		}
198

    
199
		multipartparams = mr.getMultipartParameters();
200
		it = multipartparams.keySet().iterator();
201
		while (it.hasNext()) {
202
			String key = (String) it.next();
203
			logMetacat.debug("multipartparams key: " + key);
204
			logMetacat.debug("multipartparams value: " + multipartparams.get(key));
205
		}
206

    
207
		it = params.keySet().iterator();
208
		while (it.hasNext()) {
209
			String key = (String) it.next();
210
			logMetacat.debug("param key: " + key);
211
			logMetacat.debug("param value: " + params.get(key));
212
		}
213
		logMetacat.debug("done iterating the request...");
214

    
215
		File smFile = files.get("sysmeta");
216
		if (smFile == null) {
217
			throw new InvalidRequest("1102",
218
					"Missing the required file-part 'sysmeta' from the multipart request.");
219
		}
220
		logMetacat.debug("smFile: " + smFile.getAbsolutePath());
221
		sysmeta = new FileInputStream(smFile);
222
	
223
		logMetacat.debug("Commence creation...");
224
		SystemMetadata systemMetadata = (SystemMetadata) deserializeServiceType(SystemMetadata.class, sysmeta);
225
		return systemMetadata;
226
	}
227
    
228
    protected Map<String, File> collectMultipartFiles() throws ServiceFailure, InvalidRequest {
229
    	
230
        // Read the incoming data from its Mime Multipart encoding
231
        logMetacat.debug("Disassembling MIME multipart form");
232
        InputStream object = null;
233
        InputStream sysmeta = null;
234
        
235
        
236
        // handle MMP inputs
237
        File tmpDir = getTempDirectory();
238
        logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
239
        MultipartRequestResolver mrr = 
240
        	new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
241
        MultipartRequest mr = null;
242
		try {
243
			mr = mrr.resolveMultipart(request);
244
		} catch (Exception e) {
245
            throw new ServiceFailure("1202", 
246
            		"Could not resolve multipart files: " + e.getMessage());
247
		}
248
        logMetacat.debug("resolved multipart request");
249
        Map<String, File> files = mr.getMultipartFiles();
250
        if (files == null) {
251
            throw new ServiceFailure("1202", "create/update must have multipart files with names 'object' and 'sysmeta'");
252
        }
253
        logMetacat.debug("got multipart files");
254
        
255
        if (files.keySet() == null) {
256
            logMetacat.error("No file keys in MMP request.");
257
            throw new ServiceFailure("1202", "No file keys found in MMP.  " +
258
                    "create/update must have multipart files with names 'object' and 'sysmeta'");
259
        }
260

    
261
		// for logging purposes, dump out the key-value pairs that constitute the request
262
		// 3 types exist: request params, multipart params, and multipart files
263
        Iterator it = files.keySet().iterator();
264
        logMetacat.debug("iterating through files");
265
        while (it.hasNext()) {
266
            String key = (String)it.next();
267
            logMetacat.debug("files key: " + key);
268
            logMetacat.debug("files value: " + files.get(key));
269
        }
270
        
271
        multipartparams = mr.getMultipartParameters();
272
        it = multipartparams.keySet().iterator();
273
        logMetacat.debug("iterating through multipartparams");
274
        while (it.hasNext()) {
275
            String key = (String)it.next();
276
            logMetacat.debug("multipartparams key: " + key);
277
            logMetacat.debug("multipartparams value: " + multipartparams.get(key));
278
        }
279
        
280
        it = params.keySet().iterator();
281
        logMetacat.debug("iterating through params");
282
        while (it.hasNext()) {
283
            String key = (String)it.next();
284
            logMetacat.debug("param key: " + key);
285
            logMetacat.debug("param value: " + params.get(key));
286
        }
287
        logMetacat.debug("done iterating the request...");
288

    
289
        File smFile = files.get("sysmeta");
290
		if (smFile == null) {
291
		    throw new InvalidRequest("1102", "Missing the required file-part 'sysmeta' from the multipart request.");
292
		}
293
        logMetacat.debug("smFile: " + smFile.getAbsolutePath());
294
        File objFile = files.get("object");
295
		if (objFile == null) {
296
		    throw new InvalidRequest("1102", "Missing the required file-part 'object' from the multipart request.");
297
		}
298
        logMetacat.debug("objectfile: " + objFile.getAbsolutePath());
299
        
300
        return files;
301
    }
302
    
303
		/**
304
     *  copies request parameters to a hashtable which is given as argument to native metacathandler functions  
305
     */
306
    protected void initParams() {
307

    
308
        String name = null;
309
        String[] value = null;
310
        Enumeration paramlist = request.getParameterNames();
311
        while (paramlist.hasMoreElements()) {
312
            name = (String) paramlist.nextElement();
313
            value = request.getParameterValues(name);
314
            params.put(name, value);
315
        }
316
    }
317

    
318
    /**
319
     * parse a date and return it in GMT if it ends with a 'Z'
320
     * @param date
321
     * @return
322
     * @throws ParseException
323
     */
324
    protected Date parseDateAndConvertToGMT(String date) throws ParseException
325
    {
326
        try
327
        {   //the format we want
328
            return dateFormat.parse(date);
329
        }
330
        catch(java.text.ParseException pe)
331
        {   //try another legacy format
332
            DateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
333
            dateFormat2.setTimeZone(TimeZone.getTimeZone("GMT-0"));
334
            return dateFormat2.parse(date);
335
        }    
336
    }
337

    
338
    /**
339
     * serialize an object of type to out
340
     * @param type the class of the object to serialize (i.e. SystemMetadata.class)
341
     * @param object the object to serialize
342
     * @param out the stream to serialize it to
343
     * @throws JiBXException
344
     */
345
    protected void serializeServiceType(Class type, Object object, OutputStream out)
346
      throws JiBXException {
347
        ServiceTypeUtil.serializeServiceType(type, object, out);
348
    }
349
    
350
    /**
351
     * deserialize an object of type from is
352
     * @param type the class of the object to serialize (i.e. SystemMetadata.class)
353
     * @param is the stream to deserialize from
354
     * @throws JiBXException
355
     */
356
    protected Object deserializeServiceType(Class type, InputStream is)
357
      throws JiBXException {
358
        return ServiceTypeUtil.deserializeServiceType(type, is);
359
    }
360
        
361
    /**
362
     * locate the boundary marker for an MMP
363
     * @param is
364
     * @return
365
     * @throws IOException
366
     */
367
    protected static String[] findBoundaryString(InputStream is)
368
        throws IOException {
369
        String[] endResult = new String[2];
370
        String boundary = "";
371
        String searchString = "boundary=";
372
        byte[] b = new byte[1024];
373
        int numbytes = is.read(b, 0, 1024);
374

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

    
469
}
(4-4/11)