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: 2013-08-22 16:04:43 -0700 (Thu, 22 Aug 2013) $'
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.client.formats.ObjectFormatInfo;
50
import org.dataone.mimemultipart.MultipartRequest;
51
import org.dataone.mimemultipart.MultipartRequestResolver;
52
import org.dataone.service.exceptions.BaseException;
53
import org.dataone.service.exceptions.IdentifierNotUnique;
54
import org.dataone.service.exceptions.InsufficientResources;
55
import org.dataone.service.exceptions.InvalidRequest;
56
import org.dataone.service.exceptions.InvalidSystemMetadata;
57
import org.dataone.service.exceptions.InvalidToken;
58
import org.dataone.service.exceptions.NotAuthorized;
59
import org.dataone.service.exceptions.NotFound;
60
import org.dataone.service.exceptions.NotImplemented;
61
import org.dataone.service.exceptions.ServiceFailure;
62
import org.dataone.service.exceptions.SynchronizationFailed;
63
import org.dataone.service.exceptions.UnsupportedType;
64
import org.dataone.service.types.v1.Checksum;
65
import org.dataone.service.types.v1.DescribeResponse;
66
import org.dataone.service.types.v1.Event;
67
import org.dataone.service.types.v1.Identifier;
68
import org.dataone.service.types.v1.Log;
69
import org.dataone.service.types.v1.MonitorList;
70
import org.dataone.service.types.v1.Node;
71
import org.dataone.service.types.v1.NodeReference;
72
import org.dataone.service.types.v1.ObjectFormat;
73
import org.dataone.service.types.v1.ObjectFormatIdentifier;
74
import org.dataone.service.types.v1.ObjectList;
75
import org.dataone.service.types.v1.Permission;
76
import org.dataone.service.types.v1.Subject;
77
import org.dataone.service.types.v1.SystemMetadata;
78
import org.dataone.service.types.v1_1.QueryEngineDescription;
79
import org.dataone.service.types.v1_1.QueryEngineList;
80
import org.dataone.service.util.Constants;
81
import org.dataone.service.util.DateTimeMarshaller;
82
import org.dataone.service.util.ExceptionHandler;
83
import org.dataone.service.util.TypeMarshaller;
84
import org.jibx.runtime.JiBXException;
85
import org.xml.sax.SAXException;
86

    
87
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeInputStream;
88
import edu.ucsb.nceas.metacat.dataone.MNodeService;
89
import edu.ucsb.nceas.metacat.properties.PropertyService;
90
import edu.ucsb.nceas.metacat.util.DeleteOnCloseFileInputStream;
91
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
92

    
93
/**
94
 * MN REST service implementation handler
95
 * 
96
 * ******************
97
 * MNCore -- DONE
98
 * 		ping() - GET /d1/mn/monitor/ping
99
 * 		log() - GET /d1/mn/log
100
 * 		**getObjectStatistics() - GET /d1/mn/monitor/object
101
 * 		getOperationsStatistics - GET /d1/mn/monitor/event
102
 * 		**getStatus - GET /d1/mn/monitor/status
103
 * 		getCapabilities() - GET /d1/mn/ and /d1/mn/node
104
 * 	
105
 * 	MNRead -- DONE
106
 * 		get() - GET /d1/mn/object/PID
107
 * 		getSystemMetadata() - GET /d1/mn/meta/PID
108
 * 		describe() - HEAD /d1/mn/object/PID
109
 * 		getChecksum() - GET /d1/mn/checksum/PID
110
 * 		listObjects() - GET /d1/mn/object
111
 * 		synchronizationFailed() - POST /d1/mn/error
112
 * 	
113
 * 	MNAuthorization -- DONE
114
 * 		isAuthorized() - GET /d1/mn/isAuthorized/PID
115
 * 		setAccessPolicy() - PUT /d1/mn/accessRules/PID
116
 * 		
117
 * 	MNStorage - DONE
118
 * 		create() - POST /d1/mn/object/PID
119
 * 		update() - PUT /d1/mn/object/PID
120
 * 		delete() - DELETE /d1/mn/object/PID
121
 * 		archive() - PUT /d1/mn/archive/PID
122

    
123
 *    systemMetadataChanged() - POST /dirtySystemMetadata/PID
124
 * 	
125
 * 	MNReplication
126
 * 		replicate() - POST /d1/mn/replicate
127
 *    getReplica() - GET /d1/mn/replica
128
 * 
129
 * ******************
130
 * @author leinfelder
131
 *
132
 */
