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: cjones $'
7
 *     '$Date: 2011-11-08 10:36:42 -0800 (Tue, 08 Nov 2011) $'
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.ByteArrayInputStream;
26
import java.io.File;
27
import java.io.FileInputStream;
28
import java.io.IOException;
29
import java.io.InputStream;
30
import java.io.OutputStream;
31
import java.text.DateFormat;
32
import java.text.ParseException;
33
import java.text.SimpleDateFormat;
34
import java.util.Date;
35
import java.util.Enumeration;
36
import java.util.Map;
37
import java.util.TimeZone;
38

    
39
import javax.servlet.ServletContext;
40
import javax.servlet.http.HttpServletRequest;
41
import javax.servlet.http.HttpServletResponse;
42
import javax.xml.parsers.ParserConfigurationException;
43

    
44
import org.apache.commons.fileupload.FileUploadException;
45
import org.apache.commons.io.IOUtils;
46
import org.apache.log4j.Logger;
47
import org.dataone.client.ObjectFormatCache;
48
import org.dataone.mimemultipart.MultipartRequest;
49
import org.dataone.mimemultipart.MultipartRequestResolver;
50
import org.dataone.service.exceptions.BaseException;
51
import org.dataone.service.exceptions.IdentifierNotUnique;
52
import org.dataone.service.exceptions.InsufficientResources;
53
import org.dataone.service.exceptions.InvalidRequest;
54
import org.dataone.service.exceptions.InvalidSystemMetadata;
55
import org.dataone.service.exceptions.InvalidToken;
56
import org.dataone.service.exceptions.NotAuthorized;
57
import org.dataone.service.exceptions.NotFound;
58
import org.dataone.service.exceptions.NotImplemented;
59
import org.dataone.service.exceptions.ServiceFailure;
60
import org.dataone.service.exceptions.SynchronizationFailed;
61
import org.dataone.service.exceptions.UnsupportedType;
62
import org.dataone.service.types.v1.AccessPolicy;
63
import org.dataone.service.types.v1.Checksum;
64
import org.dataone.service.types.v1.DescribeResponse;
65
import org.dataone.service.types.v1.Event;
66
import org.dataone.service.types.v1.Identifier;
67
import org.dataone.service.types.v1.Log;
68
import org.dataone.service.types.v1.MonitorList;
69
import org.dataone.service.types.v1.Node;
70
import org.dataone.service.types.v1.NodeReference;
71
import org.dataone.service.types.v1.ObjectFormat;
72
import org.dataone.service.types.v1.ObjectFormatIdentifier;
73
import org.dataone.service.types.v1.ObjectList;
74
import org.dataone.service.types.v1.Permission;
75
import org.dataone.service.types.v1.Subject;
76
import org.dataone.service.types.v1.SystemMetadata;
77
import org.dataone.service.util.DateTimeMarshaller;
78
import org.dataone.service.util.ExceptionHandler;
79
import org.dataone.service.util.TypeMarshaller;
80
import org.jibx.runtime.JiBXException;
81
import org.xml.sax.SAXException;
82

    
83
import edu.ucsb.nceas.metacat.dataone.CNodeService;
84
import edu.ucsb.nceas.metacat.dataone.MNodeService;
85

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

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

    
134
    /**
135
     * Initializes new instance by setting servlet context,request and response
136
     * */
137
    public MNResourceHandler(ServletContext servletContext,
138
            HttpServletRequest request, HttpServletResponse response) {
139
    	super(servletContext, request, response);
140
        logMetacat = Logger.getLogger(MNResourceHandler.class);
141
    }
142

    
143
    /**
144
     * This function is called from REST API servlet and handles each request to the servlet 
145
     * 
146
     * @param httpVerb (GET, POST, PUT or DELETE)
147
     */
148
    @Override
