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-28 11:08:59 -0700 (Wed, 28 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
					logMetacat.error("Error running replication: " + e.getMessage(), e);
665
					throw new RuntimeException(e.getMessage(), e);
666
				}
667
			}
668
    	};
669
    	ExecutorService executor = Executors.newSingleThreadExecutor();
670
    	executor.execute(runner);
671
    	executor.shutdown();
672
        
673
    	// thread was started, so we return success
674
        response.setStatus(200);
675
        
676
    }
677

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

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

    
714
    }
715

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

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

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

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

    
899
            out = response.getOutputStream();  
900
            IOUtils.copyLarge(data, out);
901
            
902
        }
903
        else
904
        { //call listObjects with specified params
905
            Date startTime = null;
906
            Date endTime = null;
907
            ObjectFormatIdentifier formatId = null;
908
            boolean replicaStatus = false;
909
            int start = 0;
910
            //TODO: make the max count into a const
911
            int count = 1000;
912
            Enumeration paramlist = request.getParameterNames();
913
            while (paramlist.hasMoreElements()) 
914
            { //parse the params and make the crud call
915
                String name = (String) paramlist.nextElement();
916
                String[] value = (String[])request.getParameterValues(name);
917

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

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

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

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

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

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

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

    
1130
        OutputStream out = response.getOutputStream();
1131
        response.setStatus(200);
1132
        response.setContentType("text/xml");
1133

    
1134
        Identifier id = new Identifier();
1135
        id.setValue(pid);
1136

    
1137
        logMetacat.debug("Calling delete");
1138
        MNodeService.getInstance(request).delete(session, id);
1139
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1140
        
1141
    }    
1142

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

    
1183
}
(8-8/9)