133
public class MNResourceHandler extends D1ResourceHandler {
134

    
135
    // MN-specific API Resources
136
    protected static final String RESOURCE_MONITOR = "monitor";
137
    protected static final String RESOURCE_REPLICATE = "replicate";
138
    protected static final String RESOURCE_REPLICAS = "replica";
139
    protected static final String RESOURCE_NODE = "node";
140
    protected static final String RESOURCE_ERROR = "error";
141
    protected static final String RESOURCE_META_CHANGED = "dirtySystemMetadata";
142
    protected static final String RESOURCE_GENERATE_ID = "generate";
143
    protected static final String RESOURCE_PUBLISH = "publish";
144
    protected static final String RESOURCE_PACKAGE = "package";
145
    protected static final String RESOURCE_VIEWS = "views";
146

    
147

    
148
    
149
    // shared executor
150
	private static ExecutorService executor = null;
151

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

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

    
220
                if (resource.startsWith(RESOURCE_NODE)) {
221
                    // node response
222
                    node();
223
                    status = true;
224
                } else if (resource.startsWith(RESOURCE_IS_AUTHORIZED)) {
225
                    if (httpVerb == GET) {
226
                    	// after the command
227
                        extra = parseTrailing(resource, RESOURCE_IS_AUTHORIZED);
228
	                	// check the access rules
229
	                    isAuthorized(extra);
230
	                    status = true;
231
	                    logMetacat.debug("done getting access");
232
                    }
233
                } else if (resource.startsWith(RESOURCE_META)) {
234
                    logMetacat.debug("Using resource 'meta'");
235
                    // get
236
                    if (httpVerb == GET) {
237
                    	// after the command
238
                        extra = parseTrailing(resource, RESOURCE_META);
239
                        getSystemMetadataObject(extra);
240
                        status = true;
241
                    }
242
                    
243
                } else if (resource.startsWith(RESOURCE_OBJECTS)) {
244
                    logMetacat.debug("Using resource 'object'");
245
                    // after the command
246
                    extra = parseTrailing(resource, RESOURCE_OBJECTS);
247
                    logMetacat.debug("objectId: " + extra);
248
                    logMetacat.debug("verb:" + httpVerb);
249

    
250
                    if (httpVerb == GET) {
251
                        getObject(extra);
252
                        status = true;
253
                    } else if (httpVerb == POST) {
254
                    	// part of the params, not the URL
255
                        putObject(null, FUNCTION_NAME_INSERT);
256
                        status = true;
257
                    } else if (httpVerb == PUT) {
258
                        putObject(extra, FUNCTION_NAME_UPDATE);
259
                        status = true;
260
                    } else if (httpVerb == DELETE) {
261
                        deleteObject(extra);
262
                        status = true;
263
                    } else if (httpVerb == HEAD) {
264
                        describeObject(extra);
265
                        status = true;
266
                    }
267
                  
268
                } else if (resource.startsWith(RESOURCE_LOG)) {
269
                    logMetacat.debug("Using resource 'log'");
270
                    // handle log events
271
                    if (httpVerb == GET) {
272
                        getLog();
273
                        status = true;
274
                    }
275
                } else if (resource.startsWith(Constants.RESOURCE_ARCHIVE)) {
276
                    logMetacat.debug("Using resource " + Constants.RESOURCE_ARCHIVE);
277
                    // handle archive events
278
                    if (httpVerb == PUT) {
279
                        extra = parseTrailing(resource, Constants.RESOURCE_ARCHIVE);
280
                        archive(extra);
281
                        status = true;
282
                    }
283
                } else if (resource.startsWith(Constants.RESOURCE_CHECKSUM)) {
284
                    logMetacat.debug("Using resource 'checksum'");
285
                    // handle checksum requests
286
                    if (httpVerb == GET) {
287
                    	// after the command
288
                        extra = parseTrailing(resource, Constants.RESOURCE_CHECKSUM);
289
                        checksum(extra);
290
                        status = true;
291
                    }
292
                } else if (resource.startsWith(RESOURCE_MONITOR)) {
293
                    // there are various parts to monitoring
294
                    if (httpVerb == GET) {
295
                    	// after the command
296
                        extra = parseTrailing(resource, RESOURCE_MONITOR);
297
                        
298
                        // ping
299
                        if (extra.toLowerCase().equals("ping")) {
300
                            logMetacat.debug("processing ping request");
301
                            Date result = MNodeService.getInstance(request).ping();
302
                            // TODO: send to output	
303
                            status = true;
304
                            
305
                        } else {
306
	                    	// health monitoring calls
307
	                        status = monitor(extra);
308
                        }
309
                        
310
                    }
311
                } else if (resource.startsWith(RESOURCE_REPLICATE)) {
312
                	if (httpVerb == POST) {
313
	                    logMetacat.debug("processing replicate request");
314
	                    replicate();
315
	                    status = true;
316
                	}
317
                } else if (resource.startsWith(RESOURCE_ERROR)) {
318
	                // sync error
319
	                if (httpVerb == POST) {
320
	                    syncError();
321
	                    status = true;
322
	                }
323
                } else if (resource.startsWith(RESOURCE_META_CHANGED)) {
324
                    // system metadata changed
325
                    if (httpVerb == POST) {
326
                        systemMetadataChanged();
327
                        status = true;
328
                    }
329
                } else if (resource.startsWith(RESOURCE_REPLICAS)) {
330
                    // get replica
331
                    if (httpVerb == GET) {
332
                        extra = parseTrailing(resource, RESOURCE_REPLICAS);
333
                        getReplica(extra);
334
                        status = true;
335
                    }
336
                } else if (resource.startsWith(RESOURCE_QUERY)) {
337
	                logMetacat.debug("Using resource " + RESOURCE_QUERY);
338
	                // after the command
339
	                extra = parseTrailing(resource, RESOURCE_QUERY);
340
	                logMetacat.debug("query extra: " + extra);
341

    
342
	                String engine = null;
343
	                String query = null;
344

    
345
	                if (extra != null) {
346
		                // get the engine
347
		                int engineIndex = extra.length();
348
		                if (extra.indexOf("/") > -1) {
349
		                	engineIndex = extra.indexOf("/");
350
		                }
351
		                engine = extra.substring(0, engineIndex);
352
		                logMetacat.debug("query engine: " + engine);
353
		                
354
		                // get the query if it is there
355
		                query = extra.substring(engineIndex, extra.length());
356
		                if (query != null && query.length() == 0) {
357
		                	query = null;
358
		                } else {
359
		                	if (query.startsWith("/")) {
360
		                		query = query.substring(1);
361
		                    }
362
		                }
363
		                logMetacat.debug("query: " + query);
364

    
365
	                }
366
	                logMetacat.debug("verb:" + httpVerb);
367
	                if (httpVerb == GET) {
368
	                    doQuery(engine, query);
369
	                    status = true;
370
	                }
371
                } else if (resource.startsWith(RESOURCE_GENERATE_ID)) {
372
                	// generate an id
373
                    if (httpVerb == POST) {
374
                        generateIdentifier();
375
                        status = true;
376
                    }
377
                } else if (resource.startsWith(RESOURCE_PUBLISH)) {
378
                    logMetacat.debug("Using resource: " + RESOURCE_PUBLISH);
379
                    // PUT
380
                    if (httpVerb == PUT) {
381
                    	// after the command
382
                        extra = parseTrailing(resource, RESOURCE_PUBLISH);
383
                        publish(extra);
384
                        status = true;
385
                    }  
386
                } else if (resource.startsWith(RESOURCE_PACKAGE)) {
387
                    logMetacat.debug("Using resource: " + RESOURCE_PACKAGE);
388
                    // get
389
                    if (httpVerb == GET) {
390
                    	// after the command
391
                        extra = parseTrailing(resource, RESOURCE_PACKAGE);
392
                        getPackage(extra);
393
                        status = true;
394
                    }  
395
                } else if (resource.startsWith(RESOURCE_VIEWS)) {
396
	                logMetacat.debug("Using resource " + RESOURCE_VIEWS);
397
	                // after the command
398
	                extra = parseTrailing(resource, RESOURCE_VIEWS);
399
	                logMetacat.debug("view extra: " + extra);
400

    
401
	                String format = null;
402
	                String pid = null;
403

    
404
	                if (extra != null) {
405
		                // get the format
406
		                int formatIndex = extra.length();
407
		                if (extra.indexOf("/") > -1) {
408
		                	formatIndex = extra.indexOf("/");
409
		                }
410
		                format = extra.substring(0, formatIndex);
411
		                logMetacat.debug("view format: " + format);
412
		                
413
		                // get the pid if it is there
414
		                pid = extra.substring(formatIndex, extra.length());
415
		                if (pid != null && pid.length() == 0) {
416
		                	pid = null;
417
		                } else {
418
		                	if (pid.startsWith("/")) {
419
		                		pid = pid.substring(1);
420
		                    }
421
		                }
422
		                logMetacat.debug("pid: " + pid);
423

    
424
	                }
425
	                logMetacat.debug("verb:" + httpVerb);
426
	                if (httpVerb == GET) {
427
	                    doViews(format, pid);
428
	                    status = true;
429
	                }
430
                } 
431
                
432
                if (!status) {
433
                	throw new ServiceFailure("0000", "Unknown error, status = " + status);
434
                }
435
            } else {
436
            	throw new InvalidRequest("0000", "No resource matched for " + resource);
437
            }