149
    public void handle(byte httpVerb) {
150
    	// prepare the handler
151
    	super.handle(httpVerb);
152
    	
153
        try {
154
        	// get the resource
155
            String resource = request.getPathInfo();
156
            resource = resource.substring(resource.indexOf("/") + 1);
157
            
158
            // default to node info
159
            if (resource.equals("")) {
160
                resource = RESOURCE_NODE;
161
            }
162
            
163
            // get the rest of the path info
164
            String extra = null;
165
                        
166
            logMetacat.debug("handling verb " + httpVerb + " request with resource '" + resource + "'");
167
            logMetacat.debug("resource: '" + resource + "'");
168
            boolean status = false;
169
            
170
            if (resource != null) {
171

    
172
                if (resource.startsWith(RESOURCE_NODE)) {
173
                    // node response
174
                    node();
175
                    status = true;
176
                } else if (resource.startsWith(RESOURCE_ACCESS_RULES)) {
177
                    if (httpVerb == PUT) {
178
                    	// after the command
179
                        extra = parseTrailing(resource, RESOURCE_ACCESS_RULES);
180
	                	// set the access rules
181
	                    setAccess(extra);
182
	                    status = true;
183
	                    logMetacat.debug("done setting access");
184
                    }
185
                } else if (resource.startsWith(RESOURCE_IS_AUTHORIZED)) {
186
                    if (httpVerb == GET) {
187
                    	// after the command
188
                        extra = parseTrailing(resource, RESOURCE_IS_AUTHORIZED);
189
	                	// check the access rules
190
	                    isAuthorized(extra);
191
	                    status = true;
192
	                    logMetacat.debug("done getting access");
193
                    }
194
                } else if (resource.startsWith(RESOURCE_META)) {
195
                    logMetacat.debug("Using resource 'meta'");
196
                    // get
197
                    if (httpVerb == GET) {
198
                    	// after the command
199
                        extra = parseTrailing(resource, RESOURCE_META);
200
                        getSystemMetadataObject(extra);
201
                        status = true;
202
                    }
203
                    
204
                } else if (resource.startsWith(RESOURCE_OBJECTS)) {
205
                    logMetacat.debug("Using resource 'object'");
206
                    // after the command
207
                    extra = parseTrailing(resource, RESOURCE_OBJECTS);
208
                    logMetacat.debug("objectId: " + extra);
209
                    logMetacat.debug("verb:" + httpVerb);
210

    
211
                    if (httpVerb == GET) {
212
                        getObject(extra);
213
                        status = true;
214
                    } else if (httpVerb == POST) {
215
                        putObject(extra, FUNCTION_NAME_INSERT);
216
                        status = true;
217
                    } else if (httpVerb == PUT) {
218
                        putObject(extra, FUNCTION_NAME_UPDATE);
219
                        status = true;
220
                    } else if (httpVerb == DELETE) {
221
                        deleteObject(extra);
222
                        status = true;
223
                    } else if (httpVerb == HEAD) {
224
                        describeObject(extra);
225
                        status = true;
226
                    }
227
                  
228
                } else if (resource.startsWith(RESOURCE_LOG)) {
229
                    logMetacat.debug("Using resource 'log'");
230
                    // handle log events
231
                    if (httpVerb == GET) {
232
                        getLog();
233
                        status = true;
234
                    }
235
                } else if (resource.startsWith(RESOURCE_CHECKSUM)) {
236
                    logMetacat.debug("Using resource 'checksum'");
237
                    // handle checksum requests
238
                    if (httpVerb == GET) {
239
                    	// after the command
240
                        extra = parseTrailing(resource, RESOURCE_CHECKSUM);
241
                        checksum(extra);
242
                        status = true;
243
                    }
244
                } else if (resource.startsWith(RESOURCE_MONITOR)) {
245
                    // there are various parts to monitoring
246
                    if (httpVerb == GET) {
247
                    	// after the command
248
                        extra = parseTrailing(resource, RESOURCE_MONITOR);
249
                    	// health monitoring calls
250
                        status = monitor(extra);
251
                    }
252
                } else if (resource.startsWith(RESOURCE_REPLICATE)) {
253
                	if (httpVerb == POST) {
254
	                    logMetacat.debug("processing replicate request");
255
	                    replicate();
256
	                    status = true;
257
                	}
258
                } else if (resource.startsWith(RESOURCE_ERROR)) {
259
	                // sync error
260
	                if (httpVerb == POST) {
261
	                    syncError();
262
	                    status = true;
263
	                }
264
                } else if (resource.startsWith(RESOURCE_META_CHANGED)) {
265
                    // system metadata changed
266
                    if (httpVerb == POST) {
267
                        extra = parseTrailing(resource, RESOURCE_META_CHANGED);
268
                        systemMetadataChanged(extra);
269
                        status = true;
270
                    }
271
                } else if (resource.startsWith(RESOURCE_REPLICAS)) {
272
                    // get replica
273
                    if (httpVerb == GET) {
274
                        extra = parseTrailing(resource, RESOURCE_REPLICAS);
275
                        getReplica(extra);
276
                        status = true;
277
                    }
278
                }
279
                
280
                if (!status) {
281
                	throw new ServiceFailure("0000", "Unknown error, status = " + status);
282
                }
283
            } else {
284
            	throw new InvalidRequest("0000", "No resource matched for " + resource);
285
            }
286
        } catch (BaseException be) {
287
        	// report Exceptions as clearly as possible
288
        	OutputStream out = null;
289
			try {
290
				out = response.getOutputStream();
291
			} catch (IOException e) {
292
				logMetacat.error("Could not get output stream from response", e);
293
			}
294
            serializeException(be, out);
295
        } catch (Exception e) {
296
            // report Exceptions as clearly and generically as possible
297
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
298
        	OutputStream out = null;
299
			try {
300
				out = response.getOutputStream();
301
			} catch (IOException ioe) {
302
				logMetacat.error("Could not get output stream from response", ioe);
303
			}
304
			ServiceFailure se = new ServiceFailure("0000", e.getMessage());
305
            serializeException(se, out);
306
        }
307
    }
308
    
