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: 2012-03-13 15:05:50 -0700 (Tue, 13 Mar 2012) $'
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.FileInputStream;
27
import java.io.IOException;
28
import java.io.InputStream;
29
import java.io.OutputStream;
30
import java.text.DateFormat;
31
import java.text.ParseException;
32
import java.text.SimpleDateFormat;
33
import java.util.Date;
34
import java.util.Enumeration;
35
import java.util.Map;
36
import java.util.TimeZone;
37
import java.util.concurrent.ExecutorService;
38
import java.util.concurrent.Executors;
39

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

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

    
84
import edu.ucsb.nceas.metacat.dataone.MNodeService;
85
import edu.ucsb.nceas.metacat.properties.PropertyService;
86
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
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
    @Override
146
    protected boolean isD1Enabled() {
147
    	
148
    	boolean enabled = false;
149
    	try {
150
			enabled = Boolean.parseBoolean(PropertyService.getProperty("dataone.mn.services.enabled"));
151
		} catch (PropertyNotFoundException e) {
152
			logMetacat.error("Could not check if DataONE is enabled: " + e.getMessage());
153
		}
154
    	
155
    	return enabled;	
156
    }
157

    
158
    /**
159
     * This function is called from REST API servlet and handles each request to the servlet 
160
     * 
161
     * @param httpVerb (GET, POST, PUT or DELETE)
162
     */
163
    @Override
164
    public void handle(byte httpVerb) {
165
    	// prepare the handler
166
    	super.handle(httpVerb);
167
    	
168
        try {
169
        	
170
        	// only service requests if we have D1 configured
171
        	if (!isD1Enabled()) {
172
        		ServiceFailure se = new ServiceFailure("0000", "DataONE services are not enabled on this node");
173
                serializeException(se, response.getOutputStream());
174
                return;
175
        	}
176
        	
177
        	// get the resource
178
            String resource = request.getPathInfo();
179
            resource = resource.substring(resource.indexOf("/") + 1);
180
            
181
            // default to node info
182
            if (resource.equals("")) {
183
                resource = RESOURCE_NODE;
184
            }
185
            
186
            // get the rest of the path info
187
            String extra = null;
188
                        
189
            logMetacat.debug("handling verb " + httpVerb + " request with resource '" + resource + "'");
190
            logMetacat.debug("resource: '" + resource + "'");
191
            boolean status = false;
192
            
193
            if (resource != null) {
194

    
195
                if (resource.startsWith(RESOURCE_NODE)) {
196
                    // node response
197
                    node();
198
                    status = true;
199
                } else if (resource.startsWith(RESOURCE_IS_AUTHORIZED)) {
200
                    if (httpVerb == GET) {
201
                    	// after the command
202
                        extra = parseTrailing(resource, RESOURCE_IS_AUTHORIZED);
203
	                	// check the access rules
204
	                    isAuthorized(extra);
205
	                    status = true;
206
	                    logMetacat.debug("done getting access");
207
                    }
208
                } else if (resource.startsWith(RESOURCE_META)) {
209
                    logMetacat.debug("Using resource 'meta'");
210
                    // get
211
                    if (httpVerb == GET) {
212
                    	// after the command
213
                        extra = parseTrailing(resource, RESOURCE_META);
214
                        getSystemMetadataObject(extra);
215
                        status = true;
216
                    }
217
                    
218
                } else if (resource.startsWith(RESOURCE_OBJECTS)) {
219
                    logMetacat.debug("Using resource 'object'");
220
                    // after the command
221
                    extra = parseTrailing(resource, RESOURCE_OBJECTS);
222
                    logMetacat.debug("objectId: " + extra);
223
                    logMetacat.debug("verb:" + httpVerb);
224

    
225
                    if (httpVerb == GET) {
226
                        getObject(extra);
227
                        status = true;
228
                    } else if (httpVerb == POST) {
229
                    	// part of the params, not the URL
230
                        putObject(null, FUNCTION_NAME_INSERT);
231
                        status = true;
232
                    } else if (httpVerb == PUT) {
233
                        putObject(extra, FUNCTION_NAME_UPDATE);
234
                        status = true;
235
                    } else if (httpVerb == DELETE) {
236
                        deleteObject(extra);
237
                        status = true;
238
                    } else if (httpVerb == HEAD) {
239
                        describeObject(extra);
240
                        status = true;
241
                    }
242
                  
243
                } else if (resource.startsWith(RESOURCE_LOG)) {
244
                    logMetacat.debug("Using resource 'log'");
245
                    // handle log events
246
                    if (httpVerb == GET) {
247
                        getLog();
248
                        status = true;
249
                    }
250
                } else if (resource.startsWith(Constants.RESOURCE_CHECKSUM)) {
251
                    logMetacat.debug("Using resource 'checksum'");
252
                    // handle checksum requests
253
                    if (httpVerb == GET) {
254
                    	// after the command
255
                        extra = parseTrailing(resource, Constants.RESOURCE_CHECKSUM);
256
                        checksum(extra);
257
                        status = true;
258
                    }
259
                } else if (resource.startsWith(RESOURCE_MONITOR)) {
260
                    // there are various parts to monitoring
261
                    if (httpVerb == GET) {
262
                    	// after the command
263
                        extra = parseTrailing(resource, RESOURCE_MONITOR);
264
                        
265
                        // ping
266
                        if (extra.toLowerCase().equals("ping")) {
267
                            logMetacat.debug("processing ping request");
268
                            Date result = MNodeService.getInstance(request).ping();
269
                            // TODO: send to output	
270
                            status = true;
271
                            
272
                        } else {
273
	                    	// health monitoring calls
274
	                        status = monitor(extra);
275
                        }
276
                        
277
                    }
278
                } else if (resource.startsWith(RESOURCE_REPLICATE)) {
279
                	if (httpVerb == POST) {
280
	                    logMetacat.debug("processing replicate request");
281
	                    replicate();
282
	                    status = true;
283
                	}
284
                } else if (resource.startsWith(RESOURCE_ERROR)) {
285
	                // sync error
286
	                if (httpVerb == POST) {
287
	                    syncError();
288
	                    status = true;
289
	                }
290
                } else if (resource.startsWith(RESOURCE_META_CHANGED)) {
291
                    // system metadata changed
292
                    if (httpVerb == POST) {
293
                        systemMetadataChanged();
294
                        status = true;
295
                    }
296
                } else if (resource.startsWith(RESOURCE_REPLICAS)) {
297
                    // get replica
298
                    if (httpVerb == GET) {
299
                        extra = parseTrailing(resource, RESOURCE_REPLICAS);
300
                        getReplica(extra);
301
                        status = true;
302
                    }
303
                }
304
                
305
                if (!status) {
306
                	throw new ServiceFailure("0000", "Unknown error, status = " + status);
307
                }
308
            } else {
309
            	throw new InvalidRequest("0000", "No resource matched for " + resource);
310
            }
311
        } catch (BaseException be) {
312
        	// report Exceptions as clearly as possible
313
        	OutputStream out = null;
314
			try {
315
				out = response.getOutputStream();
316
			} catch (IOException e) {
317
				logMetacat.error("Could not get output stream from response", e);
318
			}
319
            serializeException(be, out);
320
        } catch (Exception e) {
321
            // report Exceptions as clearly and generically as possible
322
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
323
        	OutputStream out = null;
324
			try {
325
				out = response.getOutputStream();
326
			} catch (IOException ioe) {
327
				logMetacat.error("Could not get output stream from response", ioe);
328
			}
329
			ServiceFailure se = new ServiceFailure("0000", e.getMessage());
330
            serializeException(se, out);
331
        }
332
    }