438
        } catch (BaseException be) {
439
        	// report Exceptions as clearly as possible
440
        	OutputStream out = null;
441
			try {
442
				out = response.getOutputStream();
443
			} catch (IOException e) {
444
				logMetacat.error("Could not get output stream from response", e);
445
			}
446
            serializeException(be, out);
447
        } catch (Exception e) {
448
            // report Exceptions as clearly and generically as possible
449
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
450
        	OutputStream out = null;
451
			try {
452
				out = response.getOutputStream();
453
			} catch (IOException ioe) {
454
				logMetacat.error("Could not get output stream from response", ioe);
455
			}
456
			ServiceFailure se = new ServiceFailure("0000", e.getMessage());
457
            serializeException(se, out);
458
        }
459
    }
460
    
461

    
462
    private void doQuery(String engine, String query) {
463
    	
464
		OutputStream out = null;
465

    
466
    	try {
467
    		// NOTE: we set the session explicitly for the MNode instance since these methods do not provide a parameter			
468
	    	if (engine == null) {
469
	    		// just looking for list of engines
470
	    		MNodeService mnode = MNodeService.getInstance(request);
471
    			mnode.setSession(session);
472
	    		QueryEngineList qel = mnode.listQueryEngines();
473
	    		response.setContentType("text/xml");
474
	            response.setStatus(200);
475
	            out = response.getOutputStream();
476
	            TypeMarshaller.marshalTypeToOutputStream(qel, out);
477
	            return;
478
	    	} else {
479
	    		if (query != null) {
480
	    			MNodeService mnode = MNodeService.getInstance(request);
481
	    			mnode.setSession(session);
482
	    			InputStream stream = mnode.query(engine, query);
483

    
484
	    			// set the content-type if we have it from the implementation
485
	    			if (stream instanceof ContentTypeInputStream) {
486
		    			//response.setContentType("application/octet-stream");
487
		    			//response.setContentType("text/xml");
488
	    				response.setContentType(((ContentTypeInputStream) stream).getContentType());
489
	    			}
490
	                response.setStatus(200);
491
	                out = response.getOutputStream();
492
	                // write the results to the output stream
493
	                IOUtils.copyLarge(stream, out);
494
	                return;
495
	    		} else {
496
	    			MNodeService mnode = MNodeService.getInstance(request);
497
	    			mnode.setSession(session);
498
	    			QueryEngineDescription qed = mnode.getQueryEngineDescription(engine);
499
	    			response.setContentType("text/xml");
500
		            response.setStatus(200);
501
		            out = response.getOutputStream();
502
		            TypeMarshaller.marshalTypeToOutputStream(qed, out);
503
		            return;
504
	    		}
505
	    	}
506
	        
507
	        
508
    	} catch (BaseException be) {
509
        	// report Exceptions as clearly as possible
510
			try {
511
				out = response.getOutputStream();
512
			} catch (IOException e) {
513
				logMetacat.error("Could not get output stream from response", e);
514
			}
515
            serializeException(be, out);
516
        } catch (Exception e) {
517
            // report Exceptions as clearly and generically as possible
518
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
519
			try {
520
				out = response.getOutputStream();
521
			} catch (IOException ioe) {
522
				logMetacat.error("Could not get output stream from response", ioe);
523
			}
524
			ServiceFailure se = new ServiceFailure("0000", e.getMessage());
525
            serializeException(se, out);
526
        }
527
    }
528
    
529
    private void doViews(String format, String pid) {
530
    	
531
		OutputStream out = null;
532
		MNodeService mnode = MNodeService.getInstance(request);
533

    
534
    	try {
535
    		// get a list of views
536
    		if (pid != null) {
537
    			Identifier identifier = new Identifier();
538
    			identifier.setValue(pid);
539
				InputStream stream = mnode.getView(session, identifier, format);
540

    
541
    			// set the content-type if we have it from the implementation
542
    			if (stream instanceof ContentTypeInputStream) {
543
    				response.setContentType(((ContentTypeInputStream) stream).getContentType());
544
    			}
545
                response.setStatus(200);
546
                out = response.getOutputStream();
547
                // write the results to the output stream
548
                IOUtils.copyLarge(stream, out);
549
                return;
550
    		} else {
551
    			// TODO: list the registered views
552
                BaseException ni = new NotImplemented("9999", "MN.listViews() is not implemented at this node");
553
				throw ni;
554
    		}
555
	    	
556
	        
557
    	} catch (BaseException be) {
558
        	// report Exceptions as clearly as possible
559
			try {
560
				out = response.getOutputStream();
561
			} catch (IOException e) {
562
				logMetacat.error("Could not get output stream from response", e);
563
			}
564
            serializeException(be, out);
565
        } catch (Exception e) {
566
            // report Exceptions as clearly and generically as possible
567
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
568
			try {
569
				out = response.getOutputStream();
570
			} catch (IOException ioe) {
571
				logMetacat.error("Could not get output stream from response", ioe);
572
			}
573
			ServiceFailure se = new ServiceFailure("0000", e.getMessage());
574
            serializeException(se, out);
575
        }
576
    }
577
    
578
    /**
579
     * Handles notification of system metadata changes for the given identifier
580
     * 
581
     * @param id  the identifier for the object
582
     * @throws InvalidToken 
583
     * @throws InvalidRequest 
584
     * @throws NotAuthorized 
585
     * @throws ServiceFailure 
586
     * @throws NotImplemented 
587
     */
588
    private void systemMetadataChanged() 
589
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
590
        InvalidToken {
591

    
592
        long serialVersion = 0L;
593
        String serialVersionStr = null;
594
        Date dateSysMetaLastModified = null;
595
        String dateSysMetaLastModifiedStr = null;
596
        Identifier pid = null;
597
        
598
        // mkae sure we have the multipart params
599
        try {
600
			initMultipartParams();
601
		} catch (Exception e1) {
602
			throw new ServiceFailure("1333", "Could not collect the multipart params for the request");
603
		}
604
        
605
        // get the pid
606
        try {
607
        	String id = multipartparams.get("pid").get(0);
608
        	pid = new Identifier();
609
            pid.setValue(id);            
610
        } catch (NullPointerException e) {
611
            String msg = "The 'pid' must be provided as a parameter and was not.";
612
            logMetacat.error(msg);
613
            throw new InvalidRequest("1334", msg);
614
        }      
615
        
616
        // get the serialVersion
617
        try {
618
            serialVersionStr = multipartparams.get("serialVersion").get(0);
619
            serialVersion = new Long(serialVersionStr).longValue();
620
            
621
        } catch (NullPointerException e) {
622
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
623
            logMetacat.error(msg);
624
            throw new InvalidRequest("1334", msg);
625
            
626
        }       
627
        
628
        // get the dateSysMetaLastModified
629
        try {
630
            dateSysMetaLastModifiedStr = multipartparams.get("dateSysMetaLastModified").get(0);
631
            dateSysMetaLastModified = DateTimeMarshaller.deserializeDateToUTC(dateSysMetaLastModifiedStr);
632
            
633
        } catch (NullPointerException e) {
634
            String msg = 
635
                "The 'dateSysMetaLastModified' must be provided as a " + 
636
                "parameter and was not, or was an invalid representation of the timestamp.";
637
            logMetacat.error(msg);
638
            throw new InvalidRequest("1334", msg);
639
            
640
        }       
641
        
642
        // call the service
643
        MNodeService.getInstance(request).systemMetadataChanged(session, pid, serialVersion, dateSysMetaLastModified);
644
        response.setStatus(200);
645
    }