309

    
310
    /**
311
     * Handles notification of system metadata changes for the given identifier
312
     * 
313
     * @param id  the identifier for the object
314
     * @throws InvalidToken 
315
     * @throws InvalidRequest 
316
     * @throws NotAuthorized 
317
     * @throws ServiceFailure 
318
     * @throws NotImplemented 
319
     */
320
    private void systemMetadataChanged(String id) 
321
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
322
        InvalidToken {
323

    
324
        long serialVersion = 0L;
325
        String serialVersionStr = null;
326
        Date dateSysMetaLastModified = null;
327
        String dateSysMetaLastModifiedStr = null;
328
        Identifier pid = new Identifier();
329
        pid.setValue(id);
330
        
331
        // get the serialVersion
332
        try {
333
            serialVersionStr = params.get("serialVersion")[0];
334
            serialVersion = new Long(serialVersionStr).longValue();
335
            
336
        } catch (NullPointerException e) {
337
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
338
            logMetacat.error(msg);
339
            throw new InvalidRequest("1334", msg);
340
            
341
        }       
342
        
343
        // get the dateSysMetaLastModified
344
        try {
345
            dateSysMetaLastModifiedStr = params.get("dateSysMetaLastModified")[0];
346
            dateSysMetaLastModified = DateTimeMarshaller.deserializeDateToUTC(dateSysMetaLastModifiedStr);
347
            
348
        } catch (NullPointerException e) {
349
            String msg = 
350
                "The 'dateSysMetaLastModified' must be provided as a " + 
351
                "parameter and was not, or was an invalid representation of the timestamp.";
352
            logMetacat.error(msg);
353
            throw new InvalidRequest("1334", msg);
354
            
355
        }       
356
        
357
        // call the service
358
        MNodeService.getInstance(request).systemMetadataChanged(session, pid, serialVersion, dateSysMetaLastModified);
359
        response.setStatus(200);
360
    }
361

    
362
    /**
363
     * Checks the access policy
364
     * @param id
365
     * @return
366
     * @throws ServiceFailure
367
     * @throws InvalidToken
368
     * @throws NotFound
369
     * @throws NotAuthorized
370
     * @throws NotImplemented
371
     * @throws InvalidRequest
372
     */
373
    private boolean isAuthorized(String id) throws ServiceFailure, InvalidToken, NotFound, NotAuthorized, NotImplemented, InvalidRequest {
374
		Identifier pid = new Identifier();
375
		pid.setValue(id);
376
		Permission permission = null;
377
		try {
378
			String perm = params.get("action")[0];
379
			permission = Permission.convert(perm);
380
		} catch (Exception e) {
381
			logMetacat.warn("No permission specified");
382
		}
383
		boolean result = MNodeService.getInstance(request).isAuthorized(session, pid, permission);
384
		response.setStatus(200);
385
		response.setContentType("text/xml");
386
		return result;
387
    }
388
    
389
    /**
390
     * Processes failed synchronization message
391
     * @throws NotImplemented
392
     * @throws ServiceFailure
393
     * @throws NotAuthorized
394
     * @throws InvalidRequest
395
     * @throws JiBXException
396
     * @throws IllegalAccessException 
397
     * @throws InstantiationException 
398
     * @throws IOException 
399
     */
400
    private void syncError() throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, JiBXException, IOException, InstantiationException, IllegalAccessException {
401
    	SynchronizationFailed syncFailed = null;
402
		try {
403
			syncFailed = collectSynchronizationFailed();
404
		} catch (ParserConfigurationException e) {
405
			throw new ServiceFailure("2161", e.getMessage());
406
		} catch (SAXException e) {
407
			throw new ServiceFailure("2161", e.getMessage());
408
		}
409
		
410
		MNodeService.getInstance(request).synchronizationFailed(session, syncFailed);
411
    }
412
    
413

    
414
    /**
415
     * Handles the monitoring resources
416
     * @return
417
     * @throws NotFound
418
     * @throws ParseException
419
     * @throws NotImplemented
420
     * @throws ServiceFailure
421
     * @throws NotAuthorized
422
     * @throws InvalidRequest
423
     * @throws InsufficientResources
424
     * @throws UnsupportedType
425
     * @throws IOException
426
     * @throws JiBXException
427
     */
428
    private boolean monitor(String pathInfo) 
429
      throws NotFound, ParseException, NotImplemented, ServiceFailure, 
430
      NotAuthorized, InvalidRequest, InsufficientResources, UnsupportedType, 