333
    
334

    
335
    /**
336
     * Handles notification of system metadata changes for the given identifier
337
     * 
338
     * @param id  the identifier for the object
339
     * @throws InvalidToken 
340
     * @throws InvalidRequest 
341
     * @throws NotAuthorized 
342
     * @throws ServiceFailure 
343
     * @throws NotImplemented 
344
     */
345
    private void systemMetadataChanged() 
346
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
347
        InvalidToken {
348

    
349
        long serialVersion = 0L;
350
        String serialVersionStr = null;
351
        Date dateSysMetaLastModified = null;
352
        String dateSysMetaLastModifiedStr = null;
353
        Identifier pid = null;
354
        
355
        // mkae sure we have the multipart params
356
        try {
357
			initMultipartParams();
358
		} catch (Exception e1) {
359
			throw new ServiceFailure("1333", "Could not collect the multipart params for the request");
360
		}
361
        
362
        // get the pid
363
        try {
364
        	String id = multipartparams.get("pid").get(0);
365
        	pid = new Identifier();
366
            pid.setValue(id);            
367
        } catch (NullPointerException e) {
368
            String msg = "The 'pid' must be provided as a parameter and was not.";
369
            logMetacat.error(msg);
370
            throw new InvalidRequest("1334", msg);
371
        }      
372
        
373
        // get the serialVersion
374
        try {
375
            serialVersionStr = multipartparams.get("serialVersion").get(0);
376
            serialVersion = new Long(serialVersionStr).longValue();
377
            
378
        } catch (NullPointerException e) {
379
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
380
            logMetacat.error(msg);
381
            throw new InvalidRequest("1334", msg);
382
            
383
        }       
384
        
385
        // get the dateSysMetaLastModified
386
        try {
387
            dateSysMetaLastModifiedStr = multipartparams.get("dateSysMetaLastModified").get(0);
388
            dateSysMetaLastModified = DateTimeMarshaller.deserializeDateToUTC(dateSysMetaLastModifiedStr);
389
            
390
        } catch (NullPointerException e) {
391
            String msg = 
392
                "The 'dateSysMetaLastModified' must be provided as a " + 
393
                "parameter and was not, or was an invalid representation of the timestamp.";
394
            logMetacat.error(msg);
395
            throw new InvalidRequest("1334", msg);
396
            
397
        }       
398
        
399
        // call the service
400
        MNodeService.getInstance(request).systemMetadataChanged(session, pid, serialVersion, dateSysMetaLastModified);
401
        response.setStatus(200);
402
    }
