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.AuthSession;
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
        	// first try the usual methods
148
        	session = PortalCertificateManager.getInstance().getSession(request);
149
        	
150
            // last resort, check for Metacat sessionid
151
            if (session == null) {
152
	            SessionData sessionData = RequestUtil.getSessionData(request);
153
				if (sessionData != null) {
154
					// is it not the public session?
155
					if (!SessionService.getInstance().getPublicSession().getUserName().equals(sessionData.getUserName())) {
156
						session = new Session();
157
						String userName = sessionData.getUserName();
158
						String[] groupNames = sessionData.getGroupNames();
159
						Subject userSubject = new Subject();
160
						userSubject.setValue(userName);
161
						session.setSubject(userSubject);
162
						SubjectInfo subjectInfo = new SubjectInfo();
163
						Person person = new Person();
164
						person.setSubject(userSubject);
165
						if (groupNames != null && groupNames.length > 0) {
166
							for (String groupName: groupNames) {
167
								Group group = new Group();
168
								group.setGroupName(groupName);
169
								Subject groupSubject = new Subject();
170
								groupSubject.setValue(groupName);
171
								group.setSubject(groupSubject);
172
								subjectInfo.addGroup(group);
173
								person.addIsMemberOf(groupSubject);
174
							}
175
						}
176
						subjectInfo.addPerson(person);
177
						session.setSubjectInfo(subjectInfo);
178
					}
179
				}
180
            } else {
181
                //The session is not null. However, the if we got the session is from a token, the ldap group information for is missing if we logged in by the ldap account.
182
                //here we just patch it.
183
                Subject subject = session.getSubject();
184
                if(subject != null) {
185
                    String dn = subject.getValue();
186
                    logMetacat.debug("D1ReourceHandler.handle - the subject dn in the session is "+dn+" This dn will be used to look up the group information");
187
                    if(dn != null) {
188
                        String username = null;
189
                        String password = null;
190
                       
191
                        String[] groups = null;
192
                        try {
193
                            AuthSession auth = new AuthSession();
194
                            groups = auth.getGroups(username, password, dn);
195
                        } catch (Exception e) {
196
                            logMetacat.warn("D1ReourceHandler.handle - we can't get group information for the user "+dn+" from the authentication interface since :", e);
197
                        }
198

    
199
                        if(groups != null) {
200
                            SubjectInfo subjectInfo = session.getSubjectInfo();
201
                            if(subjectInfo != null) {
202
                                logMetacat.debug("D1ReourceHandler.handle - the subject information is NOT null when we try to figure out the group information.");
203
                                //we don't overwrite the existing subject info, just add the new groups informations
204
                                List<Person> persons = subjectInfo.getPersonList();
205
                                Person targetPerson = null;
206
                                if(persons != null) {
207
                                    for(Person person : persons) {
208
                                        if(person.getSubject().equals(subject)) {
209
                                            targetPerson = person;
210
                                            logMetacat.debug("D1ReourceHandler.handle - we find a person with the subject "+dn+" in the subject info.");
211
                                            break;
212
                                        }
213
                                    }
214
                                }
215
                                boolean newPerson = false;
216
                                if(targetPerson == null) {
217
                                    newPerson = true;
218
                                    targetPerson = new Person();
219
                                    targetPerson.setSubject(subject);
220
                                }
221
                                for (int i=0; i<groups.length; i++) {
222
                                    logMetacat.debug("D1ReourceHandler.handle - create the group "+groups[i]+" for an existing subject info.");
223
                                    Group group = new Group();
224
                                    group.setGroupName(groups[i]);
225
                                    Subject groupSubject = new Subject();
226
                                    groupSubject.setValue(groups[i]);
227
                                    group.setSubject(groupSubject);
228
                                    subjectInfo.addGroup(group);
229
                                    targetPerson.addIsMemberOf(groupSubject);
230
                                }
231
                                if(newPerson) {
232
                                    subjectInfo.addPerson(targetPerson);
233
                                }
234
                            } else {
235
                                logMetacat.debug("D1ReourceHandler.handle - the subject information is NOT null when we try to figure out the group information.");
236
                                subjectInfo = new SubjectInfo();
237
                                Person person = new Person();
238
                                person.setSubject(subject);
239
                                for (int i=0; i<groups.length; i++) {
240
                                    logMetacat.debug("D1ReourceHandler.handle - create the group "+groups[i]+" for a new subject info.");
241
                                    Group group = new Group();
242
                                    group.setGroupName(groups[i]);
243
                                    Subject groupSubject = new Subject();
244
                                    groupSubject.setValue(groups[i]);
245
                                    group.setSubject(groupSubject);
246
                                    subjectInfo.addGroup(group);
247
                                    person.addIsMemberOf(groupSubject);
248
                                }
249
                                subjectInfo.addPerson(person);
250
                                session.setSubjectInfo(subjectInfo);
251
                            }
252
                        }
253
                    }
254
                }
255
            }