646
    
647
    /**
648
     * Handles identifier generation calls
649
     * 
650
     * @throws InvalidRequest 
651
     * @throws NotImplemented 
652
     * @throws NotAuthorized 
653
     * @throws ServiceFailure 
654
     * @throws InvalidToken 
655
     * @throws IOException 
656
     * @throws JiBXException 
657
     */
658
    private void generateIdentifier() throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, IOException, JiBXException {
659
        
660
        // make sure we have the multipart params
661
        try {
662
			initMultipartParams();
663
		} catch (Exception e1) {
664
			throw new ServiceFailure("1333", "Could not collect the multipart params for the request");
665
		}
666
        
667
        // get the scheme
668
		String scheme = null;
669
        try {
670
        	scheme = multipartparams.get("scheme").get(0);	           
671
        } catch (NullPointerException e) {
672
            String msg = "The 'scheme' parameter was not provided, using default";
673
            logMetacat.warn(msg);
674
        }
675
        
676
        // get the fragment
677
		String fragment = null;
678
        try {
679
        	fragment = multipartparams.get("fragment").get(0);	           
680
        } catch (NullPointerException e) {
681
            String msg = "The 'fragment' parameter was not provided, using default";
682
            logMetacat.warn(msg);
683
        }
684

    
685
        // call the service
686
        Identifier identifier = MNodeService.getInstance(request).generateIdentifier(session, scheme, fragment);
687
        response.setStatus(200);
688
        response.setContentType("text/xml");
689
        OutputStream out = response.getOutputStream();
690
        TypeMarshaller.marshalTypeToOutputStream(identifier, out);
691
    }
692

    
693
    /**
694
     * Checks the access policy
695
     * @param id
696
     * @return
697
     * @throws ServiceFailure
698
     * @throws InvalidToken
699
     * @throws NotFound
700
     * @throws NotAuthorized
701
     * @throws NotImplemented
702
     * @throws InvalidRequest
703
     */
704
    private boolean isAuthorized(String id) throws ServiceFailure, InvalidToken, NotFound, NotAuthorized, NotImplemented, InvalidRequest {
705
		Identifier pid = new Identifier();
706
		pid.setValue(id);
707
		Permission permission = null;
708
		try {
709
			String perm = params.get("action")[0];
710
			permission = Permission.convert(perm);
711
		} catch (Exception e) {
712
			logMetacat.warn("No permission specified");
713
		}
714
		boolean result = MNodeService.getInstance(request).isAuthorized(session, pid, permission);
715
		response.setStatus(200);
716
		response.setContentType("text/xml");
717
		return result;
718
    }
719
    
720
    /**
721
     * Processes failed synchronization message
722
     * @throws NotImplemented
723
     * @throws ServiceFailure
724
     * @throws NotAuthorized
725
     * @throws InvalidRequest
726
     * @throws JiBXException
727
     * @throws IllegalAccessException 
728
     * @throws InstantiationException 
729
     * @throws IOException 
730
     */
731
    private void syncError() throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, JiBXException, IOException, InstantiationException, IllegalAccessException {
732
    	SynchronizationFailed syncFailed = null;
733
		try {
734
			syncFailed = collectSynchronizationFailed();
735
		} catch (ParserConfigurationException e) {
736
			throw new ServiceFailure("2161", e.getMessage());
737
		} catch (SAXException e) {
738
			throw new ServiceFailure("2161", e.getMessage());
739
		}
740
		
741
		MNodeService.getInstance(request).synchronizationFailed(session, syncFailed);
742
    }
743
    
744

    
745
    /**
746
     * Handles the monitoring resources
747
     * @return
748
     * @throws NotFound
749
     * @throws ParseException
750
     * @throws NotImplemented
751
     * @throws ServiceFailure
752
     * @throws NotAuthorized
753
     * @throws InvalidRequest
754
     * @throws InsufficientResources
755
     * @throws UnsupportedType
756
     * @throws IOException
757
     * @throws JiBXException
758
     */
759
    private boolean monitor(String pathInfo) 
760
      throws NotFound, ParseException, NotImplemented, ServiceFailure, 
761
      NotAuthorized, InvalidRequest, InsufficientResources, UnsupportedType, 
762
      IOException, JiBXException {
763
    	logMetacat.debug("processing monitor request");
764
        
765
        logMetacat.debug("verb is GET");
766
        logMetacat.debug("pathInfo is " + pathInfo);
767
        
768
        if (pathInfo.toLowerCase().equals("status")) {
769
            logMetacat.debug("processing status request");
770
            // TODO: implement in MNCore
771
            //MNodeService.getInstance().getStatus();
772
            return false;
773
            
774
        } else if (pathInfo.toLowerCase().equals("object")) {
775
            logMetacat.debug("processing object request");
776
            Identifier pid = null;
777
            ObjectFormat format = null;
778
            if (params.containsKey("format")) {
779
                String f = params.get("format")[0];
780
                format = ObjectFormatCache.getInstance().getFormat(f);
781
            }
782
            if (params.containsKey("pid")) {
783
                String id = params.get("pid")[0];
784
                pid = new Identifier();
785
                pid.setValue(id);
786
            }
787
            
788
            // TODO: implement in MNCore
789
            //ObjectStatistics objectStats = MNodeService.getInstance().getObjectStatistics(format, pid);
790
            return false;
791
            
792
        } else if (pathInfo.toLowerCase().equals("event")) {
793
            logMetacat.debug("processing event request");
794
            ObjectFormatIdentifier fmtid = null;
795
            String fromDateStr = null;
796
            Date fromDate = null;
797
            String toDateStr = null;
798
            Date toDate = null;
799
            String requestor = null;
800
            Subject subject = null;
801
            String eventName = null;
802
            Event event = null;
803

    
804
            if (params.containsKey("formatId")) {
805
                String f = params.get("formatId")[0];
806
                fmtid = ObjectFormatCache.getInstance().getFormat(f).getFormatId();
807
            }
808
            
809
            if (params.containsKey("fromDate")) {
810
                fromDateStr = params.get("fromDate")[0];
811
                fromDate = getDateAsUTC(fromDateStr);
812
            }
813
            
814
            if (params.containsKey("toDate")) {
815
              toDateStr = params.get("toDate")[0];
816
              toDate = getDateAsUTC(toDateStr);
817
            }
818
            
819
            if (params.containsKey("requestor")) {
820
            	requestor = params.get("requestor")[0];
821
            	subject = new Subject();
822
            	subject.setValue(requestor);
823
            }
824
            
825
            if (params.containsKey("event")) {
826
            	eventName = params.get("event")[0];
827
                event = Event.convert(eventName);
828
            }
829
            
830
            MonitorList monitorList = MNodeService.getInstance(request).getOperationStatistics(session, fromDate, toDate, subject, event, fmtid);
831
            
832
            OutputStream out = response.getOutputStream();
833
            response.setStatus(200);
834
            response.setContentType("text/xml");
835
            
836
            TypeMarshaller.marshalTypeToOutputStream(monitorList, out);
837
            
838
            return true;
839
            
840
        }
841
        
842
        return false;
843
    }