403

    
404
    /**
405
     * Checks the access policy
406
     * @param id
407
     * @return
408
     * @throws ServiceFailure
409
     * @throws InvalidToken
410
     * @throws NotFound
411
     * @throws NotAuthorized
412
     * @throws NotImplemented
413
     * @throws InvalidRequest
414
     */
415
    private boolean isAuthorized(String id) throws ServiceFailure, InvalidToken, NotFound, NotAuthorized, NotImplemented, InvalidRequest {
416
		Identifier pid = new Identifier();
417
		pid.setValue(id);
418
		Permission permission = null;
419
		try {
420
			String perm = params.get("action")[0];
421
			permission = Permission.convert(perm);
422
		} catch (Exception e) {
423
			logMetacat.warn("No permission specified");
424
		}
425
		boolean result = MNodeService.getInstance(request).isAuthorized(session, pid, permission);
426
		response.setStatus(200);
427
		response.setContentType("text/xml");
428
		return result;
429
    }
430
    
431
    /**
432
     * Processes failed synchronization message
433
     * @throws NotImplemented
434
     * @throws ServiceFailure
435
     * @throws NotAuthorized
436
     * @throws InvalidRequest
437
     * @throws JiBXException
438
     * @throws IllegalAccessException 
439
     * @throws InstantiationException 
440
     * @throws IOException 
441
     */
442
    private void syncError() throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, JiBXException, IOException, InstantiationException, IllegalAccessException {
443
    	SynchronizationFailed syncFailed = null;
444
		try {
445
			syncFailed = collectSynchronizationFailed();
446
		} catch (ParserConfigurationException e) {
447
			throw new ServiceFailure("2161", e.getMessage());
448
		} catch (SAXException e) {
449
			throw new ServiceFailure("2161", e.getMessage());
450
		}
451
		
452
		MNodeService.getInstance(request).synchronizationFailed(session, syncFailed);
453
    }
454
    
455

    
456
    /**
457
     * Handles the monitoring resources
458
     * @return
459
     * @throws NotFound
460
     * @throws ParseException
461
     * @throws NotImplemented
462
     * @throws ServiceFailure
463
     * @throws NotAuthorized
464
     * @throws InvalidRequest
465
     * @throws InsufficientResources
466
     * @throws UnsupportedType
467
     * @throws IOException
468
     * @throws JiBXException
469
     */
470
    private boolean monitor(String pathInfo) 
471
      throws NotFound, ParseException, NotImplemented, ServiceFailure, 
472
      NotAuthorized, InvalidRequest, InsufficientResources, UnsupportedType, 
473
      IOException, JiBXException {
474
    	logMetacat.debug("processing monitor request");
475
        
476
        logMetacat.debug("verb is GET");
477
        logMetacat.debug("pathInfo is " + pathInfo);
478
        
479
        if (pathInfo.toLowerCase().equals("status")) {
480
            logMetacat.debug("processing status request");
481
            // TODO: implement in MNCore
482
            //MNodeService.getInstance().getStatus();
483
            return false;
484
            
485
        } else if (pathInfo.toLowerCase().equals("object")) {
486
            logMetacat.debug("processing object request");
487
            Identifier pid = null;
488
            ObjectFormat format = null;
489
            if (params.containsKey("format")) {
490
                String f = params.get("format")[0];
491
                format = ObjectFormatCache.getInstance().getFormat(f);
492
            }
493
            if (params.containsKey("pid")) {
494
                String id = params.get("pid")[0];
495
                pid = new Identifier();
496
                pid.setValue(id);
497
            }
498
            
499
            // TODO: implement in MNCore
500
            //ObjectStatistics objectStats = MNodeService.getInstance().getObjectStatistics(format, pid);
501
            return false;
502
            
503
        } else if (pathInfo.toLowerCase().equals("event")) {
504
            logMetacat.debug("processing event request");
505
            ObjectFormatIdentifier fmtid = null;
506
            String fromDateStr = null;
507
            Date fromDate = null;
508
            String toDateStr = null;
509
            Date toDate = null;
510
            String requestor = null;
511
            Subject subject = null;
512
            String eventName = null;
513
            Event event = null;
514

    
515
            if (params.containsKey("formatId")) {
516
                String f = params.get("formatId")[0];
517
                fmtid = ObjectFormatCache.getInstance().getFormat(f).getFormatId();
518
            }
519
            
520
            if (params.containsKey("fromDate")) {
521
                fromDateStr = params.get("fromDate")[0];
522
                fromDate = getDateAsUTC(fromDateStr);
523
            }
524
            
525
            if (params.containsKey("toDate")) {
526
              toDateStr = params.get("toDate")[0];
527
              toDate = getDateAsUTC(toDateStr);
528
            }
529
            
530
            if (params.containsKey("requestor")) {
531
            	requestor = params.get("requestor")[0];
532
            	subject = new Subject();
533
            	subject.setValue(requestor);
534
            }
535
            
536
            if (params.containsKey("event")) {
537
            	eventName = params.get("event")[0];
538
                event = Event.convert(eventName);
539
            }
540
            
541
            MonitorList monitorList = MNodeService.getInstance(request).getOperationStatistics(session, fromDate, toDate, subject, event, fmtid);
542
            
543
            OutputStream out = response.getOutputStream();
544
            response.setStatus(200);
545
            response.setContentType("text/xml");
546
            
547
            TypeMarshaller.marshalTypeToOutputStream(monitorList, out);
548
            
549
            return true;
550
            
551
        }
552
        
553
        return false;
554
    }
