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.IOException;
27
import java.io.InputStream;
28
import java.io.OutputStream;
29
import java.io.PrintWriter;
30
import java.security.PrivateKey;
31
import java.security.cert.X509Certificate;
32
import java.util.Enumeration;
33
import java.util.Hashtable;
34
import java.util.Iterator;
35
import java.util.List;
36
import java.util.Map;
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
import org.apache.commons.fileupload.FileUploadException;
44
import org.apache.commons.io.IOUtils;
45
import org.apache.log4j.Logger;
46
import org.dataone.client.auth.CertificateManager;
47
import org.dataone.mimemultipart.MultipartRequest;
48
import org.dataone.mimemultipart.MultipartRequestResolver;
49
import org.dataone.portal.PortalCertificateManager;
50
import org.dataone.service.exceptions.BaseException;
51
import org.dataone.service.exceptions.InvalidRequest;
52
import org.dataone.service.exceptions.ServiceFailure;
53
import org.dataone.service.types.v1.Group;
54
import org.dataone.service.types.v1.Person;
55
import org.dataone.service.types.v1.Session;
56
import org.dataone.service.types.v1.Subject;
57
import org.dataone.service.types.v1.SubjectInfo;
58

    
59
import edu.ucsb.nceas.metacat.MetacatHandler;
60
import edu.ucsb.nceas.metacat.properties.PropertyService;
61
import edu.ucsb.nceas.metacat.service.SessionService;
62
import edu.ucsb.nceas.metacat.util.RequestUtil;
63
import edu.ucsb.nceas.metacat.util.SessionData;
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
	/** Maximum size of uploads, defaults to 1GB if not set in property file */
85
	protected static int MAX_UPLOAD_SIZE = 1000000000;
86

    
87
    /*
88
     * API Resources
89
     */
90
    protected static final String RESOURCE_BASE_URL = "d1";
91

    
92
    protected static final String RESOURCE_OBJECTS = "object";
93
    protected static final String RESOURCE_META = "meta";
94
    protected static final String RESOURCE_LOG = "log";
95
    
96
    protected static final String RESOURCE_QUERY = "query";
97
    
98
    protected static final String RESOURCE_IS_AUTHORIZED = "isAuthorized";
99
    protected static final String RESOURCE_ACCESS_RULES = "accessRules";
100
    
101
    protected static final String RESOURCE_VIEWS = "views";
102

    
103
    
104
    /*
105
     * API Functions used as URL parameters
106
     */
107
    protected static final String FUNCTION_NAME_INSERT = "insert";
108
    protected static final String FUNCTION_NAME_UPDATE = "update";
109
    
110
    protected ServletContext servletContext;
111
    protected Logger logMetacat;
112
    protected MetacatHandler handler;
113
    protected HttpServletRequest request;
114
    protected HttpServletResponse response;
115

    
116
    protected Hashtable<String, String[]> params;
117
    protected Map<String, List<String>> multipartparams;
118
    
119
    // D1 certificate-based authentication
120
    protected Session session;
121

    
122
    /**Initializes new instance by setting servlet context,request and response*/
123
    public D1ResourceHandler(ServletContext servletContext,
124
            HttpServletRequest request, HttpServletResponse response) {
125
        this.servletContext = servletContext;
126
        this.request = request;
127
        this.response = response;
128
        logMetacat = Logger.getLogger(D1ResourceHandler.class);
129
		try {
130
			MAX_UPLOAD_SIZE = Integer.parseInt(PropertyService.getProperty("dataone.max_upload_size"));
131
		} catch (PropertyNotFoundException e) {
132
			// Just use our default as no max size is set in the properties file
133
			logMetacat.warn("Property not found: " + "dataone.max_upload_size");
134
		}
135

    
136
    }
137

    
138
    /**
139
     * This function is called from REST API servlet and handles each request 
140
     * 
141
     * @param httpVerb (GET, POST, PUT or DELETE)
142
     */