431
      IOException, JiBXException {
432
    	logMetacat.debug("processing monitor request");
433
        
434
        logMetacat.debug("verb is GET");
435
        logMetacat.debug("pathInfo is " + pathInfo);
436
        
437
        if (pathInfo.toLowerCase().equals("ping")) {
438
            logMetacat.debug("processing ping request");
439
            boolean result = MNodeService.getInstance(request).ping();
440
            return result;
441
            
442
        } else if (pathInfo.toLowerCase().equals("status")) {
443
            logMetacat.debug("processing status request");
444
            // TODO: implement in MNCore
445
            //MNodeService.getInstance().getStatus();
446
            return false;
447
            
448
        } else if (pathInfo.toLowerCase().equals("object")) {
449
            logMetacat.debug("processing object request");
450
            Identifier pid = null;
451
            ObjectFormat format = null;
452
            if (params.containsKey("format")) {
453
                String f = params.get("format")[0];
454
                format = ObjectFormatCache.getInstance().getFormat(f);
455
            }
456
            if (params.containsKey("pid")) {
457
                String id = params.get("pid")[0];
458
                pid = new Identifier();
459
                pid.setValue(id);
460
            }
461
            
462
            // TODO: implement in MNCore
463
            //ObjectStatistics objectStats = MNodeService.getInstance().getObjectStatistics(format, pid);
464
            return false;
465
            
466
        } else if (pathInfo.toLowerCase().equals("event")) {
467
            logMetacat.debug("processing event request");
468
            ObjectFormatIdentifier fmtid = null;
469
            String fromDateStr = null;
470
            Date fromDate = null;
471
            String toDateStr = null;
472
            Date toDate = null;
473
            String requestor = null;
474
            Subject subject = null;
475
            String eventName = null;
476
            Event event = null;
477

    
478
            if (params.containsKey("formatId")) {
479
                String f = params.get("formatId")[0];
480
                fmtid = ObjectFormatCache.getInstance().getFormat(f).getFormatId();
481
            }
482
            
483
            if (params.containsKey("fromDate")) {
484
                fromDateStr = params.get("fromDate")[0];
485
                fromDate = getDateAsUTC(fromDateStr);
486
            }
487
            
488
            if (params.containsKey("toDate")) {
489
              toDateStr = params.get("toDate")[0];
490
              toDate = getDateAsUTC(toDateStr);
491
            }
492
            
493
            if (params.containsKey("requestor")) {
494
            	requestor = params.get("requestor")[0];
495
            	subject = new Subject();
496
            	subject.setValue(requestor);
497
            }
498
            
499
            if (params.containsKey("event")) {
500
            	eventName = params.get("event")[0];
501
                event = Event.convert(eventName);
502
            }
503
            
504
            MonitorList monitorList = MNodeService.getInstance(request).getOperationStatistics(session, fromDate, toDate, subject, event, fmtid);
505
            
506
            OutputStream out = response.getOutputStream();
507
            response.setStatus(200);
508
            response.setContentType("text/xml");
509
            
510
            TypeMarshaller.marshalTypeToOutputStream(monitorList, out);
511
            
512
            return true;
513
            
514
        }
515
        
516
        return false;
517
    }
518
    
519
    /*
520
     * Parse an ISO8601 date string, returning a Date in UTC time if the string
521
     * ends in 'Z'.
522
     * 
523
     * @param fromDateStr -  the date string to be parsed
524
     * @return date -  the date object represented by the string
525
     */
526
    private Date getDateAsUTC(String fromDateStr)
527
      throws ParseException {
528

    
529
    	Date date = null;
530
    	
531
    	try {
532
    		// try the expected date format
533
        // a date format for date string arguments
534
        DateFormat dateFormat = 
535
        	new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
536

    
537
	      date = dateFormat.parse(fromDateStr);
538
      
539
    	} catch (ParseException e) {
540
    		// try the date with the UTC indicator
541
        DateFormat utcDateFormat = 
542
        	new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
543
        utcDateFormat.setTimeZone(TimeZone.getTimeZone("GMT-0"));
544
        date = utcDateFormat.parse(fromDateStr);
545
        
546
      }
547
    	
548
    	return date;
549
    }
550

    
551
		/**
552
     * Calculate the checksum 
553
     * @throws NotImplemented
554
     * @throws JiBXException
555
     * @throws IOException
556
     * @throws InvalidToken
557
     * @throws ServiceFailure
558
     * @throws NotAuthorized
559
     * @throws NotFound
560
     * @throws InvalidRequest
561
     */
562
    private void checksum(String pid) throws NotImplemented, JiBXException, IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest {
563
    	String checksumAlgorithm = "MD5";
564
        
565
        Identifier pidid = new Identifier();
566
        pidid.setValue(pid);
567
        try {
568
            checksumAlgorithm = params.get("checksumAlgorithm")[0];
569
        } catch(Exception e) {
570
            //do nothing.  default to MD5
571
        	logMetacat.warn("No algorithm specified, using default: " + checksumAlgorithm);
572
        }
573
        logMetacat.debug("getting checksum for object " + pid + " with algorithm " + checksumAlgorithm);
574
        
575
        Checksum c = MNodeService.getInstance(request).getChecksum(session, pidid, checksumAlgorithm);
576
        logMetacat.debug("got checksum " + c.getValue());
577
        response.setStatus(200);
578
        logMetacat.debug("serializing response");
579
        TypeMarshaller.marshalTypeToOutputStream(c, response.getOutputStream());
580
        logMetacat.debug("done serializing response.");
581
        
582
    }
583
    