555
    
556
    /*
557
     * Parse an ISO8601 date string, returning a Date in UTC time if the string
558
     * ends in 'Z'.
559
     * 
560
     * @param fromDateStr -  the date string to be parsed
561
     * @return date -  the date object represented by the string
562
     */
563
    private Date getDateAsUTC(String fromDateStr)
564
      throws ParseException {
565

    
566
    	Date date = null;
567
    	
568
    	try {
569
    		// try the expected date format
570
        // a date format for date string arguments
571
        DateFormat dateFormat = 
572
        	new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
573

    
574
	      date = dateFormat.parse(fromDateStr);
575
      
576
    	} catch (ParseException e) {
577
    		// try the date with the UTC indicator
578
        DateFormat utcDateFormat = 
579
        	new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
580
        utcDateFormat.setTimeZone(TimeZone.getTimeZone("GMT-0"));
581
        date = utcDateFormat.parse(fromDateStr);
582
        
583
      }
584
    	
585
    	return date;
586
    }
587

    
588
		/**
589
     * Calculate the checksum 
590
     * @throws NotImplemented
591
     * @throws JiBXException
592
     * @throws IOException
593
     * @throws InvalidToken
594
     * @throws ServiceFailure
595
     * @throws NotAuthorized
596
     * @throws NotFound
597
     * @throws InvalidRequest
598
     */
599
    private void checksum(String pid) throws NotImplemented, JiBXException, IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest {
600
    	String checksumAlgorithm = "MD5";
601
        
602
        Identifier pidid = new Identifier();
603
        pidid.setValue(pid);
604
        try {
605
            checksumAlgorithm = params.get("checksumAlgorithm")[0];
606
        } catch(Exception e) {
607
            //do nothing.  default to MD5
608
        	logMetacat.warn("No algorithm specified, using default: " + checksumAlgorithm);
609
        }
610
        logMetacat.debug("getting checksum for object " + pid + " with algorithm " + checksumAlgorithm);
611
        
612
        Checksum c = MNodeService.getInstance(request).getChecksum(session, pidid, checksumAlgorithm);
613
        logMetacat.debug("got checksum " + c.getValue());
614
        response.setStatus(200);
615
        logMetacat.debug("serializing response");
616
        TypeMarshaller.marshalTypeToOutputStream(c, response.getOutputStream());
617
        logMetacat.debug("done serializing response.");
618
        
619
    }
620
    
621
	/**
622
     * handle the replicate action for MN
623
	 * @throws JiBXException 
624
	 * @throws FileUploadException 
625
	 * @throws IOException 
626
	 * @throws InvalidRequest 
627
	 * @throws ServiceFailure 
628
	 * @throws UnsupportedType 
629
	 * @throws InsufficientResources 
630
	 * @throws NotAuthorized 
631
	 * @throws NotImplemented 
632
	 * @throws IllegalAccessException 
633
	 * @throws InstantiationException 
634
     */
635
    private void replicate() 
636
        throws ServiceFailure, InvalidRequest, IOException, FileUploadException, 
637
        JiBXException, NotImplemented, NotAuthorized, InsufficientResources, 
638
        UnsupportedType, InstantiationException, IllegalAccessException {
639

    
640
        logMetacat.debug("in POST replicate()");
641
        
642
        // somewhat unorthodox, but the call is asynchronous and we'd like to return this info sooner
643
        if (session == null) {
644
        	String msg = "No session was provided.";
645
            NotAuthorized failure = new NotAuthorized("2152", msg);
646
        	throw failure;
647
        }
648
        
649
        //parse the systemMetadata
650
        final SystemMetadata sysmeta = collectSystemMetadata();
651
        
652
        String sn = multipartparams.get("sourceNode").get(0);
653
        logMetacat.debug("sourceNode: " + sn);
654
        final NodeReference sourceNode = new NodeReference();
655
        sourceNode.setValue(sn);
656
        
657
        // run it in a thread to avoid connection timeout
658
        Runnable runner = new Runnable() {
659
			@Override
660
			public void run() {
661
				try {
662
			        MNodeService.getInstance(request).replicate(session, sysmeta, sourceNode);
663
				} catch (Exception e) {
664
					throw new RuntimeException(e.getMessage(), e);
665
				}
666
			}
667
    	};
668
    	ExecutorService executor = Executors.newSingleThreadExecutor();
669
    	executor.execute(runner);
670
    	executor.shutdown();
671
        
672
    	// thread was started, so we return success
673
        response.setStatus(200);
674
        
675
    }