256
			
257
            // initialize the parameters
258
            params = new Hashtable<String, String[]>();
259
            initParams();
260

    
261
            // create the handler for interacting with Metacat
262
            Timer timer = new Timer();
263
            handler = new MetacatHandler(timer);
264

    
265
        } catch (Exception e) {
266
        	// TODO: more D1 structure when failing here
267
        	response.setStatus(400);
268
            printError("Incorrect resource!", response);
269
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
270
        }
271
    }
272
    
273
  
274
    /**
275
     * subclasses should provide a more useful implementation
276
     * @return
277
     */
278
    protected boolean isD1Enabled() {
279
    	
280
    	return true;	
281
    }
282
    
283
    protected String parseTrailing(String resource, String token) {
284
    	// get the rest
285
        String extra = null;
286
        if (resource.indexOf(token) != -1) {
287
        	// what comes after the token?
288
            extra = resource.substring(resource.indexOf(token) + token.length());
289
            // remove the slash
290
            if (extra.startsWith("/")) {
291
            	extra = extra.substring(1);
292
            }
293
            // is there anything left?
294
            if (extra.length() == 0) {
295
            	extra = null;
296
            }
297
        }
298
        return extra;
299
    }
300

    
301
    /**
302
     * Parse string parameters from the mime multipart entity of the request.
303
     * Populates the multipartparams map
304
     * 
305
     * @throws IOException
306
     * @throws FileUploadException
307
     * @throws Exception
308
     */
309
    protected void collectMultipartParams() 
310
        throws IOException, FileUploadException, Exception {
311
        
312
        File tmpDir = getTempDirectory();
313
        MultipartRequest mr = null;
314

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

    
318
        // handle MMP inputs
319
        MultipartRequestResolver mrr = 
320
            new MultipartRequestResolver(tmpDir.getAbsolutePath(), MAX_UPLOAD_SIZE, 0);
321

    
322
        mr = mrr.resolveMultipart(request);
323
        logMetacat.debug("Resolved the rights holder info from the mime multipart entity.");
324
                    
325
        // we only have params in this MMP entity
326
        multipartparams = mr.getMultipartParameters();
327
                
328
    }
329
    
330
    /**
331
     * Process the MMP request that includes files for each param
332
     * @return map of param key and the temp file that contains the encoded information
333
     * @throws ServiceFailure
334
     * @throws InvalidRequest
335
     */
336
    protected Map<String, File> collectMultipartFiles() 
