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: leinfelder $'
7
 *     '$Date: 2011-12-13 10:58:38 -0800 (Tue, 13 Dec 2011) $'
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.ByteArrayInputStream;
26
import java.io.File;
27
import java.io.FileInputStream;
28
import java.io.IOException;
29
import java.io.InputStream;
30
import java.io.OutputStream;
31
import java.text.DateFormat;
32
import java.text.ParseException;
33
import java.text.SimpleDateFormat;
34
import java.util.Date;
35
import java.util.Enumeration;
36
import java.util.Map;
37
import java.util.TimeZone;
38
import java.util.concurrent.ExecutorService;
39
import java.util.concurrent.Executors;
40

    
41
import javax.servlet.ServletContext;
42
import javax.servlet.http.HttpServletRequest;
43
import javax.servlet.http.HttpServletResponse;
44
import javax.xml.parsers.ParserConfigurationException;
45

    
46
import org.apache.commons.fileupload.FileUploadException;
47
import org.apache.commons.io.IOUtils;
48
import org.apache.log4j.Logger;
49
import org.dataone.client.ObjectFormatCache;
50
import org.dataone.mimemultipart.MultipartRequest;
51
import org.dataone.mimemultipart.MultipartRequestResolver;
52
import org.dataone.service.exceptions.BaseException;
53
import org.dataone.service.exceptions.IdentifierNotUnique;
54
import org.dataone.service.exceptions.InsufficientResources;
55
import org.dataone.service.exceptions.InvalidRequest;
56
import org.dataone.service.exceptions.InvalidSystemMetadata;
57
import org.dataone.service.exceptions.InvalidToken;
58
import org.dataone.service.exceptions.NotAuthorized;
59
import org.dataone.service.exceptions.NotFound;
60
import org.dataone.service.exceptions.NotImplemented;
61
import org.dataone.service.exceptions.ServiceFailure;
62
import org.dataone.service.exceptions.SynchronizationFailed;
63
import org.dataone.service.exceptions.UnsupportedType;
64
import org.dataone.service.types.v1.AccessPolicy;
65
import org.dataone.service.types.v1.Checksum;
66
import org.dataone.service.types.v1.DescribeResponse;
67
import org.dataone.service.types.v1.Event;
68
import org.dataone.service.types.v1.Identifier;
69
import org.dataone.service.types.v1.Log;
70
import org.dataone.service.types.v1.MonitorList;
71
import org.dataone.service.types.v1.Node;
72
import org.dataone.service.types.v1.NodeReference;
73
import org.dataone.service.types.v1.ObjectFormat;
74
import org.dataone.service.types.v1.ObjectFormatIdentifier;
75
import org.dataone.service.types.v1.ObjectList;
76
import org.dataone.service.types.v1.Permission;
77
import org.dataone.service.types.v1.Subject;
78
import org.dataone.service.types.v1.SystemMetadata;
79
import org.dataone.service.util.DateTimeMarshaller;
80
import org.dataone.service.util.ExceptionHandler;
81
import org.dataone.service.util.TypeMarshaller;
82
import org.jibx.runtime.JiBXException;
83
import org.xml.sax.SAXException;
84

    
85
import edu.ucsb.nceas.metacat.dataone.CNodeService;
86
import edu.ucsb.nceas.metacat.dataone.MNodeService;
87

    
88
/**
89
 * MN REST service implementation handler
90
 * 
91
 * ******************
92
 * MNCore -- DONE
93
 * 		ping() - GET /d1/mn/monitor/ping
94
 * 		log() - GET /d1/mn/log
95
 * 		**getObjectStatistics() - GET /d1/mn/monitor/object
96
 * 		getOperationsStatistics - GET /d1/mn/monitor/event
97
 * 		**getStatus - GET /d1/mn/monitor/status
98
 * 		getCapabilities() - GET /d1/mn/ and /d1/mn/node
99
 * 	
100
 * 	MNRead -- DONE
101
 * 		get() - GET /d1/mn/object/PID
102
 * 		getSystemMetadata() - GET /d1/mn/meta/PID
103
 * 		describe() - HEAD /d1/mn/object/PID
104
 * 		getChecksum() - GET /d1/mn/checksum/PID
105
 * 		listObjects() - GET /d1/mn/object
106
 * 		synchronizationFailed() - POST /d1/mn/error
107
 * 	
108
 * 	MNAuthorization -- DONE
109
 * 		isAuthorized() - GET /d1/mn/isAuthorized/PID
110
 * 		setAccessPolicy() - PUT /d1/mn/accessRules/PID
111
 * 		
112
 * 	MNStorage - DONE
113
 * 		create() - POST /d1/mn/object/PID
114
 * 		update() - PUT /d1/mn/object/PID
115
 * 		delete() - DELETE /d1/mn/object/PID
116
 *    systemMetadataChanged() - POST /dirtySystemMetadata/PID
117
 * 	
118
 * 	MNReplication
119
 * 		replicate() - POST /d1/mn/replicate
120
 *    getReplica() - GET /d1/mn/replica
121
 * 
122
 * ******************
123
 * @author leinfelder
124
 *
125
 */
