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.io.UnsupportedEncodingException;
31
import java.net.URLDecoder;
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.mimemultipart.MultipartRequest;
47
import org.dataone.mimemultipart.MultipartRequestResolver;
48
import org.dataone.portal.PortalCertificateManager;
49
import org.dataone.service.exceptions.BaseException;
50
import org.dataone.service.exceptions.InvalidRequest;
51
import org.dataone.service.exceptions.ServiceFailure;
52
import org.dataone.service.types.v1.Group;
53
import org.dataone.service.types.v1.Person;
54
import org.dataone.service.types.v1.Session;
55
import org.dataone.service.types.v1.Subject;
56
import org.dataone.service.types.v1.SubjectInfo;
57

    
58
import edu.ucsb.nceas.metacat.MetacatHandler;
59
import edu.ucsb.nceas.metacat.properties.PropertyService;
60
import edu.ucsb.nceas.metacat.service.SessionService;
61
import edu.ucsb.nceas.metacat.util.RequestUtil;
62
import edu.ucsb.nceas.metacat.util.SessionData;
63
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
64
/**
65
 * 
66
 * Base class for handling D1 REST calls in Metacat
67
 * 
68
 * @author leinfelder
69
 */
70
public class D1ResourceHandler {
71

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

    
83
	/** Maximum size of uploads, defaults to 1GB if not set in property file */
84
	protected static int MAX_UPLOAD_SIZE = 1000000000;
85

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

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

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

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

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

    
135
    }
136

    
137
    /**
138
     * This function is called from REST API servlet and handles each request 
139
     * 
140
     * @param httpVerb (GET, POST, PUT or DELETE)
141
     */
142
    public void handle(byte httpVerb) {
143
        logMetacat = Logger.getLogger(D1ResourceHandler.class);
144
        try {
145
  
146
        	// first try the usual methods
147
        	session = PortalCertificateManager.getInstance().getSession(request);
148
        	
149
            // last resort, check for Metacat sessionid
150
            if (session == null) {
151
	            SessionData sessionData = RequestUtil.getSessionData(request);
152
				if (sessionData != null) {
153
					// is it not the public session?
154
					if (!SessionService.getInstance().getPublicSession().getUserName().equals(sessionData.getUserName())) {
155
						session = new Session();
156
						String userName = sessionData.getUserName();
157
						String[] groupNames = sessionData.getGroupNames();
158
						Subject userSubject = new Subject();
159
						userSubject.setValue(userName);
160
						session.setSubject(userSubject);
161
						SubjectInfo subjectInfo = new SubjectInfo();
162
						Person person = new Person();
163
						person.setSubject(userSubject);
164
						if (groupNames != null && groupNames.length > 0) {
165
							for (String groupName: groupNames) {
166
								Group group = new Group();
167
								group.setGroupName(groupName);
168
								Subject groupSubject = new Subject();
169
								groupSubject.setValue(groupName);
170
								group.setSubject(groupSubject);
171
								subjectInfo.addGroup(group);
172
								person.addIsMemberOf(groupSubject);
173
							}
174
						}
175
						subjectInfo.addPerson(person);
176
						session.setSubjectInfo(subjectInfo);
177
					}
178
				}
179
            }
180
			
181
            // initialize the parameters
182
            params = new Hashtable<String, String[]>();
183
            initParams();
184

    
185
            // create the handler for interacting with Metacat
186
            Timer timer = new Timer();
187
            handler = new MetacatHandler(timer);
188

    
189
        } catch (Exception e) {
190
        	// TODO: more D1 structure when failing here
191
        	response.setStatus(400);
192
            printError("Incorrect resource!", response);
193
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
194
        }
195
    }
196

    
197
    /**
198
     * subclasses should provide a more useful implementation
199
     * @return
200
     */
201
    protected boolean isD1Enabled() {
202
    	
203
    	return true;	
204
    }
205
    
206
    protected String parseTrailing(String resource, String token) {
207
    	// get the rest
208
        String extra = null;
209
        if (resource.indexOf(token) != -1) {
210
        	// what comes after the token?
211
            extra = resource.substring(resource.indexOf(token) + token.length());
212
            // remove the slash
213
            if (extra.startsWith("/")) {
214
            	extra = extra.substring(1);
215
            }
216
            // is there anything left?
217
            if (extra.length() == 0) {
218
            	extra = null;
219
            }
220
        }
221
        return extra;
222
    }
223

    
224
    /**
225
     * Parse string parameters from the mime multipart entity of the request.
226
     * Populates the multipartparams map
227
     * 
228
     * @throws IOException
229
     * @throws FileUploadException
230
     * @throws Exception
231
     */
232
    protected void collectMultipartParams() 