337
        throws ServiceFailure, InvalidRequest {
338
   
339
        // Read the incoming data from its Mime Multipart encoding
340
        logMetacat.debug("Disassembling MIME multipart form");
341
        
342
        // handle MMP inputs
343
        File tmpDir = getTempDirectory();
344
        logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
345
        MultipartRequestResolver mrr = 
346
        	new MultipartRequestResolver(tmpDir.getAbsolutePath(),  MAX_UPLOAD_SIZE, 0);
347
        MultipartRequest mr = null;
348
		    try {
349
		    	  mr = mrr.resolveMultipart(request);
350
            
351
		    } catch (Exception e) {
352
                throw new ServiceFailure("1202", 
353
                		"Could not resolve multipart files: " + e.getMessage());
354
		    }
355
        logMetacat.debug("resolved multipart request");
356
        Map<String, File> files = mr.getMultipartFiles();
357
        if (files == null) {
358
            throw new ServiceFailure("1202", "no multipart files found");
359
        }
360
        logMetacat.debug("got multipart files");
361
        
362
        if (files.keySet() == null) {
363
            logMetacat.error("No file keys in MMP request.");
364
            throw new ServiceFailure("1202", "No file keys found in MMP.");
365
        }
366
        
367
        multipartparams = mr.getMultipartParameters();
368

    
369
		    // for logging purposes, dump out the key-value pairs that constitute the request
370
		    // 3 types exist: request params, multipart params, and multipart files
371
        if (logMetacat.isDebugEnabled()) {
372
	        Iterator<String> it = files.keySet().iterator();
373
	        logMetacat.debug("iterating through files");
374
	        while (it.hasNext()) {
375
	            String key = it.next();
376
	            logMetacat.debug("files key: " + key);
377
	            logMetacat.debug("files value: " + files.get(key));
378
	        }
379
	        
380
	        it = multipartparams.keySet().iterator();
381
	        logMetacat.debug("iterating through multipartparams");
382
	        while (it.hasNext()) {
383
	            String key = (String)it.next();
384
	            logMetacat.debug("multipartparams key: " + key);
385
	            logMetacat.debug("multipartparams value: " + multipartparams.get(key));
386
	        }
387
	        
388
	        it = params.keySet().iterator();
389
	        logMetacat.debug("iterating through params");
390
	        while (it.hasNext()) {
391
	            String key = (String)it.next();
392
	            logMetacat.debug("param key: " + key);
393
	            logMetacat.debug("param value: " + params.get(key));
394
	        }
395
	        logMetacat.debug("done iterating the request...");
396
        }
397
        
398
        return files;
399
    }
400
    
401
		/**
402
     *  copies request parameters to a hashtable which is given as argument to 
403
     *  native metacathandler functions  
404
     */
405
    protected void initParams() {
406

    
407
        String name = null;
408
        String[] value = null;
409
        Enumeration paramlist = request.getParameterNames();
410
        while (paramlist.hasMoreElements()) {
411
            name = (String) paramlist.nextElement();
412
            value = request.getParameterValues(name);
413
            params.put(name, value);
414
        }
415
    }
416
    
417
    /**
418
     * Collect the multipart params from the request
419
     * @throws Exception 
420
     */
421
	protected void initMultipartParams() throws Exception {
422
		
423
		// Read the incoming data from its Mime Multipart encoding
424
		logMetacat.debug("Disassembling MIME multipart form");
425
	
426
		// handle MMP inputs
427
		File tmpDir = getTempDirectory();
428
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
429
		MultipartRequestResolver mrr = 
430
			new MultipartRequestResolver(tmpDir.getAbsolutePath(),  MAX_UPLOAD_SIZE, 0);
431
		MultipartRequest mr = mrr.resolveMultipart(request);
432
		
433
		multipartparams = mr.getMultipartParameters();
434
	}
435
   
436
    /**
437
     * locate the boundary marker for an MMP
438
     * @param is
439
     * @return
440
     * @throws IOException
441
     */
442
    protected static String[] findBoundaryString(InputStream is)