584
	/**
585
     * handle the replicate action for MN
586
	 * @throws JiBXException 
587
	 * @throws FileUploadException 
588
	 * @throws IOException 
589
	 * @throws InvalidRequest 
590
	 * @throws ServiceFailure 
591
	 * @throws UnsupportedType 
592
	 * @throws InsufficientResources 
593
	 * @throws NotAuthorized 
594
	 * @throws NotImplemented 
595
	 * @throws IllegalAccessException 
596
	 * @throws InstantiationException 
597
     */
598
    private void replicate() throws ServiceFailure, InvalidRequest, IOException, FileUploadException, JiBXException, NotImplemented, NotAuthorized, InsufficientResources, UnsupportedType, InstantiationException, IllegalAccessException {
599

    
600
        logMetacat.debug("in POST replicate()");
601
        
602
        //parse the systemMetadata
603
        SystemMetadata sysmeta = collectSystemMetadata();
604
        
605
        String sn = multipartparams.get("sourceNode").get(0);
606
        logMetacat.debug("sourceNode: " + sn);
607
        NodeReference sourceNode = new NodeReference();
608
        sourceNode.setValue(sn);
609
        MNodeService.getInstance(request).replicate(session, sysmeta, sourceNode);
610

    
611
        response.setStatus(200);
612

    
613
    }
614

    
615
    /**
616
     * Handle the getReplica action for the MN
617
     * @param id  the identifier for the object
618
     * @throws NotFound 
619
     * @throws ServiceFailure 
620
     * @throws NotImplemented 
621
     * @throws NotAuthorized 
622
     * @throws InvalidToken 
623
     * @throws InvalidRequest 
624
     */
625
    private void getReplica(String id) 
626
        throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented, 
627
        ServiceFailure, NotFound {
628
        
629
        Identifier pid = new Identifier();
630
        pid.setValue(id);
631
        OutputStream out = null;
632
        InputStream dataBytes = null;
633
                
634
        try {
635
            // call the service
636
            dataBytes = MNodeService.getInstance(request).getReplica(session, pid);
637

    
638
            response.setContentType("application/octet-stream");
639
            response.setStatus(200);
640
            out = response.getOutputStream();
641
            // write the object to the output stream
642
            IOUtils.copyLarge(dataBytes, out);
643
            
644
        } catch (IOException e) {
645
            String msg = "There was an error writing the output: " + e.getMessage();
646
            logMetacat.error(msg);
647
            throw new ServiceFailure("2181", msg);
648
        
649
        }
650

    
651
    }
652

    
653
    /**
654
     * Get the Node information
655
     * 
656
     * @throws JiBXException
657
     * @throws IOException
658
     * @throws InvalidRequest 
659
     * @throws ServiceFailure 
660
     * @throws NotAuthorized 
661
     * @throws NotImplemented 
662
     */
663
    private void node() 
664
        throws JiBXException, IOException, NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest {
665
        
666
        Node n = MNodeService.getInstance(request).getCapabilities();
667
        
668
        response.setContentType("text/xml");
669
        response.setStatus(200);
670
        TypeMarshaller.marshalTypeToOutputStream(n, response.getOutputStream());
671
        
672
    }
673
    
674
    /**
675
     * MN_crud.describe()
676
     * http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.describe
677
     * @param pid
678
     * @throws InvalidRequest 
679
     * @throws NotImplemented 
680
     * @throws NotFound 
681
     * @throws NotAuthorized 
682
     * @throws ServiceFailure 
683
     * @throws InvalidToken 
684
     */
685
    private void describeObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest
686
    {
687
        response.setStatus(200);
688
        response.setContentType("text/xml");
689
        
690
        Identifier id = new Identifier();
691
        id.setValue(pid);
692

    
693
        DescribeResponse dr = MNodeService.getInstance(request).describe(session, id);
694
        //response.addHeader("pid", pid);
695
        response.addHeader("DataONE-Checksum", dr.getDataONE_Checksum().getAlgorithm() + "," + dr.getDataONE_Checksum().getValue());
696
        response.addHeader("Content-Length", dr.getContent_Length() + "");
697
        response.addHeader("Last-Modified", DateTimeMarshaller.serializeDateToUTC(dr.getLast_Modified()));
698
        response.addHeader("DataONE-fmtid", dr.getDataONE_ObjectFormatIdentifier().getValue());
699
        
700
    }
701
    
702
    /**
703
     * get the logs based on passed params.  Available 
704
     * See http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.getLogRecords
705
     * for more info
706
     * @throws NotImplemented 
707
     * @throws InvalidRequest 
708
     * @throws NotAuthorized 
709
     * @throws ServiceFailure 
710
     * @throws InvalidToken 
711
     * @throws IOException 
712
     * @throws JiBXException 
713
     */
714
    private void getLog() throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented, IOException, JiBXException