126
public class MNResourceHandler extends D1ResourceHandler{
127

    
128
    // MN-specific API Resources
129
    protected static final String RESOURCE_MONITOR = "monitor";
130
    protected static final String RESOURCE_REPLICATE = "replicate";
131
    protected static final String RESOURCE_REPLICAS = "replica";
132
    protected static final String RESOURCE_NODE = "node";
133
    protected static final String RESOURCE_ERROR = "error";
134
    protected static final String RESOURCE_META_CHANGED = "dirtySystemMetadata";
135

    
136
    /**
137
     * Initializes new instance by setting servlet context,request and response
138
     * */
139
    public MNResourceHandler(ServletContext servletContext,
140
            HttpServletRequest request, HttpServletResponse response) {
141
    	super(servletContext, request, response);
142
        logMetacat = Logger.getLogger(MNResourceHandler.class);
143
    }
144

    
145
    /**
146
     * This function is called from REST API servlet and handles each request to the servlet 
147
     * 
148
     * @param httpVerb (GET, POST, PUT or DELETE)
149
     */
150
    @Override
151
    public void handle(byte httpVerb) {
152
    	// prepare the handler
153
    	super.handle(httpVerb);
154
    	
155
        try {
156
        	// get the resource
157
            String resource = request.getPathInfo();
158
            resource = resource.substring(resource.indexOf("/") + 1);
159
            
160
            // default to node info
161
            if (resource.equals("")) {
162
                resource = RESOURCE_NODE;
163
            }
164
            
165
            // get the rest of the path info
166
            String extra = null;
167
                        
168
            logMetacat.debug("handling verb " + httpVerb + " request with resource '" + resource + "'");
169
            logMetacat.debug("resource: '" + resource + "'");
170
            boolean status = false;
171
            
172
            if (resource != null) {
173

    
174
                if (resource.startsWith(RESOURCE_NODE)) {
175
                    // node response
176
                    node();
177
                    status = true;
178
                } else if (resource.startsWith(RESOURCE_ACCESS_RULES)) {
179
                    if (httpVerb == PUT) {
180
                    	// after the command
181
                        extra = parseTrailing(resource, RESOURCE_ACCESS_RULES);
182
	                	// set the access rules
183
	                    setAccess(extra);
184
	                    status = true;
185
	                    logMetacat.debug("done setting access");
186
                    }
187
                } else if (resource.startsWith(RESOURCE_IS_AUTHORIZED)) {
188
                    if (httpVerb == GET) {
189
                    	// after the command
190
                        extra = parseTrailing(resource, RESOURCE_IS_AUTHORIZED);
191
	                	// check the access rules
192
	                    isAuthorized(extra);
193
	                    status = true;
194
	                    logMetacat.debug("done getting access");
195
                    }
196
                } else if (resource.startsWith(RESOURCE_META)) {
197
                    logMetacat.debug("Using resource 'meta'");
198
                    // get
199
                    if (httpVerb == GET) {
200
                    	// after the command
201
                        extra = parseTrailing(resource, RESOURCE_META);
202
                        getSystemMetadataObject(extra);
203
                        status = true;
204
                    }
205
                    
206
                } else if (resource.startsWith(RESOURCE_OBJECTS)) {
207
                    logMetacat.debug("Using resource 'object'");
208
                    // after the command
209
                    extra = parseTrailing(resource, RESOURCE_OBJECTS);
210
                    logMetacat.debug("objectId: " + extra);
211
                    logMetacat.debug("verb:" + httpVerb);
212

    
213
                    if (httpVerb == GET) {
214
                        getObject(extra);
215
                        status = true;
216
                    } else if (httpVerb == POST) {
217
                        putObject(extra, FUNCTION_NAME_INSERT);
218
                        status = true;
219
                    } else if (httpVerb == PUT) {
220
                        putObject(extra, FUNCTION_NAME_UPDATE);
221
                        status = true;
222
                    } else if (httpVerb == DELETE) {
223
                        deleteObject(extra);
224
                        status = true;
225
                    } else if (httpVerb == HEAD) {
226
                        describeObject(extra);
227
                        status = true;
228
                    }
229
                  
230
                } else if (resource.startsWith(RESOURCE_LOG)) {
231
                    logMetacat.debug("Using resource 'log'");
232
                    // handle log events
233
                    if (httpVerb == GET) {
234
                        getLog();
235
                        status = true;
236
                    }
237
                } else if (resource.startsWith(RESOURCE_CHECKSUM)) {
238
                    logMetacat.debug("Using resource 'checksum'");
239
                    // handle checksum requests
240
                    if (httpVerb == GET) {
241
                    	// after the command
242
                        extra = parseTrailing(resource, RESOURCE_CHECKSUM);
243
                        checksum(extra);
244
                        status = true;
245
                    }
246
                } else if (resource.startsWith(RESOURCE_MONITOR)) {
247
                    // there are various parts to monitoring
248
                    if (httpVerb == GET) {
249
                    	// after the command
250
                        extra = parseTrailing(resource, RESOURCE_MONITOR);
251
                    	// health monitoring calls
252
                        status = monitor(extra);
253
                    }
254
                } else if (resource.startsWith(RESOURCE_REPLICATE)) {
255
                	if (httpVerb == POST) {
256
	                    logMetacat.debug("processing replicate request");
257
	                    replicate();
258
	                    status = true;
259
                	}
260
                } else if (resource.startsWith(RESOURCE_ERROR)) {
261
	                // sync error
262
	                if (httpVerb == POST) {
263
	                    syncError();
264
	                    status = true;
265
	                }
266
                } else if (resource.startsWith(RESOURCE_META_CHANGED)) {
267
                    // system metadata changed
268
                    if (httpVerb == POST) {
269
                        systemMetadataChanged();
270
                        status = true;
271
                    }
272
                } else if (resource.startsWith(RESOURCE_REPLICAS)) {
273
                    // get replica
274
                    if (httpVerb == GET) {
275
                        extra = parseTrailing(resource, RESOURCE_REPLICAS);
276
                        getReplica(extra);
277
                        status = true;
278
                    }
279
                }
280
                
281
                if (!status) {
282
                	throw new ServiceFailure("0000", "Unknown error, status = " + status);
283
                }
284
            } else {
285
            	throw new InvalidRequest("0000", "No resource matched for " + resource);
286
            }
287
        } catch (BaseException be) {
288
        	// report Exceptions as clearly as possible
289
        	OutputStream out = null;
290
			try {
291
				out = response.getOutputStream();
292
			} catch (IOException e) {
293
				logMetacat.error("Could not get output stream from response", e);
294
			}
295
            serializeException(be, out);
296
        } catch (Exception e) {
297
            // report Exceptions as clearly and generically as possible
298
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
299
        	OutputStream out = null;
300
			try {
301
				out = response.getOutputStream();
302
			} catch (IOException ioe) {
303
				logMetacat.error("Could not get output stream from response", ioe);
304
			}
305
			ServiceFailure se = new ServiceFailure("0000", e.getMessage());
306
            serializeException(se, out);
307
        }
308
    }
309
    
310

    
311
    /**
312
     * Handles notification of system metadata changes for the given identifier
313
     * 
314
     * @param id  the identifier for the object
315
     * @throws InvalidToken 
316
     * @throws InvalidRequest 
317
     * @throws NotAuthorized 
318
     * @throws ServiceFailure 
319
     * @throws NotImplemented 
320
     */
321
    private void systemMetadataChanged() 
322
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
323
        InvalidToken {
324

    
325
        long serialVersion = 0L;
326
        String serialVersionStr = null;
327
        Date dateSysMetaLastModified = null;
328
        String dateSysMetaLastModifiedStr = null;
329
        Identifier pid = null;
330
        
331
        // mkae sure we have the multipart params
332
        try {
333
			initMultipartParams();
334
		} catch (Exception e1) {
335
			throw new ServiceFailure("1333", "Could not collect the multipart params for the request");
336
		}
337
        
338
        // get the pid
339
        try {
340
        	String id = multipartparams.get("pid").get(0);
341
        	pid = new Identifier();
342
            pid.setValue(id);            
343
        } catch (NullPointerException e) {
344
            String msg = "The 'pid' must be provided as a parameter and was not.";
345
            logMetacat.error(msg);
346
            throw new InvalidRequest("1334", msg);
347
        }      
348
        
349
        // get the serialVersion
350
        try {
351
            serialVersionStr = multipartparams.get("serialVersion").get(0);
352
            serialVersion = new Long(serialVersionStr).longValue();
353
            
354
        } catch (NullPointerException e) {
355
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
356
            logMetacat.error(msg);
357
            throw new InvalidRequest("1334", msg);
358
            
359
        }       
360
        
361
        // get the dateSysMetaLastModified
362
        try {
363
            dateSysMetaLastModifiedStr = multipartparams.get("dateSysMetaLastModified").get(0);
364
            dateSysMetaLastModified = DateTimeMarshaller.deserializeDateToUTC(dateSysMetaLastModifiedStr);
365
            
366
        } catch (NullPointerException e) {
367
            String msg = 
368
                "The 'dateSysMetaLastModified' must be provided as a " + 
369
                "parameter and was not, or was an invalid representation of the timestamp.";
370
            logMetacat.error(msg);
371
            throw new InvalidRequest("1334", msg);
372
            
373
        }       
374
        
375
        // call the service
376
        MNodeService.getInstance(request).systemMetadataChanged(session, pid, serialVersion, dateSysMetaLastModified);
377
        response.setStatus(200);
378
    }
379

    
380
    /**
381
     * Checks the access policy
382
     * @param id
383
     * @return
384
     * @throws ServiceFailure
385
     * @throws InvalidToken
386
     * @throws NotFound
387
     * @throws NotAuthorized
388
     * @throws NotImplemented
389
     * @throws InvalidRequest
390
     */
391
    private boolean isAuthorized(String id) throws ServiceFailure, InvalidToken, NotFound, NotAuthorized, NotImplemented, InvalidRequest {
392
		Identifier pid = new Identifier();
393
		pid.setValue(id);
394
		Permission permission = null;
395
		try {
396
			String perm = params.get("action")[0];
397
			permission = Permission.convert(perm);
398
		} catch (Exception e) {
399
			logMetacat.warn("No permission specified");
400
		}
401
		boolean result = MNodeService.getInstance(request).isAuthorized(session, pid, permission);
402
		response.setStatus(200);
403
		response.setContentType("text/xml");
404
		return result;
405
    }
406
    
407
    /**
408
     * Processes failed synchronization message
409
     * @throws NotImplemented
410
     * @throws ServiceFailure
411
     * @throws NotAuthorized
412
     * @throws InvalidRequest
413
     * @throws JiBXException
414
     * @throws IllegalAccessException 
415
     * @throws InstantiationException 
416
     * @throws IOException 
417
     */
418
    private void syncError() throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, JiBXException, IOException, InstantiationException, IllegalAccessException {
419
    	SynchronizationFailed syncFailed = null;
420
		try {
421
			syncFailed = collectSynchronizationFailed();
422
		} catch (ParserConfigurationException e) {
423
			throw new ServiceFailure("2161", e.getMessage());
424
		} catch (SAXException e) {
425
			throw new ServiceFailure("2161", e.getMessage());
426
		}
427
		
428
		MNodeService.getInstance(request).synchronizationFailed(session, syncFailed);
429
    }
430
    
431

    
432
    /**
433
     * Handles the monitoring resources
434
     * @return
435
     * @throws NotFound
436
     * @throws ParseException
437
     * @throws NotImplemented
438
     * @throws ServiceFailure
439
     * @throws NotAuthorized
440
     * @throws InvalidRequest
441
     * @throws InsufficientResources
442
     * @throws UnsupportedType
443
     * @throws IOException
444
     * @throws JiBXException
445
     */
446
    private boolean monitor(String pathInfo) 
447
      throws NotFound, ParseException, NotImplemented, ServiceFailure, 
448
      NotAuthorized, InvalidRequest, InsufficientResources, UnsupportedType, 
449
      IOException, JiBXException {
450
    	logMetacat.debug("processing monitor request");
451
        
452
        logMetacat.debug("verb is GET");
453
        logMetacat.debug("pathInfo is " + pathInfo);
454
        
455
        if (pathInfo.toLowerCase().equals("ping")) {
456
            logMetacat.debug("processing ping request");
457
            boolean result = MNodeService.getInstance(request).ping();
458
            return result;
459
            
460
        } else if (pathInfo.toLowerCase().equals("status")) {
461
            logMetacat.debug("processing status request");
462
            // TODO: implement in MNCore
463
            //MNodeService.getInstance().getStatus();
464
            return false;
465
            
466
        } else if (pathInfo.toLowerCase().equals("object")) {
467
            logMetacat.debug("processing object request");
468
            Identifier pid = null;
469
            ObjectFormat format = null;
470
            if (params.containsKey("format")) {
471
                String f = params.get("format")[0];
472
                format = ObjectFormatCache.getInstance().getFormat(f);
473
            }
474
            if (params.containsKey("pid")) {
475
                String id = params.get("pid")[0];
476
                pid = new Identifier();
477
                pid.setValue(id);
478
            }
479
            
480
            // TODO: implement in MNCore
481
            //ObjectStatistics objectStats = MNodeService.getInstance().getObjectStatistics(format, pid);
482
            return false;
483
            
484
        } else if (pathInfo.toLowerCase().equals("event")) {
485
            logMetacat.debug("processing event request");
486
            ObjectFormatIdentifier fmtid = null;
487
            String fromDateStr = null;
488
            Date fromDate = null;
489
            String toDateStr = null;
490
            Date toDate = null;
491
            String requestor = null;
492
            Subject subject = null;
493
            String eventName = null;
494
            Event event = null;
495

    
496
            if (params.containsKey("formatId")) {
497
                String f = params.get("formatId")[0];
498
                fmtid = ObjectFormatCache.getInstance().getFormat(f).getFormatId();
499
            }
500
            
501
            if (params.containsKey("fromDate")) {
502
                fromDateStr = params.get("fromDate")[0];
503
                fromDate = getDateAsUTC(fromDateStr);
504
            }
505
            
506
            if (params.containsKey("toDate")) {
507
              toDateStr = params.get("toDate")[0];
508
              toDate = getDateAsUTC(toDateStr);
509
            }
510
            
511
            if (params.containsKey("requestor")) {
512
            	requestor = params.get("requestor")[0];
513
            	subject = new Subject();
514
            	subject.setValue(requestor);
515
            }
516
            
517
            if (params.containsKey("event")) {
518
            	eventName = params.get("event")[0];
519
                event = Event.convert(eventName);
520
            }
521
            
522
            MonitorList monitorList = MNodeService.getInstance(request).getOperationStatistics(session, fromDate, toDate, subject, event, fmtid);
523
            
524
            OutputStream out = response.getOutputStream();
525
            response.setStatus(200);
526
            response.setContentType("text/xml");
527
            
528
            TypeMarshaller.marshalTypeToOutputStream(monitorList, out);
529
            
530
            return true;
531
            
532
        }
533
        
534
        return false;
535
    }
536
    
537
    /*
538
     * Parse an ISO8601 date string, returning a Date in UTC time if the string
539
     * ends in 'Z'.
540
     * 
541
     * @param fromDateStr -  the date string to be parsed
542
     * @return date -  the date object represented by the string
543
     */
544
    private Date getDateAsUTC(String fromDateStr)
545
      throws ParseException {
546

    
547
    	Date date = null;
548
    	
549
    	try {
550
    		// try the expected date format
551
        // a date format for date string arguments
552
        DateFormat dateFormat = 
553
        	new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
554

    
555
	      date = dateFormat.parse(fromDateStr);
556
      
557
    	} catch (ParseException e) {
558
    		// try the date with the UTC indicator
559
        DateFormat utcDateFormat = 
560
        	new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
561
        utcDateFormat.setTimeZone(TimeZone.getTimeZone("GMT-0"));
562
        date = utcDateFormat.parse(fromDateStr);
563
        
564
      }
565
    	
566
    	return date;
567
    }
568

    
569
		/**
570
     * Calculate the checksum 
571
     * @throws NotImplemented
572
     * @throws JiBXException
573
     * @throws IOException
574
     * @throws InvalidToken
575
     * @throws ServiceFailure
576
     * @throws NotAuthorized
577
     * @throws NotFound
578
     * @throws InvalidRequest
579
     */
580
    private void checksum(String pid) throws NotImplemented, JiBXException, IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest {
581
    	String checksumAlgorithm = "MD5";
582
        
583
        Identifier pidid = new Identifier();
584
        pidid.setValue(pid);
585
        try {
586
            checksumAlgorithm = params.get("checksumAlgorithm")[0];
587
        } catch(Exception e) {
588
            //do nothing.  default to MD5
589
        	logMetacat.warn("No algorithm specified, using default: " + checksumAlgorithm);
590
        }
591
        logMetacat.debug("getting checksum for object " + pid + " with algorithm " + checksumAlgorithm);
592
        
593
        Checksum c = MNodeService.getInstance(request).getChecksum(session, pidid, checksumAlgorithm);
594
        logMetacat.debug("got checksum " + c.getValue());
595
        response.setStatus(200);
596
        logMetacat.debug("serializing response");
597
        TypeMarshaller.marshalTypeToOutputStream(c, response.getOutputStream());
598
        logMetacat.debug("done serializing response.");
599
        
600
    }
601
    
602
	/**
603
     * handle the replicate action for MN
604
	 * @throws JiBXException 
605
	 * @throws FileUploadException 
606
	 * @throws IOException 
607
	 * @throws InvalidRequest 
608
	 * @throws ServiceFailure 
609
	 * @throws UnsupportedType 
610
	 * @throws InsufficientResources 
611
	 * @throws NotAuthorized 
612
	 * @throws NotImplemented 
613
	 * @throws IllegalAccessException 
614
	 * @throws InstantiationException 
615
     */
616
    private void replicate() 
617
        throws ServiceFailure, InvalidRequest, IOException, FileUploadException, 
618
        JiBXException, NotImplemented, NotAuthorized, InsufficientResources, 
619
        UnsupportedType, InstantiationException, IllegalAccessException {
620

    
621
        logMetacat.debug("in POST replicate()");
622
        
623
        //parse the systemMetadata
624
        final SystemMetadata sysmeta = collectSystemMetadata();
625
        
626
        String sn = multipartparams.get("sourceNode").get(0);
627
        logMetacat.debug("sourceNode: " + sn);
628
        final NodeReference sourceNode = new NodeReference();
629
        sourceNode.setValue(sn);
630
        
631
        // run it in a thread to avoid connection timeout
632
        Runnable runner = new Runnable() {
633
			@Override
634
			public void run() {
635
				try {
636
			        MNodeService.getInstance(request).replicate(session, sysmeta, sourceNode);
637
				} catch (Exception e) {
638
					throw new RuntimeException(e.getMessage(), e);
639
				}
640
			}
641
    	};
642
    	ExecutorService executor = Executors.newSingleThreadExecutor();
643
    	executor.execute(runner);
644
    	executor.shutdown();
645
        
646
    	// thread was started, so we return success
647
        response.setStatus(200);
648
        
649
    }
650

    
651
    /**
652
     * Handle the getReplica action for the MN
653
     * @param id  the identifier for the object
654
     * @throws NotFound 
655
     * @throws ServiceFailure 
656
     * @throws NotImplemented 
657
     * @throws NotAuthorized 
658
     * @throws InvalidToken 
659
     * @throws InvalidRequest 
660
     */
661
    private void getReplica(String id) 
662
        throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented, 
663
        ServiceFailure, NotFound {
664
        
665
        Identifier pid = new Identifier();
666
        pid.setValue(id);
667
        OutputStream out = null;
668
        InputStream dataBytes = null;
669
                
670
        try {
671
            // call the service
672
            dataBytes = MNodeService.getInstance(request).getReplica(session, pid);
673

    
674
            response.setContentType("application/octet-stream");
675
            response.setStatus(200);
676
            out = response.getOutputStream();
677
            // write the object to the output stream
678
            IOUtils.copyLarge(dataBytes, out);
679
            
680
        } catch (IOException e) {
681
            String msg = "There was an error writing the output: " + e.getMessage();
682
            logMetacat.error(msg);
683
            throw new ServiceFailure("2181", msg);
684
        
685
        }
686

    
687
    }
688

    
689
    /**
690
     * Get the Node information
691
     * 
692
     * @throws JiBXException
693
     * @throws IOException
694
     * @throws InvalidRequest 
695
     * @throws ServiceFailure 
696
     * @throws NotAuthorized 
697
     * @throws NotImplemented 
698
     */
699
    private void node() 
700
        throws JiBXException, IOException, NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest {
701
        
702
        Node n = MNodeService.getInstance(request).getCapabilities();
703
        
704
        response.setContentType("text/xml");
705
        response.setStatus(200);
706
        TypeMarshaller.marshalTypeToOutputStream(n, response.getOutputStream());
707
        
708
    }
709
    
710
    /**
711
     * MN_crud.describe()
712
     * http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.describe
713
     * @param pid
714
     * @throws InvalidRequest 
715
     * @throws NotImplemented 
716
     * @throws NotFound 
717
     * @throws NotAuthorized 
718
     * @throws ServiceFailure 
719
     * @throws InvalidToken 
720
     */
721
    private void describeObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest
722
    {
723
        response.setStatus(200);
724
        response.setContentType("text/xml");
725
        
726
        Identifier id = new Identifier();
727
        id.setValue(pid);
728

    
729
        DescribeResponse dr = MNodeService.getInstance(request).describe(session, id);
730
        //response.addHeader("pid", pid);
731
        response.addHeader("DataONE-Checksum", dr.getDataONE_Checksum().getAlgorithm() + "," + dr.getDataONE_Checksum().getValue());
732
        response.addHeader("Content-Length", dr.getContent_Length() + "");
733
        response.addHeader("Last-Modified", DateTimeMarshaller.serializeDateToUTC(dr.getLast_Modified()));
734
        response.addHeader("DataONE-ObjectFormat", dr.getDataONE_ObjectFormatIdentifier().getValue());
735
        response.addHeader("DataONE-SerialVersion", dr.getSerialVersion().toString());
736

    
737
        
738
    }
739
    
740
    /**
741
     * get the logs based on passed params.  Available 
742
     * See http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.getLogRecords
743
     * for more info
744
     * @throws NotImplemented 
745
     * @throws InvalidRequest 
746
     * @throws NotAuthorized 
747
     * @throws ServiceFailure 
748
     * @throws InvalidToken 
749
     * @throws IOException 
750
     * @throws JiBXException 
751
     */
752
    private void getLog() throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented, IOException, JiBXException
753
    {
754
            
755
        Date fromDate = null;
756
        Date toDate = null;
757
        Event event = null;
758
        Integer start = null;
759
        Integer count = null;
760
        
761
        try {
762
        	String fromDateS = params.get("fromDate")[0];
763
            logMetacat.debug("param fromDateS: " + fromDateS);
764
            fromDate = DateTimeMarshaller.deserializeDateToUTC(fromDateS);
765
        } catch (Exception e) {
766
        	logMetacat.warn("Could not parse fromDate: " + e.getMessage());
767
        }
768
        try {
769
        	String toDateS = params.get("toDate")[0];
770
            logMetacat.debug("param toDateS: " + toDateS);
771
            toDate = DateTimeMarshaller.deserializeDateToUTC(toDateS);
772
        } catch (Exception e) {
773
        	logMetacat.warn("Could not parse toDate: " + e.getMessage());
774
		}
775
        try {
776
        	String eventS = params.get("event")[0];
777
            event = Event.convert(eventS);
778
        } catch (Exception e) {
779
        	logMetacat.warn("Could not parse event: " + e.getMessage());
780
		}
781
        logMetacat.debug("fromDate: " + fromDate + " toDate: " + toDate);
782
        
783
        try {
784
        	start =  Integer.parseInt(params.get("start")[0]);
785
        } catch (Exception e) {
786
			logMetacat.warn("Could not parse start: " + e.getMessage());
787
		}
788
        try {
789
        	count =  Integer.parseInt(params.get("count")[0]);
790
        } catch (Exception e) {
791
			logMetacat.warn("Could not parse count: " + e.getMessage());
792
		}
793
        
794
        logMetacat.debug("calling getLogRecords");
795
        Log log = MNodeService.getInstance(request).getLogRecords(session, fromDate, toDate, event, start, count);
796
        
797
        OutputStream out = response.getOutputStream();
798
        response.setStatus(200);
799
        response.setContentType("text/xml");
800
        
801
        TypeMarshaller.marshalTypeToOutputStream(log, out);
802
        
803
    }
804
    
805
    /**
806
     * Implements REST version of DataONE CRUD API --> get
807
     * @param pid ID of data object to be read
808
     * @throws NotImplemented 
809
     * @throws InvalidRequest 
810
     * @throws NotFound 
811
     * @throws NotAuthorized 
812
     * @throws ServiceFailure 
813
     * @throws InvalidToken 
814
     * @throws IOException 
815
     * @throws JiBXException 
816
     */
817
    protected void getObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
818
        OutputStream out = null;
819
        
820
        if (pid != null) { //get a specific document                
821
            Identifier id = new Identifier();
822
            id.setValue(pid);
823
                
824
            SystemMetadata sm = MNodeService.getInstance(request).getSystemMetadata(session, id);
825
            
826
            //set the content type
827
            if (sm.getFormatId().getValue().trim().equals(
828
            		ObjectFormatCache.getInstance().getFormat("text/csv").getFormatId().getValue()))
829
            {
830
                response.setContentType("text/csv");
831
                response.setHeader("Content-Disposition", "inline; filename=" + id.getValue() + ".csv");
832
            }
833
            else if (sm.getFormatId().getValue().trim().equals(
834
            		ObjectFormatCache.getInstance().getFormat("text/plain").getFormatId().getValue()))
835
            {
836
                response.setContentType("text/plain");
837
                response.setHeader("Content-Disposition", "inline; filename=" + id.getValue() + ".txt");
838
            } 
839
            else if (sm.getFormatId().getValue().trim().equals(
840
            		ObjectFormatCache.getInstance().getFormat("application/octet-stream").getFormatId().getValue()))
841
            {
842
                response.setContentType("application/octet-stream");
843
            }
844
            else
845
            {
846
                response.setContentType("text/xml");
847
                response.setHeader("Content-Disposition", "inline; filename=" + id.getValue() + ".xml");
848
            }
849
            
850
            InputStream data = MNodeService.getInstance(request).get(session, id);
851

    
852
            out = response.getOutputStream();  
853
            IOUtils.copyLarge(data, out);
854
            
855
        }
856
        else
857
        { //call listObjects with specified params
858
            Date startTime = null;
859
            Date endTime = null;
860
            ObjectFormat objectFormat = null;
861
            boolean replicaStatus = false;
862
            int start = 0;
863
            //TODO: make the max count into a const
864
            int count = 1000;
865
            Enumeration paramlist = request.getParameterNames();
866
            while (paramlist.hasMoreElements()) 
867
            { //parse the params and make the crud call
868
                String name = (String) paramlist.nextElement();
869
                String[] value = (String[])request.getParameterValues(name);
870

    
871
                if (name.equals("startTime") && value != null)
872
                {
873
                    try
874
                    {
875
                      //startTime = dateFormat.parse(value[0]);
876
                    	startTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
877
                        //startTime = parseDateAndConvertToGMT(value[0]);
878
                    }
879
                    catch(Exception e)
880
                    {  //if we can't parse it, just don't use the startTime param
881
                        logMetacat.warn("Could not parse startTime: " + value[0]);
882
                        startTime = null;
883
                    }
884
                }
885
                else if(name.equals("endTime") && value != null)
886
                {
887
                    try
888
                    {
889
                      //endTime = dateFormat.parse(value[0]);
890
                    	endTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
891
                        //endTime = parseDateAndConvertToGMT(value[0]);
892
                    }
893
                    catch(Exception e)
894
                    {  //if we can't parse it, just don't use the endTime param
895
                        logMetacat.warn("Could not parse endTime: " + value[0]);
896
                        endTime = null;
897
                    }
898
                }
899
                else if(name.equals("objectFormat") && value != null) 
900
                {
901
                    objectFormat = ObjectFormatCache.getInstance().getFormat(value[0]);
902
                }
903
                else if(name.equals("replicaStatus") && value != null)
904
                {
905
                    if(value != null && 
906
                       value.length > 0 && 
907
                       (value[0].equals("true") || value[0].equals("TRUE") || value[0].equals("YES")))
908
                    {
909
                        replicaStatus = true;
910
                    }
911
                }
912
                else if(name.equals("start") && value != null)
913
                {
914
                    start = new Integer(value[0]).intValue();
915
                }
916
                else if(name.equals("count") && value != null)
917
                {
918
                    count = new Integer(value[0]).intValue();
919
                }
920
            }
921
            //make the crud call
922
            logMetacat.debug("session: " + session + " startTime: " + startTime +
923
                    " endtime: " + endTime + " objectFormat: " + 
924
                    objectFormat + " replicaStatus: " + replicaStatus + 
925
                    " start: " + start + " count: " + count);
926
           
927
            ObjectFormatIdentifier fmtid = null;
928
           
929
            if ( objectFormat != null ) {
930
          	 fmtid = objectFormat.getFormatId();
931
          	 
932
            }
933
            ObjectList ol = 
934
           	 MNodeService.getInstance(request).listObjects(session, startTime, endTime, 
935
               fmtid, replicaStatus, start, count);
936
           
937
            out = response.getOutputStream();  
938
            response.setStatus(200);
939
            response.setContentType("text/xml");
940
            // Serialize and write it to the output stream
941
            TypeMarshaller.marshalTypeToOutputStream(ol, out);
942
            
943
        }
944
        
945
    }
