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-02-07 16:53:22 -0800 (Tue, 07 Feb 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.DateTimeMarshaller;
78
import org.dataone.service.util.ExceptionHandler;
79
import org.dataone.service.util.TypeMarshaller;
80
import org.jibx.runtime.JiBXException;
81
import org.xml.sax.SAXException;
82

    
83
import edu.ucsb.nceas.metacat.dataone.MNodeService;
84
import edu.ucsb.nceas.metacat.properties.PropertyService;
85
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
86

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

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

    
135
    /**
136
     * Initializes new instance by setting servlet context,request and response
137
     * */
138
    public MNResourceHandler(ServletContext servletContext,
139
            HttpServletRequest request, HttpServletResponse response) {
140
    	super(servletContext, request, response);
141
        logMetacat = Logger.getLogger(MNResourceHandler.class);
142
    }
143
    
144
    @Override
145
    protected boolean isD1Enabled() {
146
    	
147
    	boolean enabled = false;
148
    	try {
149
			enabled = Boolean.parseBoolean(PropertyService.getProperty("dataone.mn.services.enabled"));
150
		} catch (PropertyNotFoundException e) {
151
			logMetacat.error("Could not check if DataONE is enabled: " + e.getMessage());
152
		}
153
    	
154
    	return enabled;	
155
    }
156

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

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

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

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

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

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

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

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

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

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

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

    
639
        logMetacat.debug("in POST replicate()");
640
        
641
        //parse the systemMetadata
642
        final SystemMetadata sysmeta = collectSystemMetadata();
643
        
644
        String sn = multipartparams.get("sourceNode").get(0);
645
        logMetacat.debug("sourceNode: " + sn);
646
        final NodeReference sourceNode = new NodeReference();
647
        sourceNode.setValue(sn);
648
        
649
        // run it in a thread to avoid connection timeout
650
        Runnable runner = new Runnable() {
651
			@Override
652
			public void run() {
653
				try {
654
			        MNodeService.getInstance(request).replicate(session, sysmeta, sourceNode);
655
				} catch (Exception e) {
656
					throw new RuntimeException(e.getMessage(), e);
657
				}
658
			}
659
    	};
660
    	ExecutorService executor = Executors.newSingleThreadExecutor();
661
    	executor.execute(runner);
662
    	executor.shutdown();
663
        
664
    	// thread was started, so we return success
665
        response.setStatus(200);
666
        
667
    }
668

    
669
    /**
670
     * Handle the getReplica action for the MN
671
     * @param id  the identifier for the object
672
     * @throws NotFound 
673
     * @throws ServiceFailure 
674
     * @throws NotImplemented 
675
     * @throws NotAuthorized 
676
     * @throws InvalidToken 
677
     * @throws InvalidRequest 
678
     */
679
    private void getReplica(String id) 
680
        throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented, 
681
        ServiceFailure, NotFound {
682
        
683
        Identifier pid = new Identifier();
684
        pid.setValue(id);
685
        OutputStream out = null;
686
        InputStream dataBytes = null;
687
                
688
        try {
689
            // call the service
690
            dataBytes = MNodeService.getInstance(request).getReplica(session, pid);
691

    
692
            response.setContentType("application/octet-stream");
693
            response.setStatus(200);
694
            out = response.getOutputStream();
695
            // write the object to the output stream
696
            IOUtils.copyLarge(dataBytes, out);
697
            
698
        } catch (IOException e) {
699
            String msg = "There was an error writing the output: " + e.getMessage();
700
            logMetacat.error(msg);
701
            throw new ServiceFailure("2181", msg);
702
        
703
        }
704

    
705
    }
706

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

    
747
        DescribeResponse dr = MNodeService.getInstance(request).describe(session, id);
748
        //response.addHeader("pid", pid);
749
        response.addHeader("DataONE-Checksum", dr.getDataONE_Checksum().getAlgorithm() + "," + dr.getDataONE_Checksum().getValue());
750
        response.addHeader("Content-Length", dr.getContent_Length() + "");
751
        response.addHeader("Last-Modified", DateTimeMarshaller.serializeDateToUTC(dr.getLast_Modified()));
752
        response.addHeader("DataONE-ObjectFormat", dr.getDataONE_ObjectFormatIdentifier().getValue());
753
        response.addHeader("DataONE-SerialVersion", dr.getSerialVersion().toString());
754

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

    
870
            out = response.getOutputStream();  
871
            IOUtils.copyLarge(data, out);
872
            
873
        }
874
        else
