Project

General

Profile

1 5211 jones
/**
2
 *  '$RCSfile$'
3 5805 berkley
 *  Copyright: 2011 Regents of the University of California and the
4 5211 jones
 *              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 6130 leinfelder
import java.io.File;
26 6269 leinfelder
import java.io.FileInputStream;
27 6130 leinfelder
import java.io.IOException;
28
import java.io.InputStream;
29
import java.io.OutputStream;
30
import java.io.PrintWriter;
31
import java.util.Enumeration;
32
import java.util.Hashtable;
33 6269 leinfelder
import java.util.Iterator;
34
import java.util.List;
35
import java.util.Map;
36 6130 leinfelder
import java.util.Timer;
37 5211 jones
38
import javax.servlet.ServletContext;
39
import javax.servlet.http.HttpServletRequest;
40
import javax.servlet.http.HttpServletResponse;
41 6514 leinfelder
import javax.xml.parsers.ParserConfigurationException;
42 6067 rnahf
43 6269 leinfelder
import org.apache.commons.fileupload.FileUploadException;
44 5299 jones
import org.apache.commons.io.IOUtils;
45 5211 jones
import org.apache.log4j.Logger;
46 6244 leinfelder
import org.dataone.client.auth.CertificateManager;
47 6269 leinfelder
import org.dataone.mimemultipart.MultipartRequest;
48
import org.dataone.mimemultipart.MultipartRequestResolver;
49 5299 jones
import org.dataone.service.exceptions.BaseException;
50 6269 leinfelder
import org.dataone.service.exceptions.InvalidRequest;
51
import org.dataone.service.exceptions.ServiceFailure;
52 6514 leinfelder
import org.dataone.service.types.v1.AccessPolicy;
53 6366 leinfelder
import org.dataone.service.types.v1.Session;
54
import org.dataone.service.types.v1.SystemMetadata;
55 6367 leinfelder
import org.dataone.service.util.TypeMarshaller;
56 5320 jones
import org.jibx.runtime.JiBXException;
57 6514 leinfelder
import org.xml.sax.SAXException;
58 5211 jones
59
import edu.ucsb.nceas.metacat.MetacatHandler;
60 5637 berkley
import edu.ucsb.nceas.metacat.properties.PropertyService;
61
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
62 5211 jones
/**
63
 *
64 6268 leinfelder
 * Base class for handling D1 REST calls in Metacat
65 5211 jones
 *
66 6268 leinfelder
 * @author leinfelder
67 5211 jones
 */