946
    
947

    
948
    /**
949
     * Retrieve System Metadata
950
     * @param pid
951
     * @throws InvalidToken
952
     * @throws ServiceFailure
953
     * @throws NotAuthorized
954
     * @throws NotFound
955
     * @throws InvalidRequest
956
     * @throws NotImplemented
957
     * @throws IOException
958
     * @throws JiBXException
959
     */
960
    protected void getSystemMetadataObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
961

    
962
        Identifier id = new Identifier();
963
        id.setValue(pid);
964
        SystemMetadata sysmeta = MNodeService.getInstance(request).getSystemMetadata(session, id);
965
        
966
        response.setContentType("text/xml");
967
        response.setStatus(200);
968
        OutputStream out = response.getOutputStream();
969
        
970
        // Serialize and write it to the output stream
971
        TypeMarshaller.marshalTypeToOutputStream(sysmeta, out);
972
   }
973
    
974
    
975
    /**
976
     * Inserts or updates the object
977
     * 
978
     * @param pid - ID of data object to be inserted or updated.  If action is update, the pid
979
     *               is the existing pid.  If insert, the pid is the new one
980
     * @throws InvalidRequest 
981
     * @throws ServiceFailure 
982
     * @throws JiBXException 
983
     * @throws NotImplemented 
984
     * @throws InvalidSystemMetadata 
985
     * @throws InsufficientResources 
986
     * @throws UnsupportedType 
987
     * @throws IdentifierNotUnique 
988
     * @throws NotAuthorized 
989
     * @throws InvalidToken 
990
     * @throws NotFound 
991
     * @throws IOException 
992
     * @throws IllegalAccessException 
993
     * @throws InstantiationException 
994
     */