443
        throws IOException {
444
        String[] endResult = new String[2];
445
        String boundary = "";
446
        String searchString = "boundary=";
447
        byte[] b = new byte[1024];
448
        int numbytes = is.read(b, 0, 1024);
449

    
450
        while(numbytes != -1)
451
        {
452
            String s = new String(b, 0, numbytes);
453
            int searchStringIndex = s.indexOf(searchString);
454
            
455
            if(s.indexOf("\"", searchStringIndex + searchString.length() + 1) == -1)
456
            { //the end of the boundary is in the next byte array
457
                boundary = s.substring(searchStringIndex + searchString.length() + 1, s.length());
458
            }
459
            else if(!boundary.startsWith("--"))
460
            { //we can read the whole boundary from this byte array
461
                boundary = s.substring(searchStringIndex + searchString.length() + 1, 
462
                    s.indexOf("\"", searchStringIndex + searchString.length() + 1));
463
                boundary = "--" + boundary;
464
                endResult[0] = boundary;
465
                endResult[1] = s.substring(s.indexOf("\"", searchStringIndex + searchString.length() + 1) + 1,
466
                        s.length());
467
                break;
468
            }
469
            else
470
            { //we're now reading the 2nd byte array to get the rest of the boundary
471
                searchString = "\"";
472
                searchStringIndex = s.indexOf(searchString);
473
                boundary += s.substring(0, searchStringIndex);
474
                boundary = "--" + boundary;
475
                endResult[0] = boundary;
476
                endResult[1] = s.substring(s.indexOf("\"", searchStringIndex + searchString.length() + 1) + 1,
477
                        s.length());
478
                break;
479
            }
480
        }
481
        return endResult;
482
    }
483
    
484
    /**
485
     * return the directory where temp files are stored
486
     * @return
487
     */
488
    protected static File getTempDirectory()
489
    {
490
        File tmpDir = null;
491
        Logger logMetacat = Logger.getLogger(D1ResourceHandler.class);
492
        try {
493
            tmpDir = new File(PropertyService.getProperty("application.tempDir"));
494
        }
495
        catch(PropertyNotFoundException pnfe) {
496
            logMetacat.error("D1ResourceHandler.writeMMPPartstoFiles: " +
497
                    "application.tmpDir not found.  Using /tmp instead.");
498
            tmpDir = new File("/tmp");
499
        }
500
        return tmpDir;
501
    }
502
    
503
    /**
504
     * Prints xml response
505
     * @param message Message to be displayed
506
     * @param response Servlet response that xml message will be printed 
507
     * */
508
    protected void printError(String message, HttpServletResponse response) {
509
        try {
510
            logMetacat.error("D1ResourceHandler: Printing error to servlet response: " + message);
511
            PrintWriter out = response.getWriter();
512
            response.setContentType("text/xml");
513
            out.println("<?xml version=\"1.0\"?>");
514
            out.println("<error>");
515
            out.println(message);
516
            out.println("</error>");
517
            out.close();
518
        } catch (IOException e) {
519
            e.printStackTrace();
520
        }
521
    }
522
    
523
    /**
524
     * serialize a D1 exception using jibx
525
     * @param e
526
     * @param out
527
     */
528
    protected void serializeException(BaseException e, OutputStream out) {
529
        // TODO: Use content negotiation to determine which return format to use
530
        response.setContentType("text/xml");
531
        response.setStatus(e.getCode());
532
        
533
        logMetacat.error("D1ResourceHandler: Serializing exception with code " + e.getCode() + ": " + e.getMessage(), e);
534
        //e.printStackTrace();
535
        
536
        try {
537
            IOUtils.write(e.serialize(BaseException.FMT_XML), out);
538
        } catch (IOException e1) {
539
            logMetacat.error("Error writing exception to stream. " 
540
                    + e1.getMessage());
541
        }
542
    }
543
    
544
    /**
545
     * A method to decode the given string which is a part of a uri.
546
     * 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.
547
     * @param s
548
     * @return null if the given string is null
549
     */
550
    public static String decode(String s) {
551
        String result = null;
552
        if(s != null) {
553
            try
554
            {
555
                result = URLDecoder.decode(s, "UTF-8");
556
            }
557
            catch (UnsupportedEncodingException e)
558
            {
559
                result = URLDecoder.decode(s);
560
            }
561
            System.out.println("After decoded: " + result);
562
        }
563
        
564
        return result;
565
    }
566
}
(2-2/5)