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-23 09:33:25 -0700 (Wed, 23 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
        
624
        Identifier pidid = new Identifier();
625
        pidid.setValue(pid);
626
        try {
627
            checksumAlgorithm = params.get("checksumAlgorithm")[0];
628
        } catch(Exception e) {
629
            //do nothing.  default to MD5
630
        	logMetacat.warn("No algorithm specified, using default: " + checksumAlgorithm);
631
        }
632
        logMetacat.debug("getting checksum for object " + pid + " with algorithm " + checksumAlgorithm);
633
        
634
        Checksum c = MNodeService.getInstance(request).getChecksum(session, pidid, checksumAlgorithm);
635
        logMetacat.debug("got checksum " + c.getValue());
636
        response.setStatus(200);
637
        logMetacat.debug("serializing response");
638
        TypeMarshaller.marshalTypeToOutputStream(c, response.getOutputStream());
639
        logMetacat.debug("done serializing response.");
640
        
641
    }
642
    
643
	/**
644
     * handle the replicate action for MN
645
	 * @throws JiBXException 
646
	 * @throws FileUploadException 
647
	 * @throws IOException 
648
	 * @throws InvalidRequest 
649
	 * @throws ServiceFailure 
650
	 * @throws UnsupportedType 
651
	 * @throws InsufficientResources 
652
	 * @throws NotAuthorized 
653
	 * @throws NotImplemented 
654
	 * @throws IllegalAccessException 
655
	 * @throws InstantiationException 
656
	 * @throws InvalidToken 
657
     */
658
    private void replicate() 
659
        throws ServiceFailure, InvalidRequest, IOException, FileUploadException, 
660
        JiBXException, NotImplemented, NotAuthorized, InsufficientResources, 
661
        UnsupportedType, InstantiationException, IllegalAccessException, InvalidToken {
662

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

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

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

    
744
    }
745

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1240
}
(8-8/9)