995
    protected void putObject(String pid, String action) throws ServiceFailure, InvalidRequest, JiBXException, InvalidToken, NotAuthorized, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, NotFound, IOException, InstantiationException, IllegalAccessException {
996
        logMetacat.debug("putObject with pid " + pid);
997
        logMetacat.debug("Entering putObject: " + pid + "/" + action);
998
        
999
        response.setStatus(200);
1000
        response.setContentType("text/xml");
1001
        OutputStream out = response.getOutputStream();
1002
        
1003
        // Read the incoming data from its Mime Multipart encoding
1004
    	Map<String, File> files = collectMultipartFiles();
1005
        InputStream object = null;
1006
        InputStream sysmeta = null;
1007

    
1008
        File smFile = files.get("sysmeta");
1009
        sysmeta = new FileInputStream(smFile);
1010
        File objFile = files.get("object");
1011
        object = new FileInputStream(objFile);
1012
        
1013
        if (action.equals(FUNCTION_NAME_INSERT)) { 
1014
            // handle inserts
1015
            logMetacat.debug("Commence creation...");
1016
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1017

    
1018
            Identifier id = new Identifier();
1019
            id.setValue(pid);
1020
            logMetacat.debug("creating object with pid " + pid);
1021
            Identifier rId = MNodeService.getInstance(request).create(session, id, object, smd);
1022
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1023
            
1024
        } else if (action.equals(FUNCTION_NAME_UPDATE)) {
1025
        	// handle updates
1026
        	
1027
            // construct pids
1028
            Identifier newPid = new Identifier();
1029
            try {
1030
            	String newPidString = multipartparams.get("newPid").get(0);
1031
                newPid.setValue(newPidString);
1032
            } catch (Exception e) {
1033
				logMetacat.warn("newPid not given");
1034
			}
1035
            
1036
            Identifier obsoletedPid = new Identifier();
1037
            obsoletedPid.setValue(pid);
1038
           
1039
            logMetacat.debug("Commence update...");
1040
            
1041
            // get the systemmetadata object
1042
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1043

    
1044
            Identifier rId = MNodeService.getInstance(request).update(session, obsoletedPid, object, newPid, smd);
1045
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1046
        } else {
1047
            throw new InvalidRequest("1000", "Operation must be create or update.");
1048
        }
1049
            
1050
            
1051
    }