676

    
677
    /**
678
     * Handle the getReplica action for the MN
679
     * @param id  the identifier for the object
680
     * @throws NotFound 
681
     * @throws ServiceFailure 
682
     * @throws NotImplemented 
683
     * @throws NotAuthorized 
684
     * @throws InvalidToken 
685
     * @throws InvalidRequest 
686
     */
687
    private void getReplica(String id) 
688
        throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented, 
689
        ServiceFailure, NotFound {
690
        
691
        Identifier pid = new Identifier();
692
        pid.setValue(id);
693
        OutputStream out = null;
694
        InputStream dataBytes = null;
695
                
696
        try {
697
            // call the service
698
            dataBytes = MNodeService.getInstance(request).getReplica(session, pid);
699

    
700
            response.setContentType("application/octet-stream");
701
            response.setStatus(200);
702
            out = response.getOutputStream();
703
            // write the object to the output stream
704
            IOUtils.copyLarge(dataBytes, out);
705
            
706
        } catch (IOException e) {
707
            String msg = "There was an error writing the output: " + e.getMessage();
708
            logMetacat.error(msg);
709
            throw new ServiceFailure("2181", msg);
710
        
711
        }
712

    
713
    }
714

    
715
    /**
716
     * Get the Node information
717
     * 
718
     * @throws JiBXException
719
     * @throws IOException
720
     * @throws InvalidRequest 
721
     * @throws ServiceFailure 
722
     * @throws NotAuthorized 
723
     * @throws NotImplemented 
724
     */
725
    private void node() 
726
        throws JiBXException, IOException, NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest {
727
        
728
        Node n = MNodeService.getInstance(request).getCapabilities();
729
        
730
        response.setContentType("text/xml");
731
        response.setStatus(200);
732
        TypeMarshaller.marshalTypeToOutputStream(n, response.getOutputStream());
733
        
734
    }
735
    
736
    /**
737
     * MN_crud.describe()
738
     * http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.describe
739
     * @param pid
740
     * @throws InvalidRequest 
741
     * @throws NotImplemented 
742
     * @throws NotFound 
743
     * @throws NotAuthorized 
744
     * @throws ServiceFailure 
745
     * @throws InvalidToken 
746
     */
747
    private void describeObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest
748
    {
749
        
750
        response.setContentType("text/xml");
751

    
752
        Identifier id = new Identifier();
753
        id.setValue(pid);
754

    
755
        DescribeResponse dr = null;
756
        try {
757
        	dr = MNodeService.getInstance(request).describe(session, id);
758
        } catch (BaseException e) {
759
        	response.setStatus(e.getCode());
760
        	response.addHeader("DataONE-Exception-Name", e.getClass().getName());
761
            response.addHeader("DataONE-Exception-DetailCode", e.getDetail_code());
762
            response.addHeader("DataONE-Exception-Description", e.getDescription());
763
            response.addHeader("DataONE-Exception-PID", id.getValue());
764
            return;
765
		}
766
        
767
        response.setStatus(200);
768
        
769
        //response.addHeader("pid", pid);
770
        response.addHeader("DataONE-Checksum", dr.getDataONE_Checksum().getAlgorithm() + "," + dr.getDataONE_Checksum().getValue());
771
        response.addHeader("Content-Length", dr.getContent_Length() + "");
772
        response.addHeader("Last-Modified", DateTimeMarshaller.serializeDateToUTC(dr.getLast_Modified()));
773
        response.addHeader("DataONE-ObjectFormat", dr.getDataONE_ObjectFormatIdentifier().getValue());
774
        response.addHeader("DataONE-SerialVersion", dr.getSerialVersion().toString());
775

    
776
        
777
    }
778
    
779
    /**
780
     * get the logs based on passed params.  Available 
781
     * See http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.getLogRecords
782
     * for more info
783
     * @throws NotImplemented 
784
     * @throws InvalidRequest 
785
     * @throws NotAuthorized 
786
     * @throws ServiceFailure 
787
     * @throws InvalidToken 
788
     * @throws IOException 
789
     * @throws JiBXException 
790
     */
791
    private void getLog() throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented, IOException, JiBXException