715
    {
716
            
717
        Date fromDate = null;
718
        Date toDate = null;
719
        Event event = null;
720
        Integer start = null;
721
        Integer count = null;
722
        
723
        try {
724
        	String fromDateS = params.get("fromDate")[0];
725
            logMetacat.debug("param fromDateS: " + fromDateS);
726
            fromDate = DateTimeMarshaller.deserializeDateToUTC(fromDateS);
727
        } catch (Exception e) {
728
        	logMetacat.warn("Could not parse fromDate: " + e.getMessage());
729
        }
730
        try {
731
        	String toDateS = params.get("toDate")[0];
732
            logMetacat.debug("param toDateS: " + toDateS);
733
            toDate = DateTimeMarshaller.deserializeDateToUTC(toDateS);
734
        } catch (Exception e) {
735
        	logMetacat.warn("Could not parse toDate: " + e.getMessage());
736
		}
737
        try {
738
        	String eventS = params.get("event")[0];
739
            event = Event.convert(eventS);
740
        } catch (Exception e) {
741
        	logMetacat.warn("Could not parse event: " + e.getMessage());
742
		}
743
        logMetacat.debug("fromDate: " + fromDate + " toDate: " + toDate);
744
        
745
        try {
746
        	start =  Integer.parseInt(params.get("start")[0]);
747
        } catch (Exception e) {
748
			logMetacat.warn("Could not parse start: " + e.getMessage());
749
		}
750
        try {
751
        	count =  Integer.parseInt(params.get("count")[0]);
752
        } catch (Exception e) {
753
			logMetacat.warn("Could not parse count: " + e.getMessage());
754
		}
755
        
756
        logMetacat.debug("calling getLogRecords");
757
        Log log = MNodeService.getInstance(request).getLogRecords(session, fromDate, toDate, event, start, count);
758
        
759
        OutputStream out = response.getOutputStream();
760
        response.setStatus(200);
761
        response.setContentType("text/xml");
762
        
763
        TypeMarshaller.marshalTypeToOutputStream(log, out);
764
        
765
    }
766
    
767
    /**
768
     * Implements REST version of DataONE CRUD API --> get
769
     * @param pid ID of data object to be read
770
     * @throws NotImplemented 
771
     * @throws InvalidRequest 
772
     * @throws NotFound 
773
     * @throws NotAuthorized 
774
     * @throws ServiceFailure 
775
     * @throws InvalidToken 
776
     * @throws IOException 
777
     * @throws JiBXException 
778
     */
779
    protected void getObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
780
        OutputStream out = null;
781
        
782
        if (pid != null) { //get a specific document                
783
            Identifier id = new Identifier();
784
            id.setValue(pid);
785
                
786
            SystemMetadata sm = MNodeService.getInstance(request).getSystemMetadata(session, id);
787
            
788
            //set the content type
789
            if (sm.getFormatId().getValue().trim().equals(
790
            		ObjectFormatCache.getInstance().getFormat("text/csv").getFormatId().getValue()))
791
            {
792
                response.setContentType("text/csv");
793
                response.setHeader("Content-Disposition", "inline; filename=" + id.getValue() + ".csv");
794
            }
795
            else if (sm.getFormatId().getValue().trim().equals(
796
            		ObjectFormatCache.getInstance().getFormat("text/plain").getFormatId().getValue()))
797
            {
798
                response.setContentType("text/plain");
799
                response.setHeader("Content-Disposition", "inline; filename=" + id.getValue() + ".txt");
800
            } 
801
            else if (sm.getFormatId().getValue().trim().equals(
802
            		ObjectFormatCache.getInstance().getFormat("application/octet-stream").getFormatId().getValue()))
803
            {
804
                response.setContentType("application/octet-stream");
805
            }
806
            else
807
            {
808
                response.setContentType("text/xml");
809
                response.setHeader("Content-Disposition", "inline; filename=" + id.getValue() + ".xml");
810
            }
811
            
812
            InputStream data = MNodeService.getInstance(request).get(session, id);
813

    
814
            out = response.getOutputStream();  
815
            IOUtils.copyLarge(data, out);
816
            
817
        }
818
        else