233
        throws IOException, FileUploadException, Exception {
234
        
235
        File tmpDir = getTempDirectory();
236
        MultipartRequest mr = null;
237

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

    
241
        // handle MMP inputs
242
        MultipartRequestResolver mrr = 
243
            new MultipartRequestResolver(tmpDir.getAbsolutePath(), MAX_UPLOAD_SIZE, 0);
244

    
245
        mr = mrr.resolveMultipart(request);
246
        logMetacat.debug("Resolved the rights holder info from the mime multipart entity.");
247
                    
248
        // we only have params in this MMP entity
249
        multipartparams = mr.getMultipartParameters();
250
                
251
    }
252
    
253
    /**
254
     * Process the MMP request that includes files for each param
255
     * @return map of param key and the temp file that contains the encoded information
256
     * @throws ServiceFailure
257
     * @throws InvalidRequest
258
     */
259
    protected Map<String, File> collectMultipartFiles() 
260
        throws ServiceFailure, InvalidRequest {
261
   
262
        // Read the incoming data from its Mime Multipart encoding
263
        logMetacat.debug("Disassembling MIME multipart form");
264
        
265
        // handle MMP inputs
266
        File tmpDir = getTempDirectory();
267
        logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
268
        MultipartRequestResolver mrr = 
269
        	new MultipartRequestResolver(tmpDir.getAbsolutePath(),  MAX_UPLOAD_SIZE, 0);
270
        MultipartRequest mr = null;
271
		    try {
272
		    	  mr = mrr.resolveMultipart(request);
273
            
274
		    } catch (Exception e) {
275
                throw new ServiceFailure("1202", 
276
                		"Could not resolve multipart files: " + e.getMessage());
277
		    }
278
        logMetacat.debug("resolved multipart request");
279
        Map<String, File> files = mr.getMultipartFiles();
280
        if (files == null) {
281
            throw new ServiceFailure("1202", "no multipart files found");
282
        }
283
        logMetacat.debug("got multipart files");
284
        
285
        if (files.keySet() == null) {
286
            logMetacat.error("No file keys in MMP request.");
287
            throw new ServiceFailure("1202", "No file keys found in MMP.");
288
        }
289
        
290
        multipartparams = mr.getMultipartParameters();
291

    
292
		    // for logging purposes, dump out the key-value pairs that constitute the request
293
		    // 3 types exist: request params, multipart params, and multipart files
294
        if (logMetacat.isDebugEnabled()) {
295
	        Iterator<String> it = files.keySet().iterator();
296
	        logMetacat.debug("iterating through files");
297
	        while (it.hasNext()) {
298
	            String key = it.next();
299
	            logMetacat.debug("files key: " + key);
300
	            logMetacat.debug("files value: " + files.get(key));
301
	        }
302
	        
303
	        it = multipartparams.keySet().iterator();
304
	        logMetacat.debug("iterating through multipartparams");
305
	        while (it.hasNext()) {
306
	            String key = (String)it.next();
307
	            logMetacat.debug("multipartparams key: " + key);
308
	            logMetacat.debug("multipartparams value: " + multipartparams.get(key));
309
	        }
310
	        
311
	        it = params.keySet().iterator();
312
	        logMetacat.debug("iterating through params");
313
	        while (it.hasNext()) {
314
	            String key = (String)it.next();
315
	            logMetacat.debug("param key: " + key);
316
	            logMetacat.debug("param value: " + params.get(key));
317
	        }
318
	        logMetacat.debug("done iterating the request...");
319
        }
320
        
321
        return files;
322
    }
323
    
324
		/**
325
     *  copies request parameters to a hashtable which is given as argument to 
326
     *  native metacathandler functions  
327
     */
328
    protected void initParams() {
329

    
330
        String name = null;
331
        String[] value = null;
332
        Enumeration paramlist = request.getParameterNames();
333
        while (paramlist.hasMoreElements()) {
334
            name = (String) paramlist.nextElement();
335
            value = request.getParameterValues(name);
336
            params.put(name, value);
337
        }
338
    }
339
    
340
    /**
341
     * Collect the multipart params from the request
342
     * @throws Exception 
343
     */
344
	protected void initMultipartParams() throws Exception {
345
		
346
		// Read the incoming data from its Mime Multipart encoding
347
		logMetacat.debug("Disassembling MIME multipart form");
348
	
349
		// handle MMP inputs
350
		File tmpDir = getTempDirectory();
351
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
352
		MultipartRequestResolver mrr = 
353
			new MultipartRequestResolver(tmpDir.getAbsolutePath(),  MAX_UPLOAD_SIZE, 0);
354
		MultipartRequest mr = mrr.resolveMultipart(request);
355
		
356
		multipartparams = mr.getMultipartParameters();
357
	}
358
   
359
    /**
360
     * locate the boundary marker for an MMP
361
     * @param is
362
     * @return
363
     * @throws IOException
364
     */
365
    protected static String[] findBoundaryString(InputStream is)
366
        throws IOException {
367
        String[] endResult = new String[2];
368
        String boundary = "";
369
        String searchString = "boundary=";
370
        byte[] b = new byte[1024];
371
        int numbytes = is.read(b, 0, 1024);
372

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