875
        { //call listObjects with specified params
876
            Date startTime = null;
877
            Date endTime = null;
878
            ObjectFormat objectFormat = null;
879
            boolean replicaStatus = false;
880
            int start = 0;
881
            //TODO: make the max count into a const
882
            int count = 1000;
883
            Enumeration paramlist = request.getParameterNames();
884
            while (paramlist.hasMoreElements()) 
885
            { //parse the params and make the crud call
886
                String name = (String) paramlist.nextElement();
887
                String[] value = (String[])request.getParameterValues(name);
888

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

    
964
    /**
965
     * Retrieve System Metadata
966
     * @param pid
967
     * @throws InvalidToken
968
     * @throws ServiceFailure
969
     * @throws NotAuthorized
970
     * @throws NotFound
971
     * @throws InvalidRequest
972
     * @throws NotImplemented
973
     * @throws IOException
974
     * @throws JiBXException
975
     */
976
    protected void getSystemMetadataObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
977

    
978
        Identifier id = new Identifier();
979
        id.setValue(pid);
980
        SystemMetadata sysmeta = MNodeService.getInstance(request).getSystemMetadata(session, id);
981
        
982
        response.setContentType("text/xml");
983
        response.setStatus(200);
984
        OutputStream out = response.getOutputStream();
985
        
986
        // Serialize and write it to the output stream
987
        TypeMarshaller.marshalTypeToOutputStream(sysmeta, out);
988
   }
989
    
990
    
991
    /**
992
     * Inserts or updates the object
993
     * 
994
     * @param pid - ID of data object to be inserted or updated.  If action is update, the pid
995
     *               is the existing pid.  If insert, the pid is the new one
996
     * @throws InvalidRequest 
997
     * @throws ServiceFailure 
998
     * @throws JiBXException 
999
     * @throws NotImplemented 
1000
     * @throws InvalidSystemMetadata 
1001
     * @throws InsufficientResources 
1002
     * @throws UnsupportedType 
1003
     * @throws IdentifierNotUnique 
1004
     * @throws NotAuthorized 
1005
     * @throws InvalidToken 
1006
     * @throws NotFound 
1007
     * @throws IOException 
1008
     * @throws IllegalAccessException 
1009
     * @throws InstantiationException 
1010
     */
1011
    protected void putObject(String trailingPid, String action) throws ServiceFailure, InvalidRequest, JiBXException, InvalidToken, NotAuthorized, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, NotFound, IOException, InstantiationException, IllegalAccessException {
1012
       
1013
    	// Read the incoming data from its Mime Multipart encoding
1014
    	Map<String, File> files = collectMultipartFiles();
1015
               
1016
    	Identifier pid = new Identifier();
1017
        if (trailingPid == null) {
1018
	        // get the pid string from the body and set the value
1019
	        String pidString = multipartparams.get("pid").get(0);
1020
	        pid.setValue(pidString);
1021
        } else {
1022
        	// use the pid included in the URL
1023
        	pid.setValue(trailingPid);
1024
        }
1025
        logMetacat.debug("putObject with pid " + pid.getValue());
1026
        logMetacat.debug("Entering putObject: " + pid.getValue() + "/" + action);
1027

    
1028
        InputStream object = null;
1029
        InputStream sysmeta = null;
1030
        File smFile = files.get("sysmeta");
1031
        sysmeta = new FileInputStream(smFile);
1032
        File objFile = files.get("object");
1033
        object = new FileInputStream(objFile);
1034
        
1035
        // ensure we have the object bytes
1036
        if  ( objFile == null ) {
1037
            throw new InvalidRequest("1102", "The object param must contain the object bytes.");
1038
            
1039
        }
1040
        
1041
        // ensure we have the system metadata
1042
        if  ( smFile == null ) {
1043
            throw new InvalidRequest("1102", "The sysmeta param must contain the system metadata document.");
1044
            
1045
        }
1046
        
1047
        response.setStatus(200);
1048
        response.setContentType("text/xml");
1049
        OutputStream out = response.getOutputStream();
1050
        
1051
        if (action.equals(FUNCTION_NAME_INSERT)) { 
1052
            // handle inserts
1053
            logMetacat.debug("Commence creation...");
1054
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1055

    
1056
            logMetacat.debug("creating object with pid " + pid.getValue());
1057
            Identifier rId = MNodeService.getInstance(request).create(session, pid, object, smd);
1058
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1059
            
1060
        } else if (action.equals(FUNCTION_NAME_UPDATE)) {
1061
        	// handle updates
1062
        	
1063
            // construct pids
1064
            Identifier newPid = null;
1065
            try {
1066
            	String newPidString = multipartparams.get("newPid").get(0);
1067
            	newPid = new Identifier();
1068
            	newPid.setValue(newPidString);
1069
            } catch (Exception e) {
1070
				logMetacat.error("Could not get newPid from request");
1071
			}
1072
            logMetacat.debug("Commence update...");
1073
            
1074
            // get the systemmetadata object
1075
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1076

    
1077
            Identifier rId = MNodeService.getInstance(request).update(session, pid, object, newPid, smd);
1078
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1079
        } else {
1080
            throw new InvalidRequest("1000", "Operation must be create or update.");
1081
        }
1082
   
1083
    }
1084

    
1085
    /**
1086
     * Handle delete 
1087
     * @param pid ID of data object to be deleted
1088
     * @throws IOException
1089
     * @throws InvalidRequest 
1090
     * @throws NotImplemented 
1091
     * @throws NotFound 
1092
     * @throws NotAuthorized 
1093
     * @throws ServiceFailure 
1094
     * @throws InvalidToken 
1095
     * @throws JiBXException 
1096
     */
1097
    private void deleteObject(String pid) throws IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest, JiBXException 
1098
    {
1099

    
1100
        OutputStream out = response.getOutputStream();
1101
        response.setStatus(200);
1102
        response.setContentType("text/xml");
1103

    
1104
        Identifier id = new Identifier();
1105
        id.setValue(pid);
1106

    
1107
        logMetacat.debug("Calling delete");
1108
        MNodeService.getInstance(request).delete(session, id);
1109
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1110
        
1111
    }    
1112

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

    
1153
}
(8-8/9)