1052

    
1053
    /**
1054
     * Handle delete 
1055
     * @param pid ID of data object to be deleted
1056
     * @throws IOException
1057
     * @throws InvalidRequest 
1058
     * @throws NotImplemented 
1059
     * @throws NotFound 
1060
     * @throws NotAuthorized 
1061
     * @throws ServiceFailure 
1062
     * @throws InvalidToken 
1063
     * @throws JiBXException 
1064
     */
1065
    private void deleteObject(String pid) throws IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest, JiBXException 
1066
    {
1067

    
1068
        OutputStream out = response.getOutputStream();
1069
        response.setStatus(200);
1070
        response.setContentType("text/xml");
1071

    
1072
        Identifier id = new Identifier();
1073
        id.setValue(pid);
1074

    
1075
        logMetacat.debug("Calling delete");
1076
        MNodeService.getInstance(request).delete(session, id);
1077
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1078
        
1079
    }    
1080
    
1081
    /**
1082
     * set the access perms on a document
1083
     * @throws JiBXException 
1084
     * @throws InvalidRequest 
1085
     * @throws NotImplemented 
1086
     * @throws NotAuthorized 
1087
     * @throws NotFound 
1088
     * @throws ServiceFailure 
1089
     * @throws InvalidToken 
1090
     * @throws IllegalAccessException 
1091
     * @throws InstantiationException 
1092
     * @throws IOException 
1093
     * @throws SAXException 
1094
     * @throws ParserConfigurationException 
1095
     */