819
        { //call listObjects with specified params
820
            Date startTime = null;
821
            Date endTime = null;
822
            ObjectFormat objectFormat = null;
823
            boolean replicaStatus = false;
824
            int start = 0;
825
            //TODO: make the max count into a const
826
            int count = 1000;
827
            Enumeration paramlist = request.getParameterNames();
828
            while (paramlist.hasMoreElements()) 
829
            { //parse the params and make the crud call
830
                String name = (String) paramlist.nextElement();
831
                String[] value = (String[])request.getParameterValues(name);
832

    
833
                if (name.equals("startTime") && value != null)
834
                {
835
                    try
836
                    {
837
                      //startTime = dateFormat.parse(value[0]);
838
                    	startTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
839
                        //startTime = parseDateAndConvertToGMT(value[0]);
840
                    }
841
                    catch(Exception e)
842
                    {  //if we can't parse it, just don't use the startTime param
843
                        logMetacat.warn("Could not parse startTime: " + value[0]);
844
                        startTime = null;
845
                    }
846
                }
847
                else if(name.equals("endTime") && value != null)
848
                {
849
                    try
850
                    {
851
                      //endTime = dateFormat.parse(value[0]);
852
                    	endTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
853
                        //endTime = parseDateAndConvertToGMT(value[0]);
854
                    }
855
                    catch(Exception e)
856
                    {  //if we can't parse it, just don't use the endTime param
857
                        logMetacat.warn("Could not parse endTime: " + value[0]);
858
                        endTime = null;
859
                    }
860
                }
861
                else if(name.equals("objectFormat") && value != null) 
862
                {
863
                    objectFormat = ObjectFormatCache.getInstance().getFormat(value[0]);
864
                }
865
                else if(name.equals("replicaStatus") && value != null)
866
                {
867
                    if(value != null && 
868
                       value.length > 0 && 
869
                       (value[0].equals("true") || value[0].equals("TRUE") || value[0].equals("YES")))
870
                    {
871
                        replicaStatus = true;
872
                    }
873
                }
874
                else if(name.equals("start") && value != null)
875
                {
876
                    start = new Integer(value[0]).intValue();
877
                }
878
                else if(name.equals("count") && value != null)
879
                {
880
                    count = new Integer(value[0]).intValue();
881
                }
882
            }
883
            //make the crud call
884
            logMetacat.debug("session: " + session + " startTime: " + startTime +
885
                    " endtime: " + endTime + " objectFormat: " + 
886
                    objectFormat + " replicaStatus: " + replicaStatus + 
887
                    " start: " + start + " count: " + count);
888
           
889
            ObjectFormatIdentifier fmtid = null;
890
           
891
            if ( objectFormat != null ) {
892
          	 fmtid = objectFormat.getFormatId();
893
          	 
894
            }
895
            ObjectList ol = 
896
           	 MNodeService.getInstance(request).listObjects(session, startTime, endTime, 
897
               fmtid, replicaStatus, start, count);
898
           
899
            out = response.getOutputStream();  
900
            response.setStatus(200);
901
            response.setContentType("text/xml");
902
            // Serialize and write it to the output stream
903
            TypeMarshaller.marshalTypeToOutputStream(ol, out);
904
            
905
        }
906
        
907
    }
908
    
909

    
910
    /**
911
     * Retrieve System Metadata
912
     * @param pid
913
     * @throws InvalidToken
914
     * @throws ServiceFailure
915
     * @throws NotAuthorized
916
     * @throws NotFound
917
     * @throws InvalidRequest
918
     * @throws NotImplemented
919
     * @throws IOException
920
     * @throws JiBXException
921
     */
922
    protected void getSystemMetadataObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
923

    
924
        Identifier id = new Identifier();
925
        id.setValue(pid);
926
        SystemMetadata sysmeta = MNodeService.getInstance(request).getSystemMetadata(session, id);
927
        
928
        response.setContentType("text/xml");
929
        response.setStatus(200);
930
        OutputStream out = response.getOutputStream();
931
        
932
        // Serialize and write it to the output stream
933
        TypeMarshaller.marshalTypeToOutputStream(sysmeta, out);
934
   }
935
    
936
    
937
    /**
938
     * Inserts or updates the object
939
     * 
940
     * @param pid - ID of data object to be inserted or updated.  If action is update, the pid
941
     *               is the existing pid.  If insert, the pid is the new one
942
     * @throws InvalidRequest 
943
     * @throws ServiceFailure 
944
     * @throws JiBXException 
945
     * @throws NotImplemented 
946
     * @throws InvalidSystemMetadata 
947
     * @throws InsufficientResources 
948
     * @throws UnsupportedType 
949
     * @throws IdentifierNotUnique 
950
     * @throws NotAuthorized 
951
     * @throws InvalidToken 
952
     * @throws NotFound 
953
     * @throws IOException 
954
     * @throws IllegalAccessException 
955
     * @throws InstantiationException 
956
     */
957
    protected void putObject(String pid, String action) throws ServiceFailure, InvalidRequest, JiBXException, InvalidToken, NotAuthorized, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, NotFound, IOException, InstantiationException, IllegalAccessException {
958
        logMetacat.debug("putObject with pid " + pid);
959
        logMetacat.debug("Entering putObject: " + pid + "/" + action);
960
        
961
        response.setStatus(200);
962
        response.setContentType("text/xml");
963
        OutputStream out = response.getOutputStream();
964
        
965
        // Read the incoming data from its Mime Multipart encoding
966
    	Map<String, File> files = collectMultipartFiles();
967
        InputStream object = null;
968
        InputStream sysmeta = null;
969

    
970
        File smFile = files.get("sysmeta");
971
        sysmeta = new FileInputStream(smFile);
972
        File objFile = files.get("object");
973
        object = new FileInputStream(objFile);
974
        
975
        if (action.equals(FUNCTION_NAME_INSERT)) { 
976
            // handle inserts
977
            logMetacat.debug("Commence creation...");
978
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
979

    
980
            Identifier id = new Identifier();
981
            id.setValue(pid);
982
            logMetacat.debug("creating object with pid " + pid);
983
            Identifier rId = MNodeService.getInstance(request).create(session, id, object, smd);
984
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
985
            
986
        } else if (action.equals(FUNCTION_NAME_UPDATE)) {
987
        	// handle updates
988
        	
989
            // construct pids
990
            Identifier newPid = new Identifier();
991
            try {
992
            	String newPidString = multipartparams.get("newPid").get(0);
993
                newPid.setValue(newPidString);
994
            } catch (Exception e) {
995
				logMetacat.warn("newPid not given");
996
			}
997
            
998
            Identifier obsoletedPid = new Identifier();
999
            obsoletedPid.setValue(pid);
1000
           
1001
            logMetacat.debug("Commence update...");
1002
            
1003
            // get the systemmetadata object
1004
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1005

    
1006
            Identifier rId = MNodeService.getInstance(request).update(session, obsoletedPid, object, newPid, smd);
1007
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1008
        } else {
1009
            throw new InvalidRequest("1000", "Operation must be create or update.");
1010
        }
1011
            
1012
            
1013
    }