792
    {
793
            
794
        Date fromDate = null;
795
        Date toDate = null;
796
        Event event = null;
797
        Integer start = null;
798
        Integer count = null;
799
        
800
        try {
801
        	String fromDateS = params.get("fromDate")[0];
802
            logMetacat.debug("param fromDateS: " + fromDateS);
803
            fromDate = DateTimeMarshaller.deserializeDateToUTC(fromDateS);
804
        } catch (Exception e) {
805
        	logMetacat.warn("Could not parse fromDate: " + e.getMessage());
806
        }
807
        try {
808
        	String toDateS = params.get("toDate")[0];
809
            logMetacat.debug("param toDateS: " + toDateS);
810
            toDate = DateTimeMarshaller.deserializeDateToUTC(toDateS);
811
        } catch (Exception e) {
812
        	logMetacat.warn("Could not parse toDate: " + e.getMessage());
813
		}
814
        try {
815
        	String eventS = params.get("event")[0];
816
            event = Event.convert(eventS);
817
        } catch (Exception e) {
818
        	logMetacat.warn("Could not parse event: " + e.getMessage());
819
		}
820
        logMetacat.debug("fromDate: " + fromDate + " toDate: " + toDate);
821
        
822
        try {
823
        	start =  Integer.parseInt(params.get("start")[0]);
824
        } catch (Exception e) {
825
			logMetacat.warn("Could not parse start: " + e.getMessage());
826
		}
827
        try {
828
        	count =  Integer.parseInt(params.get("count")[0]);
829
        } catch (Exception e) {
830
			logMetacat.warn("Could not parse count: " + e.getMessage());
831
		}
832
        
833
        logMetacat.debug("calling getLogRecords");
834
        Log log = MNodeService.getInstance(request).getLogRecords(session, fromDate, toDate, event, start, count);
835
        
836
        OutputStream out = response.getOutputStream();
837
        response.setStatus(200);
838
        response.setContentType("text/xml");
839
        
840
        TypeMarshaller.marshalTypeToOutputStream(log, out);
841
        
842
    }
843
    
844
    /**
845
     * Implements REST version of DataONE CRUD API --> get
846
     * @param pid ID of data object to be read
847
     * @throws NotImplemented 
848
     * @throws InvalidRequest 
849
     * @throws NotFound 
850
     * @throws NotAuthorized 
851
     * @throws ServiceFailure 
852
     * @throws InvalidToken 
853
     * @throws IOException 
854
     * @throws JiBXException 
855
     */
856
    protected void getObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
857
        OutputStream out = null;
858
        
859
        if (pid != null) { //get a specific document                
860
            Identifier id = new Identifier();
861
            id.setValue(pid);
862
                
863
            SystemMetadata sm = MNodeService.getInstance(request).getSystemMetadata(session, id);
864
            
865
            //set the content type
866
            if (sm.getFormatId().getValue().trim().equals(
867
            		ObjectFormatCache.getInstance().getFormat("text/csv").getFormatId().getValue()))
868
            {
869
                response.setContentType("text/csv");
870
                response.setHeader("Content-Disposition", "inline; filename=" + id.getValue() + ".csv");
871
            }
872
            else if (sm.getFormatId().getValue().trim().equals(
873
            		ObjectFormatCache.getInstance().getFormat("text/plain").getFormatId().getValue()))
874
            {
875
                response.setContentType("text/plain");
876
                response.setHeader("Content-Disposition", "inline; filename=" + id.getValue() + ".txt");
877
            } 
878
            else if (sm.getFormatId().getValue().trim().equals(
879
            		ObjectFormatCache.getInstance().getFormat("application/octet-stream").getFormatId().getValue()))
880
            {
881
                response.setContentType("application/octet-stream");
882
            }
883
            else
884
            {
885
                response.setContentType("text/xml");
886
                response.setHeader("Content-Disposition", "inline; filename=" + id.getValue() + ".xml");
887
            }
888
            
889
            InputStream data = MNodeService.getInstance(request).get(session, id);
890

    
891
            out = response.getOutputStream();  
892
            IOUtils.copyLarge(data, out);
893
            
894
        }
895
        else