143
    public void handle(byte httpVerb) {
144
        logMetacat = Logger.getLogger(D1ResourceHandler.class);
145
        try {
146
  
147
        	// initialize the session - three options
148
        	// #1
149
        	// load session from certificate in request
150
            session = CertificateManager.getInstance().getSession(request);
151
            
152
            // #2
153
            if (session == null) {
154
	        	// check for session-based certificate from the portal
155
            	try {
156
		        	String configurationFileName = servletContext.getInitParameter("oa4mp:client.config.file");
157
		        	String configurationFilePath = servletContext.getRealPath(configurationFileName);
158
		        	PortalCertificateManager portalManager = new PortalCertificateManager(configurationFilePath);
159
		        	logMetacat.debug("Initialized the PortalCertificateManager using config file: " + configurationFilePath);
160
		        	X509Certificate certificate = portalManager.getCertificate(request);
161
		        	logMetacat.debug("Retrieved certificate: " + certificate);
162
			    	PrivateKey key = portalManager.getPrivateKey(request);
163
			    	logMetacat.debug("Retrieved key: " + key);
164
			    	if (certificate != null && key != null) {
165
			        	request.setAttribute("javax.servlet.request.X509Certificate", certificate);
166
			        	logMetacat.debug("Added certificate to the request: " + certificate.toString());
167
			    	}
168
			    	
169
		            // reload session from certificate that we jsut set in request
170
		            session = CertificateManager.getInstance().getSession(request);
171
            	} catch (Throwable t) {
172
            		// don't require configured OAuth4MyProxy
173
            		//logMetacat.error(t.getMessage(), t);
174
            	}
175
            }
176
            
177
            // #3
178
            // last resort, check for Metacat sessionid
179
            if (session == null) {
180
	            SessionData sessionData = RequestUtil.getSessionData(request);
181
				if (sessionData != null) {
182
					// is it not the public session?
183
					if (!SessionService.getInstance().getPublicSession().getUserName().equals(sessionData.getUserName())) {
184
						session = new Session();
185
						String userName = sessionData.getUserName();
186
						String[] groupNames = sessionData.getGroupNames();
187
						Subject userSubject = new Subject();
188
						userSubject.setValue(userName);
189
						session.setSubject(userSubject);
190
						SubjectInfo subjectInfo = new SubjectInfo();
191
						Person person = new Person();
192
						person.setSubject(userSubject);
193
						if (groupNames != null && groupNames.length > 0) {
194
							for (String groupName: groupNames) {
195
								Group group = new Group();
196
								group.setGroupName(groupName);
197
								Subject groupSubject = new Subject();
198
								groupSubject.setValue(groupName);
199
								group.setSubject(groupSubject);
200
								subjectInfo.addGroup(group);
201
								person.addIsMemberOf(groupSubject);
202
							}
203
						}
204
						subjectInfo.addPerson(person);
205
						session.setSubjectInfo(subjectInfo);
206
					}
207
				}
208
            }
209
			
210
            // initialize the parameters
211
            params = new Hashtable<String, String[]>();
212
            initParams();
213

    
214
            // create the handler for interacting with Metacat
215
            Timer timer = new Timer();
216
            handler = new MetacatHandler(timer);
217

    
218
        } catch (Exception e) {
219
        	// TODO: more D1 structure when failing here
220
        	response.setStatus(400);
221
            printError("Incorrect resource!", response);
222
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
223
        }
224
    }
225

    
226
    /**
227
     * subclasses should provide a more useful implementation
228
     * @return
229
     */
230
    protected boolean isD1Enabled() {
231
    	
232
    	return true;	
233
    }
234
    
235
    protected String parseTrailing(String resource, String token) {
236
    	// get the rest
237
        String extra = null;
238
        if (resource.indexOf(token) != -1) {
239
        	// what comes after the token?
240
            extra = resource.substring(resource.indexOf(token) + token.length());
241
            // remove the slash
242
            if (extra.startsWith("/")) {
243
            	extra = extra.substring(1);
244
            }
245
            // is there anything left?
246
            if (extra.length() == 0) {
247
            	extra = null;
248
            }
249
        }
250
        return extra;
251
    }
252

    
253
    /**
254
     * Parse string parameters from the mime multipart entity of the request.
255
     * Populates the multipartparams map
256
     * 
257
     * @throws IOException
258
     * @throws FileUploadException
259
     * @throws Exception
260
     */
261
    protected void collectMultipartParams() 
262
        throws IOException, FileUploadException, Exception {
263
        
264
        File tmpDir = getTempDirectory();
265
        MultipartRequest mr = null;
266

    
267
        // Read the incoming data from its Mime Multipart encoding
268
        logMetacat.debug("Parsing rights holder info from the mime multipart entity");
269

    
270
        // handle MMP inputs
271
        MultipartRequestResolver mrr = 
272
            new MultipartRequestResolver(tmpDir.getAbsolutePath(), MAX_UPLOAD_SIZE, 0);
273

    
274
        mr = mrr.resolveMultipart(request);
275
        logMetacat.debug("Resolved the rights holder info from the mime multipart entity.");
276
                    
277
        // we only have params in this MMP entity
278
        multipartparams = mr.getMultipartParameters();
279
                
280
    }
