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-05-01 10:25:23 -0700 (Tue, 01 May 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
 * 		archive() - PUT /d1/mn/archive/PID
117

    
118
 *    systemMetadataChanged() - POST /dirtySystemMetadata/PID
119
 * 	
120
 * 	MNReplication
121
 * 		replicate() - POST /d1/mn/replicate
122
 *    getReplica() - GET /d1/mn/replica
123
 * 
124
 * ******************
125
 * @author leinfelder
126
 *
127
 */
128
public class MNResourceHandler extends D1ResourceHandler{
129

    
130
    // MN-specific API Resources
131
    protected static final String RESOURCE_MONITOR = "monitor";
132
    protected static final String RESOURCE_REPLICATE = "replicate";
133
    protected static final String RESOURCE_REPLICAS = "replica";
134
    protected static final String RESOURCE_NODE = "node";
135
    protected static final String RESOURCE_ERROR = "error";
136
    protected static final String RESOURCE_META_CHANGED = "dirtySystemMetadata";
137
    
138
    // shared executor
139
	private static ExecutorService executor = null;
140

    
141
	static {
142
		// use a shared executor service with nThreads == one less than available processors
143
    	int availableProcessors = Runtime.getRuntime().availableProcessors();
144
        int nThreads = availableProcessors * 1;
145
        nThreads--;
146
    	executor = Executors.newFixedThreadPool(nThreads);	
147
	}
148
	
149
    /**
150
     * Initializes new instance by setting servlet context,request and response
151
     * */
152
    public MNResourceHandler(ServletContext servletContext,
153
            HttpServletRequest request, HttpServletResponse response) {
154
    	super(servletContext, request, response);
155
        logMetacat = Logger.getLogger(MNResourceHandler.class);
156
    }
157
    
158
    @Override
159
    protected boolean isD1Enabled() {
160
    	
161
    	boolean enabled = false;
162
    	try {
163
			enabled = Boolean.parseBoolean(PropertyService.getProperty("dataone.mn.services.enabled"));
164
		} catch (PropertyNotFoundException e) {
165
			logMetacat.error("Could not check if DataONE is enabled: " + e.getMessage());
166
		}
167
    	
168
    	return enabled;	
169
    }
170

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

    
208
                if (resource.startsWith(RESOURCE_NODE)) {
209
                    // node response
210
                    node();
211
                    status = true;
212
                } else if (resource.startsWith(RESOURCE_IS_AUTHORIZED)) {
213
                    if (httpVerb == GET) {
214
                    	// after the command
215
                        extra = parseTrailing(resource, RESOURCE_IS_AUTHORIZED);
216
	                	// check the access rules
217
	                    isAuthorized(extra);
218
	                    status = true;
219
	                    logMetacat.debug("done getting access");
220
                    }
221
                } else if (resource.startsWith(RESOURCE_META)) {
222
                    logMetacat.debug("Using resource 'meta'");
223
                    // get
224
                    if (httpVerb == GET) {
225
                    	// after the command
226
                        extra = parseTrailing(resource, RESOURCE_META);
227
                        getSystemMetadataObject(extra);
228
                        status = true;
229
                    }
230
                    
231
                } else if (resource.startsWith(RESOURCE_OBJECTS)) {
232
                    logMetacat.debug("Using resource 'object'");
233
                    // after the command
234
                    extra = parseTrailing(resource, RESOURCE_OBJECTS);
235
                    logMetacat.debug("objectId: " + extra);
236
                    logMetacat.debug("verb:" + httpVerb);
237

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

    
356
    /**
357
     * Handles notification of system metadata changes for the given identifier
358
     * 
359
     * @param id  the identifier for the object
360
     * @throws InvalidToken 
361
     * @throws InvalidRequest 
362
     * @throws NotAuthorized 
363
     * @throws ServiceFailure 
364
     * @throws NotImplemented 
365
     */
366
    private void systemMetadataChanged() 
367
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
368
        InvalidToken {
369

    
370
        long serialVersion = 0L;
371
        String serialVersionStr = null;
372
        Date dateSysMetaLastModified = null;
373
        String dateSysMetaLastModifiedStr = null;
374
        Identifier pid = null;
375
        
376
        // mkae sure we have the multipart params
377
        try {
378
			initMultipartParams();
379
		} catch (Exception e1) {
380
			throw new ServiceFailure("1333", "Could not collect the multipart params for the request");
381
		}
382
        
383
        // get the pid
384
        try {
385
        	String id = multipartparams.get("pid").get(0);
386
        	pid = new Identifier();
387
            pid.setValue(id);            
388
        } catch (NullPointerException e) {
389
            String msg = "The 'pid' must be provided as a parameter and was not.";
390
            logMetacat.error(msg);
391
            throw new InvalidRequest("1334", msg);
392
        }      
393
        
394
        // get the serialVersion
395
        try {
396
            serialVersionStr = multipartparams.get("serialVersion").get(0);
397
            serialVersion = new Long(serialVersionStr).longValue();
398
            
399
        } catch (NullPointerException e) {
400
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
401
            logMetacat.error(msg);
402
            throw new InvalidRequest("1334", msg);
403
            
404
        }       
405
        
406
        // get the dateSysMetaLastModified
407
        try {
408
            dateSysMetaLastModifiedStr = multipartparams.get("dateSysMetaLastModified").get(0);
409
            dateSysMetaLastModified = DateTimeMarshaller.deserializeDateToUTC(dateSysMetaLastModifiedStr);
410
            
411
        } catch (NullPointerException e) {
412
            String msg = 
413
                "The 'dateSysMetaLastModified' must be provided as a " + 
414
                "parameter and was not, or was an invalid representation of the timestamp.";
415
            logMetacat.error(msg);
416
            throw new InvalidRequest("1334", msg);
417
            
418
        }       
419
        
420
        // call the service
421
        MNodeService.getInstance(request).systemMetadataChanged(session, pid, serialVersion, dateSysMetaLastModified);
422
        response.setStatus(200);
423
    }
424

    
425
    /**
426
     * Checks the access policy
427
     * @param id
428
     * @return
429
     * @throws ServiceFailure
430
     * @throws InvalidToken
431
     * @throws NotFound
432
     * @throws NotAuthorized
433
     * @throws NotImplemented
434
     * @throws InvalidRequest
435
     */
436
    private boolean isAuthorized(String id) throws ServiceFailure, InvalidToken, NotFound, NotAuthorized, NotImplemented, InvalidRequest {
437
		Identifier pid = new Identifier();
438
		pid.setValue(id);
439
		Permission permission = null;
440
		try {
441
			String perm = params.get("action")[0];
442
			permission = Permission.convert(perm);
443
		} catch (Exception e) {
444
			logMetacat.warn("No permission specified");
445
		}
446
		boolean result = MNodeService.getInstance(request).isAuthorized(session, pid, permission);
447
		response.setStatus(200);
448
		response.setContentType("text/xml");
449
		return result;
450
    }
451
    
452
    /**
453
     * Processes failed synchronization message
454
     * @throws NotImplemented
455
     * @throws ServiceFailure
456
     * @throws NotAuthorized
457
     * @throws InvalidRequest
458
     * @throws JiBXException
459
     * @throws IllegalAccessException 
460
     * @throws InstantiationException 
461
     * @throws IOException 
462
     */
463
    private void syncError() throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, JiBXException, IOException, InstantiationException, IllegalAccessException {
464
    	SynchronizationFailed syncFailed = null;
465
		try {
466
			syncFailed = collectSynchronizationFailed();
467
		} catch (ParserConfigurationException e) {
468
			throw new ServiceFailure("2161", e.getMessage());
469
		} catch (SAXException e) {
470
			throw new ServiceFailure("2161", e.getMessage());
471
		}
472
		
473
		MNodeService.getInstance(request).synchronizationFailed(session, syncFailed);
474
    }
475
    
476

    
477
    /**
478
     * Handles the monitoring resources
479
     * @return
480
     * @throws NotFound
481
     * @throws ParseException
482
     * @throws NotImplemented
483
     * @throws ServiceFailure
484
     * @throws NotAuthorized
485
     * @throws InvalidRequest
486
     * @throws InsufficientResources
487
     * @throws UnsupportedType
488
     * @throws IOException
489
     * @throws JiBXException
490
     */
491
    private boolean monitor(String pathInfo) 
492
      throws NotFound, ParseException, NotImplemented, ServiceFailure, 
493
      NotAuthorized, InvalidRequest, InsufficientResources, UnsupportedType, 
494
      IOException, JiBXException {
495
    	logMetacat.debug("processing monitor request");
496
        
497
        logMetacat.debug("verb is GET");
498
        logMetacat.debug("pathInfo is " + pathInfo);
499
        
500
        if (pathInfo.toLowerCase().equals("status")) {
501
            logMetacat.debug("processing status request");
502
            // TODO: implement in MNCore
503
            //MNodeService.getInstance().getStatus();
504
            return false;
505
            
506
        } else if (pathInfo.toLowerCase().equals("object")) {
507
            logMetacat.debug("processing object request");
508
            Identifier pid = null;
509
            ObjectFormat format = null;
510
            if (params.containsKey("format")) {
511
                String f = params.get("format")[0];
512
                format = ObjectFormatCache.getInstance().getFormat(f);
513
            }
514
            if (params.containsKey("pid")) {
515
                String id = params.get("pid")[0];
516
                pid = new Identifier();
517
                pid.setValue(id);
518
            }
519
            
520
            // TODO: implement in MNCore
521
            //ObjectStatistics objectStats = MNodeService.getInstance().getObjectStatistics(format, pid);
522
            return false;
523
            
524
        } else if (pathInfo.toLowerCase().equals("event")) {
525
            logMetacat.debug("processing event request");
526
            ObjectFormatIdentifier fmtid = null;
527
            String fromDateStr = null;
528
            Date fromDate = null;
529
            String toDateStr = null;
530
            Date toDate = null;
531
            String requestor = null;
532
            Subject subject = null;
533
            String eventName = null;
534
            Event event = null;
535

    
536
            if (params.containsKey("formatId")) {
537
                String f = params.get("formatId")[0];
538
                fmtid = ObjectFormatCache.getInstance().getFormat(f).getFormatId();
539
            }
540
            
541
            if (params.containsKey("fromDate")) {
542
                fromDateStr = params.get("fromDate")[0];
543
                fromDate = getDateAsUTC(fromDateStr);
544
            }
545
            
546
            if (params.containsKey("toDate")) {
547
              toDateStr = params.get("toDate")[0];
548
              toDate = getDateAsUTC(toDateStr);
549
            }
550
            
551
            if (params.containsKey("requestor")) {
552
            	requestor = params.get("requestor")[0];
553
            	subject = new Subject();
554
            	subject.setValue(requestor);
555
            }
556
            
557
            if (params.containsKey("event")) {
558
            	eventName = params.get("event")[0];
559
                event = Event.convert(eventName);
560
            }
561
            
562
            MonitorList monitorList = MNodeService.getInstance(request).getOperationStatistics(session, fromDate, toDate, subject, event, fmtid);
563
            
564
            OutputStream out = response.getOutputStream();
565
            response.setStatus(200);
566
            response.setContentType("text/xml");
567
            
568
            TypeMarshaller.marshalTypeToOutputStream(monitorList, out);
569
            
570
            return true;
571
            
572
        }
573
        
574
        return false;
575
    }
576
    
577
    /*
578
     * Parse an ISO8601 date string, returning a Date in UTC time if the string
579
     * ends in 'Z'.
580
     * 
581
     * @param fromDateStr -  the date string to be parsed
582
     * @return date -  the date object represented by the string
583
     */
584
    private Date getDateAsUTC(String fromDateStr)
585
      throws ParseException {
586

    
587
    	Date date = null;
588
    	
589
    	try {
590
    		// try the expected date format
591
        // a date format for date string arguments
592
        DateFormat dateFormat = 
593
        	new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
594

    
595
	      date = dateFormat.parse(fromDateStr);
596
      
597
    	} catch (ParseException e) {
598
    		// try the date with the UTC indicator
599
        DateFormat utcDateFormat = 
600
        	new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
601
        utcDateFormat.setTimeZone(TimeZone.getTimeZone("GMT-0"));
602
        date = utcDateFormat.parse(fromDateStr);
603
        
604
      }
605
    	
606
    	return date;
607
    }
608

    
609
		/**
610
     * Calculate the checksum 
611
     * @throws NotImplemented
612
     * @throws JiBXException
613
     * @throws IOException
614
     * @throws InvalidToken
615
     * @throws ServiceFailure
616
     * @throws NotAuthorized
617
     * @throws NotFound
618
     * @throws InvalidRequest
619
     */
620
    private void checksum(String pid) throws NotImplemented, JiBXException, IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest {
621
    	String checksumAlgorithm = "MD5";
622
        
623
        Identifier pidid = new Identifier();
624
        pidid.setValue(pid);
625
        try {
626
            checksumAlgorithm = params.get("checksumAlgorithm")[0];
627
        } catch(Exception e) {
628
            //do nothing.  default to MD5
629
        	logMetacat.warn("No algorithm specified, using default: " + checksumAlgorithm);
630
        }
631
        logMetacat.debug("getting checksum for object " + pid + " with algorithm " + checksumAlgorithm);
632
        
633
        Checksum c = MNodeService.getInstance(request).getChecksum(session, pidid, checksumAlgorithm);
634
        logMetacat.debug("got checksum " + c.getValue());
635
        response.setStatus(200);
636
        logMetacat.debug("serializing response");
637
        TypeMarshaller.marshalTypeToOutputStream(c, response.getOutputStream());
638
        logMetacat.debug("done serializing response.");
639
        
640
    }
641
    
642
	/**
643
     * handle the replicate action for MN
644
	 * @throws JiBXException 
645
	 * @throws FileUploadException 
646
	 * @throws IOException 
647
	 * @throws InvalidRequest 
648
	 * @throws ServiceFailure 
649
	 * @throws UnsupportedType 
650
	 * @throws InsufficientResources 
651
	 * @throws NotAuthorized 
652
	 * @throws NotImplemented 
653
	 * @throws IllegalAccessException 
654
	 * @throws InstantiationException 
655
	 * @throws InvalidToken 
656
     */
657
    private void replicate() 
658
        throws ServiceFailure, InvalidRequest, IOException, FileUploadException, 
659
        JiBXException, NotImplemented, NotAuthorized, InsufficientResources, 
660
        UnsupportedType, InstantiationException, IllegalAccessException, InvalidToken {
661

    
662
        logMetacat.debug("in POST replicate()");
663
        
664
        // somewhat unorthodox, but the call is asynchronous and we'd like to return this info sooner
665
        boolean allowed = false;
666
        if (session == null) {
667
        	String msg = "No session was provided.";
668
            NotAuthorized failure = new NotAuthorized("2152", msg);
669
        	throw failure;
670
        } else {
671
        	allowed = MNodeService.getInstance(request).isAdminAuthorized(session);
672
        	if (!allowed) {
673
        		String msg = "User is not an admin user";
674
                NotAuthorized failure = new NotAuthorized("2152", msg);
675
            	throw failure;
676
        	}
677
        }
678
        
679
        //parse the systemMetadata
680
        final SystemMetadata sysmeta = collectSystemMetadata();
681
        
682
        String sn = multipartparams.get("sourceNode").get(0);
683
        logMetacat.debug("sourceNode: " + sn);
684
        final NodeReference sourceNode = new NodeReference();
685
        sourceNode.setValue(sn);
686
        
687
        // run it in a thread to avoid connection timeout
688
        Runnable runner = new Runnable() {
689
			@Override
690
			public void run() {
691
				try {
692
			        MNodeService.getInstance(request).replicate(session, sysmeta, sourceNode);
693
				} catch (Exception e) {
694
					logMetacat.error("Error running replication: " + e.getMessage(), e);
695
					throw new RuntimeException(e.getMessage(), e);
696
				}
697
			}
698
    	};
699
    	// submit the task, and that's it
700
    	executor.submit(runner);
701
        
702
    	// thread was started, so we return success
703
        response.setStatus(200);
704
        
705
    }
706

    
707
    /**
708
     * Handle the getReplica action for the MN
709
     * @param id  the identifier for the object
710
     * @throws NotFound 
711
     * @throws ServiceFailure 
712
     * @throws NotImplemented 
713
     * @throws NotAuthorized 
714
     * @throws InvalidToken 
715
     * @throws InvalidRequest 
716
     */
717
    private void getReplica(String id) 
718
        throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented, 
719
        ServiceFailure, NotFound {
720
        
721
        Identifier pid = new Identifier();
722
        pid.setValue(id);
723
        OutputStream out = null;
724
        InputStream dataBytes = null;
725
                
726
        try {
727
            // call the service
728
            dataBytes = MNodeService.getInstance(request).getReplica(session, pid);
729

    
730
            response.setContentType("application/octet-stream");
731
            response.setStatus(200);
732
            out = response.getOutputStream();
733
            // write the object to the output stream
734
            IOUtils.copyLarge(dataBytes, out);
735
            
736
        } catch (IOException e) {
737
            String msg = "There was an error writing the output: " + e.getMessage();
738
            logMetacat.error(msg);
739
            throw new ServiceFailure("2181", msg);
740
        
741
        }
742

    
743
    }
744

    
745
    /**
746
     * Get the Node information
747
     * 
748
     * @throws JiBXException
749
     * @throws IOException
750
     * @throws InvalidRequest 
751
     * @throws ServiceFailure 
752
     * @throws NotAuthorized 
753
     * @throws NotImplemented 
754
     */
755
    private void node() 
756
        throws JiBXException, IOException, NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest {
757
        
758
        Node n = MNodeService.getInstance(request).getCapabilities();
759
        
760
        response.setContentType("text/xml");
761
        response.setStatus(200);
762
        TypeMarshaller.marshalTypeToOutputStream(n, response.getOutputStream());
763
        
764
    }
765
    
766
    /**
767
     * MN_crud.describe()
768
     * http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.describe
769
     * @param pid
770
     * @throws InvalidRequest 
771
     * @throws NotImplemented 
772
     * @throws NotFound 
773
     * @throws NotAuthorized 
774
     * @throws ServiceFailure 
775
     * @throws InvalidToken 
776
     */
777
    private void describeObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest
778
    {
779
        
780
        response.setContentType("text/xml");
781

    
782
        Identifier id = new Identifier();
783
        id.setValue(pid);
784

    
785
        DescribeResponse dr = null;
786
        try {
787
        	dr = MNodeService.getInstance(request).describe(session, id);
788
        } catch (BaseException e) {
789
        	response.setStatus(e.getCode());
790
        	response.addHeader("DataONE-Exception-Name", e.getClass().getName());
791
            response.addHeader("DataONE-Exception-DetailCode", e.getDetail_code());
792
            response.addHeader("DataONE-Exception-Description", e.getDescription());
793
            response.addHeader("DataONE-Exception-PID", id.getValue());
794
            return;
795
		}
796
        
797
        response.setStatus(200);
798
        
799
        //response.addHeader("pid", pid);
800
        response.addHeader("DataONE-Checksum", dr.getDataONE_Checksum().getAlgorithm() + "," + dr.getDataONE_Checksum().getValue());
801
        response.addHeader("Content-Length", dr.getContent_Length() + "");
802
        response.addHeader("Last-Modified", DateTimeMarshaller.serializeDateToUTC(dr.getLast_Modified()));
803
        response.addHeader("DataONE-ObjectFormat", dr.getDataONE_ObjectFormatIdentifier().getValue());
804
        response.addHeader("DataONE-SerialVersion", dr.getSerialVersion().toString());
805

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

    
928
            out = response.getOutputStream();  
929
            IOUtils.copyLarge(data, out);
930
            
931
        }
932
        else
933
        { //call listObjects with specified params
934
            Date startTime = null;
935
            Date endTime = null;
936
            ObjectFormatIdentifier formatId = null;
937
            boolean replicaStatus = false;
938
            int start = 0;
939
            //TODO: make the max count into a const
940
            int count = 1000;
941
            Enumeration paramlist = request.getParameterNames();
942
            while (paramlist.hasMoreElements()) 
943
            { //parse the params and make the crud call
944
                String name = (String) paramlist.nextElement();
945
                String[] value = (String[])request.getParameterValues(name);
946

    
947
                if (name.equals("fromDate") && value != null)
948
                {
949
                    try
950
                    {
951
                      //startTime = dateFormat.parse(value[0]);
952
                    	startTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
953
                        //startTime = parseDateAndConvertToGMT(value[0]);
954
                    }
955
                    catch(Exception e)
956
                    {  //if we can't parse it, just don't use the fromDate param
957
                        logMetacat.warn("Could not parse fromDate: " + value[0]);
958
                        startTime = null;
959
                    }
960
                }
961
                else if(name.equals("toDate") && value != null)
962
                {
963
                    try
964
                    {
965
                    	endTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
966
                    }
967
                    catch(Exception e)
968
                    {  //if we can't parse it, just don't use the toDate param
969
                        logMetacat.warn("Could not parse toDate: " + value[0]);
970
                        endTime = null;
971
                    }
972
                }
973
                else if(name.equals("formatId") && value != null) 
974
                {
975
                	formatId = new ObjectFormatIdentifier();
976
                	formatId.setValue(value[0]);
977
                }
978
                else if(name.equals("replicaStatus") && value != null)
979
                {
980
                    if(value != null && 
981
                       value.length > 0 && 
982
                       (value[0].equals("true") || value[0].equals("TRUE") || value[0].equals("YES")))
983
                    {
984
                        replicaStatus = true;
985
                    }
986
                }
987
                else if(name.equals("start") && value != null)
988
                {
989
                    start = new Integer(value[0]).intValue();
990
                }
991
                else if(name.equals("count") && value != null)
992
                {
993
                    count = new Integer(value[0]).intValue();
994
                }
995
            }
996
            //make the crud call
997
            logMetacat.debug("session: " + session + " startTime: " + startTime +
998
                    " endTime: " + endTime + " formatId: " + 
999
                    formatId + " replicaStatus: " + replicaStatus + 
1000
                    " start: " + start + " count: " + count);
1001
           
1002
            ObjectList ol = 
1003
           	 MNodeService.getInstance(request).listObjects(session, startTime, endTime, 
1004
           			formatId, replicaStatus, start, count);
1005
           
1006
            out = response.getOutputStream();  
1007
            response.setStatus(200);
1008
            response.setContentType("text/xml");
1009
            // Serialize and write it to the output stream
1010
            TypeMarshaller.marshalTypeToOutputStream(ol, out);
1011
            
1012
        }
1013
        
1014
    }
1015
    
1016

    
1017
    /**
1018
     * Retrieve System Metadata
1019
     * @param pid
1020
     * @throws InvalidToken
1021
     * @throws ServiceFailure
1022
     * @throws NotAuthorized
1023
     * @throws NotFound
1024
     * @throws InvalidRequest
1025
     * @throws NotImplemented
1026
     * @throws IOException
1027
     * @throws JiBXException
1028
     */
1029
    protected void getSystemMetadataObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
1030

    
1031
        Identifier id = new Identifier();
1032
        id.setValue(pid);
1033
        SystemMetadata sysmeta = MNodeService.getInstance(request).getSystemMetadata(session, id);
1034
        
1035
        response.setContentType("text/xml");
1036
        response.setStatus(200);
1037
        OutputStream out = response.getOutputStream();
1038
        
1039
        // Serialize and write it to the output stream
1040
        TypeMarshaller.marshalTypeToOutputStream(sysmeta, out);
1041
   }
1042
    
1043
    
1044
    /**
1045
     * Inserts or updates the object
1046
     * 
1047
     * @param pid - ID of data object to be inserted or updated.  If action is update, the pid
1048
     *               is the existing pid.  If insert, the pid is the new one
1049
     * @throws InvalidRequest 
1050
     * @throws ServiceFailure 
1051
     * @throws JiBXException 
1052
     * @throws NotImplemented 
1053
     * @throws InvalidSystemMetadata 
1054
     * @throws InsufficientResources 
1055
     * @throws UnsupportedType 
1056
     * @throws IdentifierNotUnique 
1057
     * @throws NotAuthorized 
1058
     * @throws InvalidToken 
1059
     * @throws NotFound 
1060
     * @throws IOException 
1061
     * @throws IllegalAccessException 
1062
     * @throws InstantiationException 
1063
     */
1064
    protected void putObject(String trailingPid, String action) throws ServiceFailure, InvalidRequest, JiBXException, InvalidToken, NotAuthorized, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, NotFound, IOException, InstantiationException, IllegalAccessException {
1065
       
1066
    	// Read the incoming data from its Mime Multipart encoding
1067
    	Map<String, File> files = collectMultipartFiles();
1068
               
1069
    	Identifier pid = new Identifier();
1070
        if (trailingPid == null) {
1071
	        // get the pid string from the body and set the value
1072
	        String pidString = multipartparams.get("pid").get(0);
1073
	        if (pidString != null) {
1074
            pid.setValue(pidString);
1075
            
1076
          } else {
1077
              throw new InvalidRequest("1102", "The pid param must be included and contain the identifier.");
1078
              
1079
          }
1080
        } else {
1081
        	// use the pid included in the URL
1082
        	pid.setValue(trailingPid);
1083
        }
1084
        logMetacat.debug("putObject with pid " + pid.getValue());
1085
        logMetacat.debug("Entering putObject: " + pid.getValue() + "/" + action);
1086

    
1087
        InputStream object = null;
1088
        InputStream sysmeta = null;
1089
        File smFile = files.get("sysmeta");
1090
        sysmeta = new FileInputStream(smFile);
1091
        File objFile = files.get("object");
1092
        object = new FileInputStream(objFile);
1093
        
1094
        // ensure we have the object bytes
1095
        if  ( objFile == null ) {
1096
            throw new InvalidRequest("1102", "The object param must contain the object bytes.");
1097
            
1098
        }
1099
        
1100
        // ensure we have the system metadata
1101
        if  ( smFile == null ) {
1102
            throw new InvalidRequest("1102", "The sysmeta param must contain the system metadata document.");
1103
            
1104
        }
1105
        
1106
        response.setStatus(200);
1107
        response.setContentType("text/xml");
1108
        OutputStream out = response.getOutputStream();
1109
        
1110
        if (action.equals(FUNCTION_NAME_INSERT)) { 
1111
            // handle inserts
1112
            logMetacat.debug("Commence creation...");
1113
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1114

    
1115
            logMetacat.debug("creating object with pid " + pid.getValue());
1116
            Identifier rId = MNodeService.getInstance(request).create(session, pid, object, smd);
1117
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1118
            
1119
        } else if (action.equals(FUNCTION_NAME_UPDATE)) {
1120
        	// handle updates
1121
        	
1122
            // construct pids
1123
            Identifier newPid = null;
1124
            try {
1125
            	String newPidString = multipartparams.get("newPid").get(0);
1126
            	newPid = new Identifier();
1127
            	newPid.setValue(newPidString);
1128
            } catch (Exception e) {
1129
				logMetacat.error("Could not get newPid from request");
1130
			}
1131
            logMetacat.debug("Commence update...");
1132
            
1133
            // get the systemmetadata object
1134
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1135

    
1136
            Identifier rId = MNodeService.getInstance(request).update(session, pid, object, newPid, smd);
1137
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1138
        } else {
1139
            throw new InvalidRequest("1000", "Operation must be create or update.");
1140
        }
1141
   
1142
    }
1143

    
1144
    /**
1145
     * Handle delete 
1146
     * @param pid ID of data object to be deleted
1147
     * @throws IOException
1148
     * @throws InvalidRequest 
1149
     * @throws NotImplemented 
1150
     * @throws NotFound 
1151
     * @throws NotAuthorized 
1152
     * @throws ServiceFailure 
1153
     * @throws InvalidToken 
1154
     * @throws JiBXException 
1155
     */
1156
    private void deleteObject(String pid) throws IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest, JiBXException 
1157
    {
1158

    
1159
        OutputStream out = response.getOutputStream();
1160
        response.setStatus(200);
1161
        response.setContentType("text/xml");
1162

    
1163
        Identifier id = new Identifier();
1164
        id.setValue(pid);
1165

    
1166
        logMetacat.debug("Calling delete");
1167
        MNodeService.getInstance(request).delete(session, id);
1168
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1169
        
1170
    }
1171
    
1172
    /**
1173
     * Archives the given pid
1174
     * @param pid
1175
     * @throws InvalidToken
1176
     * @throws ServiceFailure
1177
     * @throws NotAuthorized
1178
     * @throws NotFound
1179
     * @throws NotImplemented
1180
     * @throws IOException
1181
     * @throws JiBXException
1182
     */
1183
    private void archive(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, IOException, JiBXException {
1184

    
1185
        OutputStream out = response.getOutputStream();
1186
        response.setStatus(200);
1187
        response.setContentType("text/xml");
1188

    
1189
        Identifier id = new Identifier();
1190
        id.setValue(pid);
1191

    
1192
        logMetacat.debug("Calling archive");
1193
        MNodeService.getInstance(request).archive(session, id);
1194
        
1195
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1196
        
1197
    }
1198

    
1199
	protected SynchronizationFailed collectSynchronizationFailed() throws IOException, ServiceFailure, InvalidRequest, JiBXException, InstantiationException, IllegalAccessException, ParserConfigurationException, SAXException  {
1200
		
1201
		// Read the incoming data from its Mime Multipart encoding
1202
		logMetacat.debug("Disassembling MIME multipart form");
1203
		InputStream sf = null;
1204
	
1205
		// handle MMP inputs
1206
		File tmpDir = getTempDirectory();
1207
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
1208
		MultipartRequestResolver mrr = 
1209
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
1210
		MultipartRequest mr = null;
1211
		try {
1212
			mr = mrr.resolveMultipart(request);
1213
		} catch (Exception e) {
1214
			throw new ServiceFailure("2161", 
1215
					"Could not resolve multipart: " + e.getMessage());
1216
		}
1217
		logMetacat.debug("resolved multipart request");
1218
		Map<String, File> files = mr.getMultipartFiles();
1219
		if (files == null || files.keySet() == null) {
1220
			throw new InvalidRequest("2163",
1221
					"must have multipart file with name 'message'");
1222
		}
1223
		logMetacat.debug("got multipart files");
1224
	
1225
		multipartparams = mr.getMultipartParameters();
1226
	
1227
		File sfFile = files.get("message");
1228
		if (sfFile == null) {
1229
			throw new InvalidRequest("2163",
1230
					"Missing the required file-part 'message' from the multipart request.");
1231
		}
1232
		logMetacat.debug("sfFile: " + sfFile.getAbsolutePath());
1233
		sf = new FileInputStream(sfFile);
1234
	
1235
		SynchronizationFailed syncFailed = (SynchronizationFailed) ExceptionHandler.deserializeXml(sf, "Error deserializing exception");
1236
		return syncFailed;
1237
	}
1238

    
1239
}
(8-8/9)