896
        { //call listObjects with specified params
897
            Date startTime = null;
898
            Date endTime = null;
899
            ObjectFormat objectFormat = null;
900
            boolean replicaStatus = false;
901
            int start = 0;
902
            //TODO: make the max count into a const
903
            int count = 1000;
904
            Enumeration paramlist = request.getParameterNames();
905
            while (paramlist.hasMoreElements()) 
906
            { //parse the params and make the crud call
907
                String name = (String) paramlist.nextElement();
908
                String[] value = (String[])request.getParameterValues(name);
909

    
910
                if (name.equals("fromDate") && value != null)
911
                {
912
                    try
913
                    {
914
                      //startTime = dateFormat.parse(value[0]);
915
                    	startTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
916
                        //startTime = parseDateAndConvertToGMT(value[0]);
917
                    }
918
                    catch(Exception e)
919
                    {  //if we can't parse it, just don't use the fromDate param
920
                        logMetacat.warn("Could not parse fromDate: " + value[0]);
921
                        startTime = null;
922
                    }
923
                }
924
                else if(name.equals("toDate") && value != null)
925
                {
926
                    try
927
                    {
928
                    	endTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
929
                    }
930
                    catch(Exception e)
931
                    {  //if we can't parse it, just don't use the toDate param
932
                        logMetacat.warn("Could not parse toDate: " + value[0]);
933
                        endTime = null;
934
                    }
935
                }
936
                else if(name.equals("objectFormat") && value != null) 
937
                {
938
                    objectFormat = ObjectFormatCache.getInstance().getFormat(value[0]);
939
                }
940
                else if(name.equals("replicaStatus") && value != null)
941
                {
942
                    if(value != null && 
943
                       value.length > 0 && 
944
                       (value[0].equals("true") || value[0].equals("TRUE") || value[0].equals("YES")))
945
                    {
946
                        replicaStatus = true;
947
                    }
948
                }
949
                else if(name.equals("start") && value != null)
950
                {
951
                    start = new Integer(value[0]).intValue();
952
                }
953
                else if(name.equals("count") && value != null)
954
                {
955
                    count = new Integer(value[0]).intValue();
956
                }
957
            }
958
            //make the crud call
959
            logMetacat.debug("session: " + session + " startTime: " + startTime +
960
                    " endtime: " + endTime + " objectFormat: " + 
961
                    objectFormat + " replicaStatus: " + replicaStatus + 
962
                    " start: " + start + " count: " + count);
963
           
964
            ObjectFormatIdentifier fmtid = null;
965
           
966
            if ( objectFormat != null ) {
967
          	 fmtid = objectFormat.getFormatId();
968
          	 
969
            }
970
            ObjectList ol = 
971
           	 MNodeService.getInstance(request).listObjects(session, startTime, endTime, 
972
               fmtid, replicaStatus, start, count);
973
           
974
            out = response.getOutputStream();  
975
            response.setStatus(200);
976
            response.setContentType("text/xml");
977
            // Serialize and write it to the output stream
978
            TypeMarshaller.marshalTypeToOutputStream(ol, out);
979
            
980
        }
981
        
982
    }
983
    
984

    
985
    /**
986
     * Retrieve System Metadata
987
     * @param pid
988
     * @throws InvalidToken
989
     * @throws ServiceFailure
990
     * @throws NotAuthorized
991
     * @throws NotFound
992
     * @throws InvalidRequest
993
     * @throws NotImplemented
994
     * @throws IOException
995
     * @throws JiBXException
996
     */
997
    protected void getSystemMetadataObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
998

    
999
        Identifier id = new Identifier();
1000
        id.setValue(pid);
1001
        SystemMetadata sysmeta = MNodeService.getInstance(request).getSystemMetadata(session, id);
1002
        
1003
        response.setContentType("text/xml");
1004
        response.setStatus(200);
1005
        OutputStream out = response.getOutputStream();
1006
        
1007
        // Serialize and write it to the output stream
1008
        TypeMarshaller.marshalTypeToOutputStream(sysmeta, out);
1009
   }
1010
    
1011
    
1012
    /**
1013
     * Inserts or updates the object
1014
     * 
1015
     * @param pid - ID of data object to be inserted or updated.  If action is update, the pid
1016
     *               is the existing pid.  If insert, the pid is the new one
1017
     * @throws InvalidRequest 
1018
     * @throws ServiceFailure 
1019
     * @throws JiBXException 
1020
     * @throws NotImplemented 
1021
     * @throws InvalidSystemMetadata 
1022
     * @throws InsufficientResources 
1023
     * @throws UnsupportedType 
1024
     * @throws IdentifierNotUnique 
1025
     * @throws NotAuthorized 
1026
     * @throws InvalidToken 
1027
     * @throws NotFound 
1028
     * @throws IOException 
1029
     * @throws IllegalAccessException 
1030
     * @throws InstantiationException 
1031
     */