844
    
845
    /*
846
     * Parse an ISO8601 date string, returning a Date in UTC time if the string
847
     * ends in 'Z'.
848
     * 
849
     * @param fromDateStr -  the date string to be parsed
850
     * @return date -  the date object represented by the string
851
     */
852
    private Date getDateAsUTC(String fromDateStr)
853
      throws ParseException {
854

    
855
    	Date date = null;
856
    	
857
    	try {
858
    		// try the expected date format
859
        // a date format for date string arguments
860
        DateFormat dateFormat = 
861
        	new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
862

    
863
	      date = dateFormat.parse(fromDateStr);
864
      
865
    	} catch (ParseException e) {
866
    		// try the date with the UTC indicator
867
        DateFormat utcDateFormat = 
868
        	new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
869
        utcDateFormat.setTimeZone(TimeZone.getTimeZone("GMT-0"));
870
        date = utcDateFormat.parse(fromDateStr);
871
        
872
      }
873
    	
874
    	return date;
875
    }
876

    
877
		/**
878
     * Calculate the checksum 
879
     * @throws NotImplemented
880
     * @throws JiBXException
881
     * @throws IOException
882
     * @throws InvalidToken
883
     * @throws ServiceFailure
884
     * @throws NotAuthorized
885
     * @throws NotFound
886
     * @throws InvalidRequest
887
     */
888
    private void checksum(String pid) throws NotImplemented, JiBXException, IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest {
889
    	String checksumAlgorithm = "MD5";
890
    	try {
891
    		checksumAlgorithm = PropertyService.getProperty("dataone.checksumAlgorithm.default");
892
        } catch(Exception e) {
893
        	logMetacat.warn("Could not lookup configured default checksum algorithm, using: " + checksumAlgorithm);
894
        }    
895
        
896
        Identifier pidid = new Identifier();
897
        pidid.setValue(pid);
898
        try {
899
            checksumAlgorithm = params.get("checksumAlgorithm")[0];
900
        } catch(Exception e) {
901
            //do nothing.  use the default
902
        	logMetacat.warn("No algorithm specified, using default: " + checksumAlgorithm);
903
        }
904
        logMetacat.debug("getting checksum for object " + pid + " with algorithm " + checksumAlgorithm);
905
        
906
        Checksum c = MNodeService.getInstance(request).getChecksum(session, pidid, checksumAlgorithm);
907
        logMetacat.debug("got checksum " + c.getValue());
908
        response.setStatus(200);
909
        logMetacat.debug("serializing response");
910
        TypeMarshaller.marshalTypeToOutputStream(c, response.getOutputStream());
911
        logMetacat.debug("done serializing response.");
912
        
913
    }
914
    
915
	/**
916
     * handle the replicate action for MN
917
	 * @throws JiBXException 
918
	 * @throws FileUploadException 
919
	 * @throws IOException 
920
	 * @throws InvalidRequest 
921
	 * @throws ServiceFailure 
922
	 * @throws UnsupportedType 
923
	 * @throws InsufficientResources 
924
	 * @throws NotAuthorized 
925
	 * @throws NotImplemented 
926
	 * @throws IllegalAccessException 
927
	 * @throws InstantiationException 
928
	 * @throws InvalidToken 
929
     */
930
    private void replicate() 
931
        throws ServiceFailure, InvalidRequest, IOException, FileUploadException, 
932
        JiBXException, NotImplemented, NotAuthorized, InsufficientResources, 
933
        UnsupportedType, InstantiationException, IllegalAccessException, InvalidToken {
934

    
935
        logMetacat.debug("in POST replicate()");
936
        
937
        // somewhat unorthodox, but the call is asynchronous and we'd like to return this info sooner
938
        boolean allowed = false;
939
        if (session == null) {
940
        	String msg = "No session was provided.";
941
            NotAuthorized failure = new NotAuthorized("2152", msg);
942
        	throw failure;
943
        } else {
944
        	allowed = MNodeService.getInstance(request).isAdminAuthorized(session);
945
        	if (!allowed) {
946
        		String msg = "User is not an admin user";
947
                NotAuthorized failure = new NotAuthorized("2152", msg);
948
            	throw failure;
949
        	}
950
        }
951
        
952
        //parse the systemMetadata
953
        final SystemMetadata sysmeta = collectSystemMetadata();
954
        
955
        String sn = multipartparams.get("sourceNode").get(0);
956
        logMetacat.debug("sourceNode: " + sn);
957
        final NodeReference sourceNode = new NodeReference();
958
        sourceNode.setValue(sn);
959
        
960
        // run it in a thread to avoid connection timeout
961
        Runnable runner = new Runnable() {
962
			@Override
963
			public void run() {
964
				try {
965
			        MNodeService.getInstance(request).replicate(session, sysmeta, sourceNode);
966
				} catch (Exception e) {
967
					logMetacat.error("Error running replication: " + e.getMessage(), e);
968
					throw new RuntimeException(e.getMessage(), e);
969
				}
970
			}
971
    	};
972
    	// submit the task, and that's it
973
    	executor.submit(runner);
974
        
975
    	// thread was started, so we return success
976
        response.setStatus(200);
977
        
978
    }
979

    
980
    /**
981
     * Handle the getReplica action for the MN
982
     * @param id  the identifier for the object
983
     * @throws NotFound 
984
     * @throws ServiceFailure 
985
     * @throws NotImplemented 
986
     * @throws NotAuthorized 
987
     * @throws InvalidToken 
988
     * @throws InvalidRequest 
989
     */
990
    private void getReplica(String id) 
991
        throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented, 
992
        ServiceFailure, NotFound {
993
        
994
        Identifier pid = new Identifier();
995
        pid.setValue(id);
996
        OutputStream out = null;
997
        InputStream dataBytes = null;
998
                
999
        try {
1000
            // call the service
1001
            dataBytes = MNodeService.getInstance(request).getReplica(session, pid);
1002

    
1003
            response.setContentType("application/octet-stream");
1004
            response.setStatus(200);
1005
            out = response.getOutputStream();
1006
            // write the object to the output stream
1007
            IOUtils.copyLarge(dataBytes, out);
1008
            
1009
        } catch (IOException e) {
1010
            String msg = "There was an error writing the output: " + e.getMessage();
1011
            logMetacat.error(msg);
1012
            throw new ServiceFailure("2181", msg);
1013
        
1014
        }
1015

    
1016
    }