1014

    
1015
    /**
1016
     * Handle delete 
1017
     * @param pid ID of data object to be deleted
1018
     * @throws IOException
1019
     * @throws InvalidRequest 
1020
     * @throws NotImplemented 
1021
     * @throws NotFound 
1022
     * @throws NotAuthorized 
1023
     * @throws ServiceFailure 
1024
     * @throws InvalidToken 
1025
     * @throws JiBXException 
1026
     */
1027
    private void deleteObject(String pid) throws IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest, JiBXException 
1028
    {
1029

    
1030
        OutputStream out = response.getOutputStream();
1031
        response.setStatus(200);
1032
        response.setContentType("text/xml");
1033

    
1034
        Identifier id = new Identifier();
1035
        id.setValue(pid);
1036

    
1037
        logMetacat.debug("Calling delete");
1038
        MNodeService.getInstance(request).delete(session, id);
1039
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1040
        
1041
    }    
1042
    
1043
    /**
1044
     * set the access perms on a document
1045
     * @throws JiBXException 
1046
     * @throws InvalidRequest 
1047
     * @throws NotImplemented 
1048
     * @throws NotAuthorized 
1049
     * @throws NotFound 
1050
     * @throws ServiceFailure 
1051
     * @throws InvalidToken 
1052
     * @throws IllegalAccessException 
1053
     * @throws InstantiationException 
1054
     * @throws IOException 
1055
     * @throws SAXException 
1056
     * @throws ParserConfigurationException 
1057
     */
1058
    protected void setAccess(String pid) throws JiBXException, InvalidToken, ServiceFailure, NotFound, NotAuthorized, NotImplemented, InvalidRequest, IOException, InstantiationException, IllegalAccessException, ParserConfigurationException, SAXException
1059
    {
1060
    
1061
        //String pid = params.get("pid")[0];
1062
        Identifier id = new Identifier();
1063
        id.setValue(pid);
1064
        
1065
        AccessPolicy accessPolicy = collectAccessPolicy();
1066
        MNodeService.getInstance(request).setAccessPolicy(session, id, accessPolicy);
1067
        
1068
        
1069
    }
1070

    
1071
	protected SynchronizationFailed collectSynchronizationFailed() throws IOException, ServiceFailure, InvalidRequest, JiBXException, InstantiationException, IllegalAccessException, ParserConfigurationException, SAXException  {
1072
		
1073
		// Read the incoming data from its Mime Multipart encoding
1074
		logMetacat.debug("Disassembling MIME multipart form");
1075
		InputStream sf = null;
1076
	
1077
		// handle MMP inputs
1078
		File tmpDir = getTempDirectory();
1079
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
1080
		MultipartRequestResolver mrr = 
1081
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
1082
		MultipartRequest mr = null;
1083
		try {
1084
			mr = mrr.resolveMultipart(request);
1085
		} catch (Exception e) {
1086
			throw new ServiceFailure("2161", 
1087
					"Could not resolve multipart: " + e.getMessage());
1088
		}
1089
		logMetacat.debug("resolved multipart request");
1090
		Map<String, File> files = mr.getMultipartFiles();
1091
		if (files == null || files.keySet() == null) {
1092
			throw new InvalidRequest("2163",
1093
					"must have multipart file with name 'message'");
1094
		}
1095
		logMetacat.debug("got multipart files");
1096
	
1097
		multipartparams = mr.getMultipartParameters();
1098
	
1099
		File sfFile = files.get("message");
1100
		if (sfFile == null) {
1101
			throw new InvalidRequest("2163",
1102
					"Missing the required file-part 'message' from the multipart request.");
1103
		}
1104
		logMetacat.debug("sfFile: " + sfFile.getAbsolutePath());
1105
		sf = new FileInputStream(sfFile);
1106
	
1107
		SynchronizationFailed syncFailed = (SynchronizationFailed) ExceptionHandler.deserializeXml(sf, "Error deserializing exception");
1108
		return syncFailed;
1109
	}
1110

    
1111
}
(8-8/9)