1096
    protected void setAccess(String pid) throws JiBXException, InvalidToken, ServiceFailure, NotFound, NotAuthorized, NotImplemented, InvalidRequest, IOException, InstantiationException, IllegalAccessException, ParserConfigurationException, SAXException
1097
    {
1098
    
1099
        //String pid = params.get("pid")[0];
1100
        Identifier id = new Identifier();
1101
        id.setValue(pid);
1102
        
1103
        AccessPolicy accessPolicy = collectAccessPolicy();
1104
        MNodeService.getInstance(request).setAccessPolicy(session, id, accessPolicy);
1105
        
1106
        
1107
    }
1108

    
1109
	protected SynchronizationFailed collectSynchronizationFailed() throws IOException, ServiceFailure, InvalidRequest, JiBXException, InstantiationException, IllegalAccessException, ParserConfigurationException, SAXException  {
1110
		
1111
		// Read the incoming data from its Mime Multipart encoding
1112
		logMetacat.debug("Disassembling MIME multipart form");
1113
		InputStream sf = null;
1114
	
1115
		// handle MMP inputs
1116
		File tmpDir = getTempDirectory();
1117
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
1118
		MultipartRequestResolver mrr = 
1119
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
1120
		MultipartRequest mr = null;
1121
		try {
1122
			mr = mrr.resolveMultipart(request);
1123
		} catch (Exception e) {
1124
			throw new ServiceFailure("2161", 
1125
					"Could not resolve multipart: " + e.getMessage());
1126
		}
1127
		logMetacat.debug("resolved multipart request");
1128
		Map<String, File> files = mr.getMultipartFiles();
1129
		if (files == null || files.keySet() == null) {
1130
			throw new InvalidRequest("2163",
1131
					"must have multipart file with name 'message'");
1132
		}
1133
		logMetacat.debug("got multipart files");
1134
	
1135
		multipartparams = mr.getMultipartParameters();
1136
	
1137
		File sfFile = files.get("message");
1138
		if (sfFile == null) {
1139
			throw new InvalidRequest("2163",
1140
					"Missing the required file-part 'message' from the multipart request.");
1141
		}
1142
		logMetacat.debug("sfFile: " + sfFile.getAbsolutePath());
1143
		sf = new FileInputStream(sfFile);
1144
	
1145
		SynchronizationFailed syncFailed = (SynchronizationFailed) ExceptionHandler.deserializeXml(sf, "Error deserializing exception");
1146
		return syncFailed;
1147
	}
1148

    
1149
}
(8-8/9)