1017

    
1018
    /**
1019
     * Get the Node information
1020
     * 
1021
     * @throws JiBXException
1022
     * @throws IOException
1023
     * @throws InvalidRequest 
1024
     * @throws ServiceFailure 
1025
     * @throws NotAuthorized 
1026
     * @throws NotImplemented 
1027
     */
1028
    private void node() 
1029
        throws JiBXException, IOException, NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest {
1030
        
1031
        Node n = MNodeService.getInstance(request).getCapabilities();
1032
        
1033
        response.setContentType("text/xml");
1034
        response.setStatus(200);
1035
        TypeMarshaller.marshalTypeToOutputStream(n, response.getOutputStream());
1036
        
1037
    }
1038
    
1039
    /**
1040
     * MN_crud.describe()
1041
     * http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.describe
1042
     * @param pid
1043
     * @throws InvalidRequest 
1044
     * @throws NotImplemented 
1045
     * @throws NotFound 
1046
     * @throws NotAuthorized 
1047
     * @throws ServiceFailure 
1048
     * @throws InvalidToken 
1049
     */
1050
    private void describeObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest
1051
    {
1052
        
1053
        response.setContentType("text/xml");
1054

    
1055
        Identifier id = new Identifier();
1056
        id.setValue(pid);
1057

    
1058
        DescribeResponse dr = null;
1059
        try {
1060
        	dr = MNodeService.getInstance(request).describe(session, id);
1061
        } catch (BaseException e) {
1062
        	response.setStatus(e.getCode());
1063
        	response.addHeader("DataONE-Exception-Name", e.getClass().getName());
1064
            response.addHeader("DataONE-Exception-DetailCode", e.getDetail_code());
1065
            response.addHeader("DataONE-Exception-Description", e.getDescription());
1066
            response.addHeader("DataONE-Exception-PID", id.getValue());
1067
            return;
1068
		}
1069
        
1070
        response.setStatus(200);
1071
        
1072
        //response.addHeader("pid", pid);
1073
        response.addHeader("DataONE-Checksum", dr.getDataONE_Checksum().getAlgorithm() + "," + dr.getDataONE_Checksum().getValue());
1074
        response.addHeader("Content-Length", dr.getContent_Length() + "");
1075
        response.addHeader("Last-Modified", DateTimeMarshaller.serializeDateToUTC(dr.getLast_Modified()));
1076
        response.addHeader("DataONE-ObjectFormat", dr.getDataONE_ObjectFormatIdentifier().getValue());
1077
        response.addHeader("DataONE-SerialVersion", dr.getSerialVersion().toString());
1078

    
1079
        
1080
    }
1081
    
1082
    /**
1083
     * get the logs based on passed params.  Available 
1084
     * See http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.getLogRecords
1085
     * for more info
1086
     * @throws NotImplemented 
1087
     * @throws InvalidRequest 
1088
     * @throws NotAuthorized 
1089
     * @throws ServiceFailure 
1090
     * @throws InvalidToken 
1091
     * @throws IOException 
1092
     * @throws JiBXException 
1093
     */
1094
    private void getLog() throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented, IOException, JiBXException
1095
    {
1096
            
1097
        Date fromDate = null;
1098
        Date toDate = null;
1099
        Event event = null;
1100
        Integer start = null;
1101
        Integer count = null;
1102
        String pidFilter = null;
1103
        
1104
        try {
1105
        	String fromDateS = params.get("fromDate")[0];
1106
            logMetacat.debug("param fromDateS: " + fromDateS);
1107
            fromDate = DateTimeMarshaller.deserializeDateToUTC(fromDateS);
1108
        } catch (Exception e) {
1109
        	logMetacat.warn("Could not parse fromDate: " + e.getMessage());
1110
        }
1111
        try {
1112
        	String toDateS = params.get("toDate")[0];
1113
            logMetacat.debug("param toDateS: " + toDateS);
1114
            toDate = DateTimeMarshaller.deserializeDateToUTC(toDateS);
1115
        } catch (Exception e) {
1116
        	logMetacat.warn("Could not parse toDate: " + e.getMessage());
1117
		}
1118
        try {
1119
        	String eventS = params.get("event")[0];
1120
            event = Event.convert(eventS);
1121
        } catch (Exception e) {
1122
        	logMetacat.warn("Could not parse event: " + e.getMessage());
1123
		}
1124
        logMetacat.debug("fromDate: " + fromDate + " toDate: " + toDate);
1125
        
1126
        try {
1127
        	start =  Integer.parseInt(params.get("start")[0]);
1128
        } catch (Exception e) {
1129
			logMetacat.warn("Could not parse start: " + e.getMessage());
1130
		}
1131
        try {
1132
        	count =  Integer.parseInt(params.get("count")[0]);
1133
        } catch (Exception e) {
1134
			logMetacat.warn("Could not parse count: " + e.getMessage());
1135
		}
1136
        
1137
        try {
1138
            pidFilter = params.get("pidFilter")[0];
1139
        } catch (Exception e) {
1140
            logMetacat.warn("Could not parse pidFilter: " + e.getMessage());
1141
        }
1142
        
1143
        logMetacat.debug("calling getLogRecords");
1144
        Log log = MNodeService.getInstance(request).getLogRecords(session, fromDate, toDate, event, pidFilter, start, count);
1145
        
1146
        OutputStream out = response.getOutputStream();
1147
        response.setStatus(200);
1148
        response.setContentType("text/xml");
1149
        
1150
        TypeMarshaller.marshalTypeToOutputStream(log, out);
1151
        
1152
    }
1153
    
1154
    
1155
    
1156
    /**
1157
     * Implements REST version of DataONE CRUD API --> get
1158
     * @param pid ID of data object to be read
1159
     * @throws NotImplemented 
1160
     * @throws InvalidRequest 
1161
     * @throws NotFound 
1162
     * @throws NotAuthorized 
1163
     * @throws ServiceFailure 
1164
     * @throws InvalidToken 
1165
     * @throws IOException 
1166
     * @throws JiBXException 
1167
     */
1168
    protected void getObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
1169
        OutputStream out = null;
1170
        
1171
        if (pid != null) { //get a specific document                
1172
            Identifier id = new Identifier();
1173
            id.setValue(pid);
1174
                
1175
            SystemMetadata sm = MNodeService.getInstance(request).getSystemMetadata(session, id);
1176
            
1177
            // set the headers for the content
1178
            String mimeType = ObjectFormatInfo.instance().getMimeType(sm.getFormatId().getValue());
1179
            if (mimeType == null) {
1180
            	mimeType = "application/octet-stream";
1181
            }
1182
            String extension = ObjectFormatInfo.instance().getExtension(sm.getFormatId().getValue());
1183
            String filename = id.getValue();
1184
            if (extension != null) {
1185
            	filename = id.getValue() + extension;
1186
            }
1187
            response.setContentType(mimeType);
1188
            response.setHeader("Content-Disposition", "inline; filename=" + filename);
1189
            
1190
            InputStream data = MNodeService.getInstance(request).get(session, id);
1191

    
1192
            out = response.getOutputStream();  
1193
            IOUtils.copyLarge(data, out);
1194
            
1195
        }