68 6267 leinfelder
public class D1ResourceHandler {
69 5211 jones
70
    /**HTTP Verb GET*/
71
    public static final byte GET = 1;
72
    /**HTTP Verb POST*/
73
    public static final byte POST = 2;
74
    /**HTTP Verb PUT*/
75
    public static final byte PUT = 3;
76
    /**HTTP Verb DELETE*/
77
    public static final byte DELETE = 4;
78 5651 berkley
    /**HTTP Verb HEAD*/
79
    public static final byte HEAD = 5;
80 5211 jones
81
    /*
82
     * API Resources
83
     */
84 6271 leinfelder
    protected static final String RESOURCE_BASE_URL = "d1";
85
86 6247 leinfelder
    protected static final String RESOURCE_OBJECTS = "object";
87
    protected static final String RESOURCE_META = "meta";
88
    protected static final String RESOURCE_LOG = "log";
89
    protected static final String RESOURCE_CHECKSUM = "checksum";
90 6249 leinfelder
91
    protected static final String RESOURCE_IS_AUTHORIZED = "isAuthorized";
92
    protected static final String RESOURCE_ACCESS_RULES = "accessRules";
93 5211 jones
94
    /*
95
     * API Functions used as URL parameters
96
     */
97 6247 leinfelder
    protected static final String FUNCTION_NAME_INSERT = "insert";
98
    protected static final String FUNCTION_NAME_UPDATE = "update";
99 5414 berkley
100 6247 leinfelder
    protected ServletContext servletContext;
101
    protected Logger logMetacat;
102
    protected MetacatHandler handler;
103
    protected HttpServletRequest request;
104
    protected HttpServletResponse response;
105 5211 jones
106 6247 leinfelder
    protected Hashtable<String, String[]> params;
107 6269 leinfelder
    protected Map<String, List<String>> multipartparams;
108 6244 leinfelder
109
    // D1 certificate-based authentication
110 6247 leinfelder
    protected Session session;
111 5211 jones
112
    /**Initializes new instance by setting servlet context,request and response*/
113 6267 leinfelder
    public D1ResourceHandler(ServletContext servletContext,
114 5211 jones
            HttpServletRequest request, HttpServletResponse response) {
115
        this.servletContext = servletContext;
116
        this.request = request;
117
        this.response = response;
118
    }
119
120
    /**
121 6268 leinfelder
     * This function is called from REST API servlet and handles each request
122 5211 jones
     *
123
     * @param httpVerb (GET, POST, PUT or DELETE)
124
     */
125
    public void handle(byte httpVerb) {
126 6267 leinfelder
        logMetacat = Logger.getLogger(D1ResourceHandler.class);
127 5211 jones
        try {
128 6268 leinfelder
129 6244 leinfelder
            // load session from certificate in request
130
            session = CertificateManager.getInstance().getSession(request);
131 5211 jones
132 6268 leinfelder
            // initialize the parameters
133
            params = new Hashtable<String, String[]>();
134
            initParams();
135 5211 jones
136 6268 leinfelder
            // create the handler for interacting with Metacat
137
            Timer timer = new Timer();
138
            handler = new MetacatHandler(timer);
139 5211 jones
140
        } catch (Exception e) {
141 6268 leinfelder
        	// TODO: more D1 structure when failing here
142
        	response.setStatus(400);
143
            printError("Incorrect resource!", response);
144
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
145 5211 jones
        }
146
    }
147 5374 berkley
148 6514 leinfelder
    protected String parseTrailing(String resource, String token) {
149
    	// get the rest
150
        String extra = null;
151
        if (resource.indexOf(token) != -1) {
152
        	// what comes after the token?
153
            extra = resource.substring(resource.indexOf(token) + token.length());
154
            // remove the slash
155
            if (extra.startsWith("/")) {
156
            	extra = extra.substring(1);
157
            }
158
            // is there anything left?
159
            if (extra.length() == 0) {
160
            	extra = null;
161
            }
162
        }
163
        return extra;
164
    }
165
166
    protected AccessPolicy collectAccessPolicy() throws IOException, ServiceFailure, InvalidRequest, JiBXException, InstantiationException, IllegalAccessException, ParserConfigurationException, SAXException  {
167
168
		// Read the incoming data from its Mime Multipart encoding
169
		logMetacat.debug("Disassembling MIME multipart form");
170
		InputStream ap = null;
171
172
		// handle MMP inputs
173
		File tmpDir = getTempDirectory();
174
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
175
		MultipartRequestResolver mrr =
176
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
177
		MultipartRequest mr = null;
178
		try {
179
			mr = mrr.resolveMultipart(request);
180
		} catch (Exception e) {
181
			throw new ServiceFailure("2161",
182
					"Could not resolve multipart: " + e.getMessage());
183
		}
184
		logMetacat.debug("resolved multipart request");
185
		Map<String, File> files = mr.getMultipartFiles();
186
		if (files == null || files.keySet() == null) {
187
			throw new InvalidRequest("2163",
188
					"must have multipart file with name 'accessPolicy'");
189
		}
190
		logMetacat.debug("got multipart files");
191
192
		multipartparams = mr.getMultipartParameters();
193
194
		File apFile = files.get("accessPolicy");
195
		if (apFile == null) {
196
			throw new InvalidRequest("2163",
197
					"Missing the required file-part 'accessPolicy' from the multipart request.");
198
		}
199
		logMetacat.debug("apFile: " + apFile.getAbsolutePath());
200
		ap = new FileInputStream(apFile);
201
202
		AccessPolicy accessPolicy = TypeMarshaller.unmarshalTypeFromStream(AccessPolicy.class, ap);
203
		return accessPolicy;
204
	}
205
206 6367 leinfelder
    protected SystemMetadata collectSystemMetadata() throws IOException, FileUploadException, ServiceFailure, InvalidRequest, JiBXException, InstantiationException, IllegalAccessException  {
207 6269 leinfelder
208
		// Read the incoming data from its Mime Multipart encoding
209
		logMetacat.debug("Disassembling MIME multipart form");
210
		InputStream sysmeta = null;
211
212
		// handle MMP inputs
213
		File tmpDir = getTempDirectory();
214
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
215
		MultipartRequestResolver mrr =
216
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
217
		MultipartRequest mr = null;
218
		try {
219
			mr = mrr.resolveMultipart(request);
220
		} catch (Exception e) {
221
			throw new ServiceFailure("1202",
222
					"Could not resolve multipart: " + e.getMessage());
223
		}
224
		logMetacat.debug("resolved multipart request");
225
		Map<String, File> files = mr.getMultipartFiles();
226
		if (files == null) {
227
			throw new ServiceFailure("1202",
228
					"register meta must have multipart file with name 'sysmeta'");
229
		}
230
		logMetacat.debug("got multipart files");
231
232
		if (files.keySet() == null) {
233
			logMetacat.error("No file keys in MMP request.");
234
			throw new ServiceFailure(
235
					"1202",
236
					"No file keys found in MMP.  "
237
							+ "register meta must have multipart file with name 'sysmeta'");
238
		}
239
240
		// for logging purposes, dump out the key-value pairs that
241
		// constitute the request
242
		// 3 types exist: request params, multipart params, and
243
		// multipart files
244
		Iterator it = files.keySet().iterator();
245
		logMetacat.debug("iterating through request parts: " + it);
246
		while (it.hasNext()) {
247
			String key = (String) it.next();
248
			logMetacat.debug("files key: " + key);
249
			logMetacat.debug("files value: " + files.get(key));
250
		}
251
252
		multipartparams = mr.getMultipartParameters();
253
		it = multipartparams.keySet().iterator();
254
		while (it.hasNext()) {
255
			String key = (String) it.next();
256
			logMetacat.debug("multipartparams key: " + key);
257
			logMetacat.debug("multipartparams value: " + multipartparams.get(key));
258
		}
259
260
		it = params.keySet().iterator();
261
		while (it.hasNext()) {
262
			String key = (String) it.next();
263
			logMetacat.debug("param key: " + key);
264
			logMetacat.debug("param value: " + params.get(key));
265
		}
266
		logMetacat.debug("done iterating the request...");
267
268
		File smFile = files.get("sysmeta");
269
		if (smFile == null) {
270
			throw new InvalidRequest("1102",
271
					"Missing the required file-part 'sysmeta' from the multipart request.");
272
		}
273
		logMetacat.debug("smFile: " + smFile.getAbsolutePath());
274
		sysmeta = new FileInputStream(smFile);
275
276
		logMetacat.debug("Commence creation...");
277 6367 leinfelder
		SystemMetadata systemMetadata = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
278 6269 leinfelder
		return systemMetadata;
279
	}
280
281
    protected Map<String, File> collectMultipartFiles() throws ServiceFailure, InvalidRequest {
282
283
        // Read the incoming data from its Mime Multipart encoding
284
        logMetacat.debug("Disassembling MIME multipart form");
285
        InputStream object = null;
286
        InputStream sysmeta = null;
287
288
289
        // handle MMP inputs
290
        File tmpDir = getTempDirectory();
291 6272 leinfelder
        logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
292 6269 leinfelder
        MultipartRequestResolver mrr =
293
        	new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
294
        MultipartRequest mr = null;
295
		try {
296
			mr = mrr.resolveMultipart(request);
297
		} catch (Exception e) {
298
            throw new ServiceFailure("1202",
299
            		"Could not resolve multipart files: " + e.getMessage());
300
		}
301 6272 leinfelder
        logMetacat.debug("resolved multipart request");
302 6269 leinfelder
        Map<String, File> files = mr.getMultipartFiles();
303
        if (files == null) {
304
            throw new ServiceFailure("1202", "create/update must have multipart files with names 'object' and 'sysmeta'");
305
        }
306 6272 leinfelder
        logMetacat.debug("got multipart files");
307 6269 leinfelder
308
        if (files.keySet() == null) {
309 6272 leinfelder
            logMetacat.error("No file keys in MMP request.");
310 6269 leinfelder
            throw new ServiceFailure("1202", "No file keys found in MMP.  " +
311
                    "create/update must have multipart files with names 'object' and 'sysmeta'");
312
        }
313
314
		// for logging purposes, dump out the key-value pairs that constitute the request
315
		// 3 types exist: request params, multipart params, and multipart files
316
        Iterator it = files.keySet().iterator();
317 6272 leinfelder
        logMetacat.debug("iterating through files");
318 6269 leinfelder
        while (it.hasNext()) {
319
            String key = (String)it.next();
320 6272 leinfelder
            logMetacat.debug("files key: " + key);
321
            logMetacat.debug("files value: " + files.get(key));
322 6269 leinfelder
        }
323
324
        multipartparams = mr.getMultipartParameters();
325
        it = multipartparams.keySet().iterator();
326 6272 leinfelder
        logMetacat.debug("iterating through multipartparams");
327 6269 leinfelder
        while (it.hasNext()) {
328
            String key = (String)it.next();
329 6272 leinfelder
            logMetacat.debug("multipartparams key: " + key);
330
            logMetacat.debug("multipartparams value: " + multipartparams.get(key));
331 6269 leinfelder
        }
332
333
        it = params.keySet().iterator();
334 6272 leinfelder
        logMetacat.debug("iterating through params");
335 6269 leinfelder
        while (it.hasNext()) {
336
            String key = (String)it.next();
337 6272 leinfelder
            logMetacat.debug("param key: " + key);
338
            logMetacat.debug("param value: " + params.get(key));
339 6269 leinfelder
        }
340 6272 leinfelder
        logMetacat.debug("done iterating the request...");
341 6269 leinfelder
342
        File smFile = files.get("sysmeta");
343
		if (smFile == null) {
344
		    throw new InvalidRequest("1102", "Missing the required file-part 'sysmeta' from the multipart request.");
345
		}
346 6272 leinfelder
        logMetacat.debug("smFile: " + smFile.getAbsolutePath());
347 6269 leinfelder
        File objFile = files.get("object");
348
		if (objFile == null) {
349
		    throw new InvalidRequest("1102", "Missing the required file-part 'object' from the multipart request.");
350
		}
351 6272 leinfelder
        logMetacat.debug("objectfile: " + objFile.getAbsolutePath());
352 6269 leinfelder
353
        return files;
354
    }
355
356 6140 cjones
		/**
357 5374 berkley
     *  copies request parameters to a hashtable which is given as argument to native metacathandler functions
358
     */
359 6247 leinfelder
    protected void initParams() {
360 5211 jones
361 5374 berkley
        String name = null;
362
        String[] value = null;
363
        Enumeration paramlist = request.getParameterNames();
364
        while (paramlist.hasMoreElements()) {
365
            name = (String) paramlist.nextElement();
366
            value = request.getParameterValues(name);
367
            params.put(name, value);
368
        }
369
    }
370 6469 leinfelder
371 5211 jones
    /**
372 5808 berkley
     * locate the boundary marker for an MMP
373 5462 berkley
     * @param is
374
     * @return
375 5808 berkley
     * @throws IOException
376 5462 berkley
     */
377 5639 berkley
    protected static String[] findBoundaryString(InputStream is)
378 6268 leinfelder
        throws IOException {
379 5639 berkley
        String[] endResult = new String[2];
380 5637 berkley
        String boundary = "";
381 5639 berkley
        String searchString = "boundary=";
382 5637 berkley
        byte[] b = new byte[1024];
383 5639 berkley
        int numbytes = is.read(b, 0, 1024);
384 5826 berkley
385 5639 berkley
        while(numbytes != -1)
386 5636 berkley
        {
387 5639 berkley
            String s = new String(b, 0, numbytes);
388 5826 berkley
            int searchStringIndex = s.indexOf(searchString);
389 5639 berkley
390
            if(s.indexOf("\"", searchStringIndex + searchString.length() + 1) == -1)
391
            { //the end of the boundary is in the next byte array
392
                boundary = s.substring(searchStringIndex + searchString.length() + 1, s.length());
393
            }
394
            else if(!boundary.startsWith("--"))
395
            { //we can read the whole boundary from this byte array
396
                boundary = s.substring(searchStringIndex + searchString.length() + 1,
397
                    s.indexOf("\"", searchStringIndex + searchString.length() + 1));
398
                boundary = "--" + boundary;
399
                endResult[0] = boundary;
400
                endResult[1] = s.substring(s.indexOf("\"", searchStringIndex + searchString.length() + 1) + 1,
401
                        s.length());
402
                break;
403
            }
404
            else
405
            { //we're now reading the 2nd byte array to get the rest of the boundary
406
                searchString = "\"";
407
                searchStringIndex = s.indexOf(searchString);
408
                boundary += s.substring(0, searchStringIndex);
409
                boundary = "--" + boundary;
410
                endResult[0] = boundary;
411
                endResult[1] = s.substring(s.indexOf("\"", searchStringIndex + searchString.length() + 1) + 1,
412
                        s.length());
413
                break;
414
            }
415 5637 berkley
        }
416 5639 berkley
        return endResult;
417
    }
418
419 5808 berkley
    /**
420 5838 berkley
     * return the directory where temp files are stored
421
     * @return
422
     */
423 6248 leinfelder
    protected static File getTempDirectory()
424 5838 berkley
    {
425
        File tmpDir = null;
426 6267 leinfelder
        Logger logMetacat = Logger.getLogger(D1ResourceHandler.class);
427 6268 leinfelder
        try {
428 5639 berkley
            tmpDir = new File(PropertyService.getProperty("application.tempDir"));
429
        }
430 6268 leinfelder
        catch(PropertyNotFoundException pnfe) {
431 6267 leinfelder
            logMetacat.error("D1ResourceHandler.writeMMPPartstoFiles: " +
432 5639 berkley
                    "application.tmpDir not found.  Using /tmp instead.");
433
            tmpDir = new File("/tmp");
434
        }
435 5838 berkley
        return tmpDir;
436 5639 berkley
    }
437
438 5637 berkley
    /**
439 5211 jones
     * Prints xml response
440
     * @param message Message to be displayed
441
     * @param response Servlet response that xml message will be printed
442
     * */
443 6247 leinfelder
    protected void printError(String message, HttpServletResponse response) {
444 5211 jones
        try {
445 6267 leinfelder
            logMetacat.error("D1ResourceHandler: Printing error to servlet response: " + message);
446 5211 jones
            PrintWriter out = response.getWriter();
447
            response.setContentType("text/xml");
448
            out.println("<?xml version=\"1.0\"?>");
449
            out.println("<error>");
450
            out.println(message);
451
            out.println("</error>");
452
            out.close();
453
        } catch (IOException e) {
454
            e.printStackTrace();
455
        }
456
    }
457 5370 berkley
458 5692 berkley
    /**
459
     * serialize a D1 exception using jibx
460
     * @param e
461
     * @param out
462
     */
463 6247 leinfelder
    protected void serializeException(BaseException e, OutputStream out) {
464 5319 jones
        // TODO: Use content negotiation to determine which return format to use
465
        response.setContentType("text/xml");
466
        response.setStatus(e.getCode());
467 5512 berkley
468 6267 leinfelder
        logMetacat.error("D1ResourceHandler: Serializing exception with code " + e.getCode() + ": " + e.getMessage());
469 5512 berkley
        e.printStackTrace();
470
471 5319 jones
        try {
472
            IOUtils.write(e.serialize(BaseException.FMT_XML), out);
473
        } catch (IOException e1) {
474
            logMetacat.error("Error writing exception to stream. "
475
                    + e1.getMessage());
476
        }
477
    }
478
479 5211 jones
}