281
    
282
    /**
283
     * Process the MMP request that includes files for each param
284
     * @return map of param key and the temp file that contains the encoded information
285
     * @throws ServiceFailure
286
     * @throws InvalidRequest
287
     */
288
    protected Map<String, File> collectMultipartFiles() 
289
        throws ServiceFailure, InvalidRequest {
290
   
291
        // Read the incoming data from its Mime Multipart encoding
292
        logMetacat.debug("Disassembling MIME multipart form");
293
        
294
        // handle MMP inputs
295
        File tmpDir = getTempDirectory();
296
        logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
297
        MultipartRequestResolver mrr = 
298
        	new MultipartRequestResolver(tmpDir.getAbsolutePath(),  MAX_UPLOAD_SIZE, 0);
299
        MultipartRequest mr = null;
300
		    try {
301
		    	  mr = mrr.resolveMultipart(request);
302
            
303
		    } catch (Exception e) {
304
                throw new ServiceFailure("1202", 
305
                		"Could not resolve multipart files: " + e.getMessage());
306
		    }
307
        logMetacat.debug("resolved multipart request");
308
        Map<String, File> files = mr.getMultipartFiles();
309
        if (files == null) {
310
            throw new ServiceFailure("1202", "no multipart files found");
311
        }
312
        logMetacat.debug("got multipart files");
313
        
314
        if (files.keySet() == null) {
315
            logMetacat.error("No file keys in MMP request.");
316
            throw new ServiceFailure("1202", "No file keys found in MMP.");
317
        }
318
        
319
        multipartparams = mr.getMultipartParameters();
320

    
321
		    // for logging purposes, dump out the key-value pairs that constitute the request
322
		    // 3 types exist: request params, multipart params, and multipart files
323
        if (logMetacat.isDebugEnabled()) {
324
	        Iterator<String> it = files.keySet().iterator();
325
	        logMetacat.debug("iterating through files");
326
	        while (it.hasNext()) {
327
	            String key = it.next();
328
	            logMetacat.debug("files key: " + key);
329
	            logMetacat.debug("files value: " + files.get(key));
330
	        }
331
	        
332
	        it = multipartparams.keySet().iterator();
333
	        logMetacat.debug("iterating through multipartparams");
334
	        while (it.hasNext()) {
335
	            String key = (String)it.next();
336
	            logMetacat.debug("multipartparams key: " + key);
337
	            logMetacat.debug("multipartparams value: " + multipartparams.get(key));
338
	        }
339
	        
340
	        it = params.keySet().iterator();
341
	        logMetacat.debug("iterating through params");
342
	        while (it.hasNext()) {
343
	            String key = (String)it.next();
344
	            logMetacat.debug("param key: " + key);
345
	            logMetacat.debug("param value: " + params.get(key));
346
	        }
347
	        logMetacat.debug("done iterating the request...");
348
        }
349
        
350
        return files;
351
    }
352
    
353
		/**
354
     *  copies request parameters to a hashtable which is given as argument to 
355
     *  native metacathandler functions  
356
     */
357
    protected void initParams() {
358

    
359
        String name = null;
360
        String[] value = null;
361
        Enumeration paramlist = request.getParameterNames();
362
        while (paramlist.hasMoreElements()) {
363
            name = (String) paramlist.nextElement();
364
            value = request.getParameterValues(name);
365
            params.put(name, value);
366
        }
367
    }
368
    
369
    /**
370
     * Collect the multipart params from the request
371
     * @throws Exception 
372
     */
373
	protected void initMultipartParams() throws Exception {
374
		
375
		// Read the incoming data from its Mime Multipart encoding
376
		logMetacat.debug("Disassembling MIME multipart form");
377
	
378
		// handle MMP inputs
379
		File tmpDir = getTempDirectory();
380
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
381
		MultipartRequestResolver mrr = 
382
			new MultipartRequestResolver(tmpDir.getAbsolutePath(),  MAX_UPLOAD_SIZE, 0);
383
		MultipartRequest mr = mrr.resolveMultipart(request);
384
		
385
		multipartparams = mr.getMultipartParameters();
386
	}
387
   
388
    /**
389
     * locate the boundary marker for an MMP
390
     * @param is
391
     * @return
392
     * @throws IOException
393
     */
394
    protected static String[] findBoundaryString(InputStream is)
395
        throws IOException {
396
        String[] endResult = new String[2];
397
        String boundary = "";
398
        String searchString = "boundary=";
399
        byte[] b = new byte[1024];
400
        int numbytes = is.read(b, 0, 1024);
401

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

    
496
}
(2-2/5)