1196
        else
1197
        { //call listObjects with specified params
1198
            Date startTime = null;
1199
            Date endTime = null;
1200
            ObjectFormatIdentifier formatId = null;
1201
            boolean replicaStatus = true;
1202
            int start = 0;
1203
            //TODO: make the max count into a const
1204
            int count = 1000;
1205
            Enumeration paramlist = request.getParameterNames();
1206
            while (paramlist.hasMoreElements()) 
1207
            { //parse the params and make the crud call
1208
                String name = (String) paramlist.nextElement();
1209
                String[] value = (String[])request.getParameterValues(name);
1210

    
1211
                if (name.equals("fromDate") && value != null)
1212
                {
1213
                    try
1214
                    {
1215
                      //startTime = dateFormat.parse(value[0]);
1216
                    	startTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
1217
                        //startTime = parseDateAndConvertToGMT(value[0]);
1218
                    }
1219
                    catch(Exception e)
1220
                    {  //if we can't parse it, just don't use the fromDate param
1221
                        logMetacat.warn("Could not parse fromDate: " + value[0]);
1222
                        startTime = null;
1223
                    }
1224
                }
1225
                else if(name.equals("toDate") && value != null)
1226
                {
1227
                    try
1228
                    {
1229
                    	endTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
1230
                    }
1231
                    catch(Exception e)
1232
                    {  //if we can't parse it, just don't use the toDate param
1233
                        logMetacat.warn("Could not parse toDate: " + value[0]);
1234
                        endTime = null;
1235
                    }
1236
                }
1237
                else if(name.equals("formatId") && value != null) 
1238
                {
1239
                	formatId = new ObjectFormatIdentifier();
1240
                	formatId.setValue(value[0]);
1241
                }
1242
                else if(name.equals("replicaStatus") && value != null)
1243
                {
1244
                    if(value != null && 
1245
                       value.length > 0 && 
1246
                       (value[0].equalsIgnoreCase("false") || value[0].equalsIgnoreCase("no")))
1247
                    {
1248
                        replicaStatus = false;
1249
                    }
1250
                }
1251
                else if(name.equals("start") && value != null)
1252
                {
1253
                    start = new Integer(value[0]).intValue();
1254
                }
1255
                else if(name.equals("count") && value != null)
1256
                {
1257
                    count = new Integer(value[0]).intValue();
1258
                }
1259
            }
1260
            //make the crud call
1261
            logMetacat.debug("session: " + session + " startTime: " + startTime +
1262
                    " endTime: " + endTime + " formatId: " + 
1263
                    formatId + " replicaStatus: " + replicaStatus + 
1264
                    " start: " + start + " count: " + count);
1265
           
1266
            ObjectList ol = 
1267
           	 MNodeService.getInstance(request).listObjects(session, startTime, endTime, 
1268
           			formatId, replicaStatus, start, count);
1269
           
1270
            out = response.getOutputStream();  
1271
            response.setStatus(200);
1272
            response.setContentType("text/xml");
1273
            // Serialize and write it to the output stream
1274
            TypeMarshaller.marshalTypeToOutputStream(ol, out);
1275
            
1276
        }
1277
        
1278
    }
1279
    
1280

    
1281
    /**
1282
     * Retrieve data package as Bagit zip
1283
     * @param pid
1284
     * @throws NotImplemented 
1285
     * @throws NotFound 
1286
     * @throws NotAuthorized 
1287
     * @throws ServiceFailure 
1288
     * @throws InvalidToken 
1289
     * @throws IOException 
1290
     */
1291
    protected void getPackage(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, IOException {
1292

    
1293
        Identifier id = new Identifier();
1294
        id.setValue(pid);
1295
        InputStream is = MNodeService.getInstance(request).getPackage(session, id);
1296
        
1297
        // use the provided filename
1298
        String filename = null;
1299
        if (is instanceof DeleteOnCloseFileInputStream) {
1300
            filename = ((DeleteOnCloseFileInputStream)is).getFile().getName();
1301
        } else {
1302
        	filename = "dataPackage-" + System.currentTimeMillis() + ".zip";
1303
        }
1304
        response.setHeader("Content-Disposition", "inline; filename=" + filename);
1305
        response.setContentType("application/zip");
1306
        response.setStatus(200);
1307
        OutputStream out = response.getOutputStream();
1308
        
1309
        // write it to the output stream
1310
        IOUtils.copyLarge(is, out);
1311
   }
1312
    
1313
	protected void publish(String pid) throws InvalidToken, ServiceFailure,
1314
			NotAuthorized, NotFound, NotImplemented, IOException,
1315
			JiBXException, InvalidRequest, IdentifierNotUnique,
1316
			UnsupportedType, InsufficientResources, InvalidSystemMetadata {
1317

    
1318
		// publish the object
1319
		Identifier originalIdentifier = new Identifier();
1320
		originalIdentifier.setValue(pid);
1321
		Identifier newIdentifier = MNodeService.getInstance(request).publish(session, originalIdentifier);
1322

    
1323
		response.setStatus(200);
1324
		response.setContentType("text/xml");
1325
		OutputStream out = response.getOutputStream();
1326

    
1327
		// write new identifier to the output stream
1328
		TypeMarshaller.marshalTypeToOutputStream(newIdentifier, out);
1329
	}
1330
    
1331
    /**
1332
     * Retrieve System Metadata
1333
     * @param pid
1334
     * @throws InvalidToken
1335
     * @throws ServiceFailure
1336
     * @throws NotAuthorized
1337
     * @throws NotFound
1338
     * @throws InvalidRequest
1339
     * @throws NotImplemented
1340
     * @throws IOException
1341
     * @throws JiBXException
1342
     */
1343
    protected void getSystemMetadataObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
1344

    
1345
        Identifier id = new Identifier();
1346
        id.setValue(pid);
1347
        SystemMetadata sysmeta = MNodeService.getInstance(request).getSystemMetadata(session, id);
1348
        
1349
        response.setContentType("text/xml");
1350
        response.setStatus(200);
1351
        OutputStream out = response.getOutputStream();
1352
        
1353
        // Serialize and write it to the output stream
1354
        TypeMarshaller.marshalTypeToOutputStream(sysmeta, out);
1355
   }
1356
    
1357
    
1358
    /**
1359
     * Inserts or updates the object
1360
     * 
1361
     * @param pid - ID of data object to be inserted or updated.  If action is update, the pid
1362
     *               is the existing pid.  If insert, the pid is the new one
1363
     * @throws InvalidRequest 
1364
     * @throws ServiceFailure 
1365
     * @throws JiBXException 
1366
     * @throws NotImplemented 
1367
     * @throws InvalidSystemMetadata 
1368
     * @throws InsufficientResources 
1369
     * @throws UnsupportedType 
1370
     * @throws IdentifierNotUnique 
1371
     * @throws NotAuthorized 
1372
     * @throws InvalidToken 
1373
     * @throws NotFound 
1374
     * @throws IOException 
1375
     * @throws IllegalAccessException 
1376
     * @throws InstantiationException 
1377
     */