1032
    protected void putObject(String trailingPid, String action) throws ServiceFailure, InvalidRequest, JiBXException, InvalidToken, NotAuthorized, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, NotFound, IOException, InstantiationException, IllegalAccessException {
1033
       
1034
    	// Read the incoming data from its Mime Multipart encoding
1035
    	Map<String, File> files = collectMultipartFiles();
1036
               
1037
    	Identifier pid = new Identifier();
1038
        if (trailingPid == null) {
1039
	        // get the pid string from the body and set the value
1040
	        String pidString = multipartparams.get("pid").get(0);
1041
	        if (pidString != null) {
1042
            pid.setValue(pidString);
1043
            
1044
          } else {
1045
              throw new InvalidRequest("1102", "The pid param must be included and contain the identifier.");
1046
              
1047
          }
1048
        } else {
1049
        	// use the pid included in the URL
1050
        	pid.setValue(trailingPid);
1051
        }
1052
        logMetacat.debug("putObject with pid " + pid.getValue());
1053
        logMetacat.debug("Entering putObject: " + pid.getValue() + "/" + action);
1054

    
1055
        InputStream object = null;
1056
        InputStream sysmeta = null;
1057
        File smFile = files.get("sysmeta");
1058
        sysmeta = new FileInputStream(smFile);
1059
        File objFile = files.get("object");
1060
        object = new FileInputStream(objFile);
1061
        
1062
        // ensure we have the object bytes
1063
        if  ( objFile == null ) {
1064
            throw new InvalidRequest("1102", "The object param must contain the object bytes.");
1065
            
1066
        }
1067
        
1068
        // ensure we have the system metadata
1069
        if  ( smFile == null ) {
1070
            throw new InvalidRequest("1102", "The sysmeta param must contain the system metadata document.");
1071
            
1072
        }
1073
        
1074
        response.setStatus(200);
1075
        response.setContentType("text/xml");
1076
        OutputStream out = response.getOutputStream();
1077
        
1078
        if (action.equals(FUNCTION_NAME_INSERT)) { 
1079
            // handle inserts
1080
            logMetacat.debug("Commence creation...");
1081
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1082

    
1083
            logMetacat.debug("creating object with pid " + pid.getValue());
1084
            Identifier rId = MNodeService.getInstance(request).create(session, pid, object, smd);
1085
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1086
            
1087
        } else if (action.equals(FUNCTION_NAME_UPDATE)) {
1088
        	// handle updates
1089
        	
1090
            // construct pids
1091
            Identifier newPid = null;
1092
            try {
1093
            	String newPidString = multipartparams.get("newPid").get(0);
1094
            	newPid = new Identifier();
1095
            	newPid.setValue(newPidString);
1096
            } catch (Exception e) {
1097
				logMetacat.error("Could not get newPid from request");
1098
			}
1099
            logMetacat.debug("Commence update...");
1100
            
1101
            // get the systemmetadata object
1102
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1103

    
1104
            Identifier rId = MNodeService.getInstance(request).update(session, pid, object, newPid, smd);
1105
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1106
        } else {
1107
            throw new InvalidRequest("1000", "Operation must be create or update.");
1108
        }
1109
   
1110
    }
1111

    
1112
    /**
1113
     * Handle delete 
1114
     * @param pid ID of data object to be deleted
1115
     * @throws IOException
1116
     * @throws InvalidRequest 
1117
     * @throws NotImplemented 
1118
     * @throws NotFound 
1119
     * @throws NotAuthorized 
1120
     * @throws ServiceFailure 
1121
     * @throws InvalidToken 
1122
     * @throws JiBXException 
1123
     */
1124
    private void deleteObject(String pid) throws IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest, JiBXException 
1125
    {
1126

    
1127
        OutputStream out = response.getOutputStream();
1128
        response.setStatus(200);
1129
        response.setContentType("text/xml");
1130

    
1131
        Identifier id = new Identifier();
1132
        id.setValue(pid);
1133

    
1134
        logMetacat.debug("Calling delete");
1135
        MNodeService.getInstance(request).delete(session, id);
1136
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1137
        
1138
    }    
1139

    
1140
	protected SynchronizationFailed collectSynchronizationFailed() throws IOException, ServiceFailure, InvalidRequest, JiBXException, InstantiationException, IllegalAccessException, ParserConfigurationException, SAXException  {
1141
		
1142
		// Read the incoming data from its Mime Multipart encoding
1143
		logMetacat.debug("Disassembling MIME multipart form");
1144
		InputStream sf = null;
1145
	
1146
		// handle MMP inputs
1147
		File tmpDir = getTempDirectory();
1148
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
1149
		MultipartRequestResolver mrr = 
1150
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
1151
		MultipartRequest mr = null;
1152
		try {
1153
			mr = mrr.resolveMultipart(request);
1154
		} catch (Exception e) {
1155
			throw new ServiceFailure("2161", 
1156
					"Could not resolve multipart: " + e.getMessage());
1157
		}
1158
		logMetacat.debug("resolved multipart request");
1159
		Map<String, File> files = mr.getMultipartFiles();
1160
		if (files == null || files.keySet() == null) {
1161
			throw new InvalidRequest("2163",
1162
					"must have multipart file with name 'message'");
1163
		}
1164
		logMetacat.debug("got multipart files");
1165
	
1166
		multipartparams = mr.getMultipartParameters();
1167
	
1168
		File sfFile = files.get("message");
1169
		if (sfFile == null) {
1170
			throw new InvalidRequest("2163",
1171
					"Missing the required file-part 'message' from the multipart request.");
1172
		}
1173
		logMetacat.debug("sfFile: " + sfFile.getAbsolutePath());
1174
		sf = new FileInputStream(sfFile);
1175
	
1176
		SynchronizationFailed syncFailed = (SynchronizationFailed) ExceptionHandler.deserializeXml(sf, "Error deserializing exception");
1177
		return syncFailed;
1178
	}
1179

    
1180
}
(8-8/9)