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-31 21:04:46 -0700 (Thu, 31 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
        nThreads = Math.max(1, nThreads);
147
    	executor = Executors.newFixedThreadPool(nThreads);	
148
	}
149
	
150
    /**
151
     * Initializes new instance by setting servlet context,request and response
152
     * */
153
    public MNResourceHandler(ServletContext servletContext,
154
            HttpServletRequest request, HttpServletResponse response) {
155
    	super(servletContext, request, response);
156
        logMetacat = Logger.getLogger(MNResourceHandler.class);
157
    }
158
    
159
    @Override
160
    protected boolean isD1Enabled() {
161
    	
162
    	boolean enabled = false;
163
    	try {
164
			enabled = Boolean.parseBoolean(PropertyService.getProperty("dataone.mn.services.enabled"));
165
		} catch (PropertyNotFoundException e) {
166
			logMetacat.error("Could not check if DataONE is enabled: " + e.getMessage());
167
		}
168
    	
169
    	return enabled;	
170
    }
171

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
749
    }
750

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

    
788
        Identifier id = new Identifier();
789
        id.setValue(pid);
790

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

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

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

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

    
1023
    /**
1024
     * Retrieve System Metadata
1025
     * @param pid
1026
     * @throws InvalidToken
1027
     * @throws ServiceFailure
1028
     * @throws NotAuthorized
1029
     * @throws NotFound
1030
     * @throws InvalidRequest
1031
     * @throws NotImplemented
1032
     * @throws IOException
1033
     * @throws JiBXException
1034
     */
1035
    protected void getSystemMetadataObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
1036

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

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

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

    
1142
            Identifier rId = MNodeService.getInstance(request).update(session, pid, object, newPid, smd);
1143
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1144
        } else {
1145
            throw new InvalidRequest("1000", "Operation must be create or update.");
1146
        }
1147
   
1148
    }
1149

    
1150
    /**
1151
     * Handle delete 
1152
     * @param pid ID of data object to be deleted
1153
     * @throws IOException
1154
     * @throws InvalidRequest 
1155
     * @throws NotImplemented 
1156
     * @throws NotFound 
1157
     * @throws NotAuthorized 
1158
     * @throws ServiceFailure 
1159
     * @throws InvalidToken 
1160
     * @throws JiBXException 
1161
     */
1162
    private void deleteObject(String pid) throws IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest, JiBXException 
1163
    {
1164

    
1165
        OutputStream out = response.getOutputStream();
1166
        response.setStatus(200);
1167
        response.setContentType("text/xml");
1168

    
1169
        Identifier id = new Identifier();
1170
        id.setValue(pid);
1171

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

    
1191
        OutputStream out = response.getOutputStream();
1192
        response.setStatus(200);
1193
        response.setContentType("text/xml");
1194

    
1195
        Identifier id = new Identifier();
1196
        id.setValue(pid);
1197

    
1198
        logMetacat.debug("Calling archive");
1199
        MNodeService.getInstance(request).archive(session, id);
1200
        
1201
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1202
        
1203
    }
1204

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

    
1245
}
(8-8/9)