1378
    protected void putObject(String trailingPid, String action) throws ServiceFailure, InvalidRequest, JiBXException, InvalidToken, NotAuthorized, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, NotFound, IOException, InstantiationException, IllegalAccessException {
1379
       
1380
    	// Read the incoming data from its Mime Multipart encoding
1381
    	Map<String, File> files = collectMultipartFiles();
1382
               
1383
    	Identifier pid = new Identifier();
1384
        if (trailingPid == null) {
1385
	        // get the pid string from the body and set the value
1386
	        String pidString = multipartparams.get("pid").get(0);
1387
	        if (pidString != null) {
1388
            pid.setValue(pidString);
1389
            
1390
          } else {
1391
              throw new InvalidRequest("1102", "The pid param must be included and contain the identifier.");
1392
              
1393
          }
1394
        } else {
1395
        	// use the pid included in the URL
1396
        	pid.setValue(trailingPid);
1397
        }
1398
        logMetacat.debug("putObject with pid " + pid.getValue());
1399
        logMetacat.debug("Entering putObject: " + pid.getValue() + "/" + action);
1400

    
1401
        InputStream object = null;
1402
        InputStream sysmeta = null;
1403
        File smFile = files.get("sysmeta");
1404
        sysmeta = new FileInputStream(smFile);
1405
        File objFile = files.get("object");
1406
        object = new FileInputStream(objFile);
1407
        
1408
        // ensure we have the object bytes
1409
        if  ( objFile == null ) {
1410
            throw new InvalidRequest("1102", "The object param must contain the object bytes.");
1411
            
1412
        }
1413
        
1414
        // ensure we have the system metadata
1415
        if  ( smFile == null ) {
1416
            throw new InvalidRequest("1102", "The sysmeta param must contain the system metadata document.");
1417
            
1418
        }
1419
        
1420
        response.setStatus(200);
1421
        response.setContentType("text/xml");
1422
        OutputStream out = response.getOutputStream();
1423
        
1424
        if (action.equals(FUNCTION_NAME_INSERT)) { 
1425
            // handle inserts
1426
            logMetacat.debug("Commence creation...");
1427
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1428

    
1429
            logMetacat.debug("creating object with pid " + pid.getValue());
1430
            Identifier rId = MNodeService.getInstance(request).create(session, pid, object, smd);
1431
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1432
            
1433
        } else if (action.equals(FUNCTION_NAME_UPDATE)) {
1434
        	// handle updates
1435
        	
1436
            // construct pids
1437
            Identifier newPid = null;
1438
            try {
1439
            	String newPidString = multipartparams.get("newPid").get(0);
1440
            	newPid = new Identifier();
1441
            	newPid.setValue(newPidString);
1442
            } catch (Exception e) {
1443
				logMetacat.error("Could not get newPid from request");
1444
			}
1445
            logMetacat.debug("Commence update...");
1446
            
1447
            // get the systemmetadata object
1448
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1449

    
1450
            Identifier rId = MNodeService.getInstance(request).update(session, pid, object, newPid, smd);
1451
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1452
        } else {
1453
            throw new InvalidRequest("1000", "Operation must be create or update.");
1454
        }
1455
   
1456
    }
1457

    
1458
    /**
1459
     * Handle delete 
1460
     * @param pid ID of data object to be deleted
1461
     * @throws IOException
1462
     * @throws InvalidRequest 
1463
     * @throws NotImplemented 
1464
     * @throws NotFound 
1465
     * @throws NotAuthorized 
1466
     * @throws ServiceFailure 
1467
     * @throws InvalidToken 
1468
     * @throws JiBXException 
1469
     */
1470
    private void deleteObject(String pid) throws IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest, JiBXException 
1471
    {
1472

    
1473
        OutputStream out = response.getOutputStream();
1474
        response.setStatus(200);
1475
        response.setContentType("text/xml");
1476

    
1477
        Identifier id = new Identifier();
1478
        id.setValue(pid);
1479

    
1480
        logMetacat.debug("Calling delete");
1481
        MNodeService.getInstance(request).delete(session, id);
1482
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1483
        
1484
    }
1485
    
1486
    /**
1487
     * Archives the given pid
1488
     * @param pid
1489
     * @throws InvalidToken
1490
     * @throws ServiceFailure
1491
     * @throws NotAuthorized
1492
     * @throws NotFound
1493
     * @throws NotImplemented
1494
     * @throws IOException
1495
     * @throws JiBXException
1496
     */
1497
    private void archive(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, IOException, JiBXException {
1498

    
1499
        OutputStream out = response.getOutputStream();
1500
        response.setStatus(200);
1501
        response.setContentType("text/xml");
1502

    
1503
        Identifier id = new Identifier();
1504
        id.setValue(pid);
1505

    
1506
        logMetacat.debug("Calling archive");
1507
        MNodeService.getInstance(request).archive(session, id);
1508
        
1509
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1510
        
1511
    }
1512

    
1513
	protected SynchronizationFailed collectSynchronizationFailed() throws IOException, ServiceFailure, InvalidRequest, JiBXException, InstantiationException, IllegalAccessException, ParserConfigurationException, SAXException  {
1514
		
1515
		// Read the incoming data from its Mime Multipart encoding
1516
		logMetacat.debug("Disassembling MIME multipart form");
1517
		InputStream sf = null;
1518
	
1519
		// handle MMP inputs
1520
		File tmpDir = getTempDirectory();
1521
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
1522
		MultipartRequestResolver mrr = 
1523
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
1524
		MultipartRequest mr = null;
1525
		try {
1526
			mr = mrr.resolveMultipart(request);
1527
		} catch (Exception e) {
1528
			throw new ServiceFailure("2161", 
1529
					"Could not resolve multipart: " + e.getMessage());
1530
		}
1531
		logMetacat.debug("resolved multipart request");
1532
		Map<String, File> files = mr.getMultipartFiles();
1533
		if (files == null || files.keySet() == null) {
1534
			throw new InvalidRequest("2163",
1535
					"must have multipart file with name 'message'");
1536
		}
1537
		logMetacat.debug("got multipart files");
1538
	
1539
		multipartparams = mr.getMultipartParameters();
1540
	
1541
		File sfFile = files.get("message");
1542
		if (sfFile == null) {
1543
			throw new InvalidRequest("2163",
1544
					"Missing the required file-part 'message' from the multipart request.");
1545
		}
1546
		logMetacat.debug("sfFile: " + sfFile.getAbsolutePath());
1547
		sf = new FileInputStream(sfFile);
1548
	
1549
		SynchronizationFailed syncFailed = (SynchronizationFailed) ExceptionHandler.deserializeXml(sf, "Error deserializing exception");
1550
		return syncFailed;
1551
	}
1552

    
1553
}
(8-8/9)