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: tao $'
7
 *     '$Date: 2016-01-04 17:23:14 -0800 (Mon, 04 Jan 2016) $'
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.v2;
24

    
25
import java.io.File;
26
import java.io.FileInputStream;
27
import java.io.FileNotFoundException;
28
import java.io.IOException;
29
import java.io.InputStream;
30
import java.io.OutputStream;
31
import java.util.Date;
32
import java.util.Enumeration;
33
import java.util.Iterator;
34
import java.util.Map;
35
import java.util.concurrent.ExecutorService;
36
import java.util.concurrent.Executors;
37

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

    
43
import org.apache.commons.fileupload.FileUploadException;
44
import org.apache.commons.io.IOUtils;
45
import org.apache.log4j.Logger;
46
import org.dataone.client.v2.formats.ObjectFormatCache;
47
import org.dataone.client.v2.formats.ObjectFormatInfo;
48
import org.dataone.mimemultipart.MultipartRequest;
49
import org.dataone.mimemultipart.MultipartRequestResolver;
50
import org.dataone.portal.TokenGenerator;
51
import org.dataone.service.exceptions.BaseException;
52
import org.dataone.service.exceptions.IdentifierNotUnique;
53
import org.dataone.service.exceptions.InsufficientResources;
54
import org.dataone.service.exceptions.InvalidRequest;
55
import org.dataone.service.exceptions.InvalidSystemMetadata;
56
import org.dataone.service.exceptions.InvalidToken;
57
import org.dataone.service.exceptions.NotAuthorized;
58
import org.dataone.service.exceptions.NotFound;
59
import org.dataone.service.exceptions.NotImplemented;
60
import org.dataone.service.exceptions.ServiceFailure;
61
import org.dataone.service.exceptions.SynchronizationFailed;
62
import org.dataone.service.exceptions.UnsupportedType;
63
import org.dataone.service.types.v1.Checksum;
64
import org.dataone.service.types.v1.DescribeResponse;
65
import org.dataone.service.types.v1.Identifier;
66
import org.dataone.service.types.v1.NodeReference;
67
import org.dataone.service.types.v1.ObjectFormatIdentifier;
68
import org.dataone.service.types.v1.ObjectList;
69
import org.dataone.service.types.v1.Permission;
70
import org.dataone.service.types.v1.Person;
71
import org.dataone.service.types.v1.Subject;
72
import org.dataone.service.types.v1.SubjectInfo;
73
import org.dataone.service.types.v1_1.QueryEngineDescription;
74
import org.dataone.service.types.v1_1.QueryEngineList;
75
import org.dataone.service.types.v2.Log;
76
import org.dataone.service.types.v2.MediaType;
77
import org.dataone.service.types.v2.MediaTypeProperty;
78
import org.dataone.service.types.v2.Node;
79
import org.dataone.service.types.v2.ObjectFormat;
80
import org.dataone.service.types.v2.OptionList;
81
import org.dataone.service.types.v2.SystemMetadata;
82
import org.dataone.service.util.Constants;
83
import org.dataone.service.util.DateTimeMarshaller;
84
import org.dataone.service.util.ExceptionHandler;
85
import org.dataone.service.util.TypeMarshaller;
86
import org.jibx.runtime.JiBXException;
87
import org.xml.sax.SAXException;
88

    
89
import edu.ucsb.nceas.metacat.MetaCatServlet;
90
import edu.ucsb.nceas.metacat.ReadOnlyChecker;
91
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeInputStream;
92
import edu.ucsb.nceas.metacat.dataone.MNodeService;
93
import edu.ucsb.nceas.metacat.properties.PropertyService;
94
import edu.ucsb.nceas.metacat.restservice.D1ResourceHandler;
95
import edu.ucsb.nceas.metacat.util.DeleteOnCloseFileInputStream;
96
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
97

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

    
129
 *    systemMetadataChanged() - POST /dirtySystemMetadata/PID
130
 * 	
131
 * 	MNReplication
132
 * 		replicate() - POST /d1/mn/replicate
133
 *    getReplica() - GET /d1/mn/replica
134
 * 
135
 * ******************
136
 * @author leinfelder
137
 *
138
 */
139
public class MNResourceHandler extends D1ResourceHandler {
140

    
141
    // MN-specific API Resources
142
    protected static final String RESOURCE_MONITOR = "monitor";
143
    protected static final String RESOURCE_REPLICATE = "replicate";
144
    protected static final String RESOURCE_REPLICAS = "replica";
145
    protected static final String RESOURCE_NODE = "node";
146
    protected static final String RESOURCE_ERROR = "error";
147
    protected static final String RESOURCE_META_CHANGED = "dirtySystemMetadata";
148
    protected static final String RESOURCE_GENERATE_ID = "generate";
149
    protected static final String RESOURCE_PUBLISH = "publish";
150
    protected static final String RESOURCE_PACKAGE = "packages";
151
    protected static final String RESOURCE_TOKEN = "token";
152
    protected static final String RESOURCE_WHOAMI = "whoami";
153

    
154

    
155

    
156
    
157
    // shared executor
158
	private static ExecutorService executor = null;
159

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

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

    
228
                if (resource.startsWith(RESOURCE_NODE)) {
229
                    // node response
230
                    node();
231
                    status = true;
232
                } else if (resource.startsWith(RESOURCE_TOKEN)) {
233
                    logMetacat.debug("Using resource 'token'");
234
                    // get
235
                    if (httpVerb == GET) {
236
                    	// after the command
237
                        getToken();
238
                        status = true;
239
                    }
240
                    
241
                } else if (resource.startsWith(RESOURCE_WHOAMI)) {
242
                    logMetacat.debug("Using resource 'whoami'");
243
                    // get
244
                    if (httpVerb == GET) {
245
                    	// after the command
246
                        whoami();
247
                        status = true;
248
                    }
249
                    
250
                } else if (resource.startsWith(RESOURCE_IS_AUTHORIZED)) {
251
                    if (httpVerb == GET) {
252
                    	// after the command
253
                        extra = parseTrailing(resource, RESOURCE_IS_AUTHORIZED);
254
                        extra = decode(extra);
255
	                	// check the access rules
256
	                    isAuthorized(extra);
257
	                    status = true;
258
	                    logMetacat.debug("done getting access");
259
                    }
260
                } else if (resource.startsWith(RESOURCE_META)) {
261
                    logMetacat.debug("Using resource 'meta'");
262
                    // get
263
                    if (httpVerb == GET) {
264
                        logMetacat.debug("Using resource 'meta' for GET");
265
                    	// after the command
266
                        extra = parseTrailing(resource, RESOURCE_META);
267
                        extra = decode(extra);
268
                        getSystemMetadataObject(extra);
269
                        status = true;
270
                    } else if (httpVerb == PUT) {
271
                        logMetacat.debug("Using resource 'meta' for PUT");
272
                        updateSystemMetadata();
273
                        status = true;
274
                    }
275
                    
276
                } else if (resource.startsWith(RESOURCE_OBJECTS)) {
277
                    logMetacat.debug("Using resource 'object'");
278
                    // after the command
279
                    extra = parseTrailing(resource, RESOURCE_OBJECTS);
280
                    extra = decode(extra);
281
                    logMetacat.debug("objectId: " + extra);
282
                    logMetacat.debug("verb:" + httpVerb);
283

    
284
                    if (httpVerb == GET) {
285
                        getObject(extra);
286
                        status = true;
287
                    } else if (httpVerb == POST) {
288
                    	// part of the params, not the URL
289
                        putObject(null, FUNCTION_NAME_INSERT);
290
                        status = true;
291
                    } else if (httpVerb == PUT) {
292
                        putObject(extra, FUNCTION_NAME_UPDATE);
293
                        status = true;
294
                    } else if (httpVerb == DELETE) {
295
                        deleteObject(extra);
296
                        status = true;
297
                    } else if (httpVerb == HEAD) {
298
                        describeObject(extra);
299
                        status = true;
300
                    }
301
                  
302
                } else if (resource.startsWith(RESOURCE_LOG)) {
303
                    logMetacat.debug("Using resource 'log'");
304
                    // handle log events
305
                    if (httpVerb == GET) {
306
                        getLog();
307
                        status = true;
308
                    }
309
                } else if (resource.startsWith(Constants.RESOURCE_ARCHIVE)) {
310
                    logMetacat.debug("Using resource " + Constants.RESOURCE_ARCHIVE);
311
                    // handle archive events
312
                    if (httpVerb == PUT) {
313
                        extra = parseTrailing(resource, Constants.RESOURCE_ARCHIVE);
314
                        extra = decode(extra);
315
                        archive(extra);
316
                        status = true;
317
                    }
318
                } else if (resource.startsWith(Constants.RESOURCE_CHECKSUM)) {
319
                    logMetacat.debug("Using resource 'checksum'");
320
                    // handle checksum requests
321
                    if (httpVerb == GET) {
322
                    	// after the command
323
                        extra = parseTrailing(resource, Constants.RESOURCE_CHECKSUM);
324
                        extra = decode(extra);
325
                        checksum(extra);
326
                        status = true;
327
                    }
328
                } else if (resource.startsWith(RESOURCE_MONITOR)) {
329
                    // there are various parts to monitoring
330
                    if (httpVerb == GET) {
331
                    	// after the command
332
                        extra = parseTrailing(resource, RESOURCE_MONITOR);
333
                        extra = decode(extra);
334
                        // ping
335
                        if (extra.toLowerCase().equals("ping")) {
336
                            logMetacat.debug("processing ping request");
337
                            Date result = MNodeService.getInstance(request).ping();
338
                            // TODO: send to output	
339
                            status = true;
340
                            
341
                        }
342
                        
343
                    }
344
                } else if (resource.startsWith(RESOURCE_REPLICATE)) {
345
                	if (httpVerb == POST) {
346
	                    logMetacat.debug("processing replicate request");
347
	                    replicate();
348
	                    status = true;
349
                	}
350
                } else if (resource.startsWith(RESOURCE_ERROR)) {
351
	                // sync error
352
	                if (httpVerb == POST) {
353
	                    syncError();
354
	                    status = true;
355
	                }
356
                } else if (resource.startsWith(RESOURCE_META_CHANGED)) {
357
                    // system metadata changed
358
                    if (httpVerb == POST) {
359
                        systemMetadataChanged();
360
                        status = true;
361
                    }
362
                } else if (resource.startsWith(RESOURCE_REPLICAS)) {
363
                    // get replica
364
                    if (httpVerb == GET) {
365
                        extra = parseTrailing(resource, RESOURCE_REPLICAS);
366
                        extra = decode(extra);
367
                        getReplica(extra);
368
                        status = true;
369
                    }
370
                } else if (resource.startsWith(RESOURCE_QUERY)) {
371
	                logMetacat.debug("Using resource " + RESOURCE_QUERY);
372
	                // after the command
373
	                extra = parseTrailing(resource, RESOURCE_QUERY);
374
	                logMetacat.debug("query extra: " + extra);
375

    
376
	                String engine = null;
377
	                String query = null;
378

    
379
	                if (extra != null) {
380
		                // get the engine
381
		                int engineIndex = extra.length();
382
		                if (extra.indexOf("/") > -1) {
383
		                	engineIndex = extra.indexOf("/");
384
		                }
385
		                engine = extra.substring(0, engineIndex);
386
		                engine = decode(engine);
387
		                logMetacat.debug("query engine: " + engine);
388
		                
389
		                // check the query string first
390
		                query = request.getQueryString();
391
		                
392
		                // if null, look at the whole endpoint
393
		                if (query == null) {
394
			                // get the query if it is there
395
			                query = extra.substring(engineIndex, extra.length());
396
			                if (query != null && query.length() == 0) {
397
			                	query = null;
398
			                } else {
399
			                	if (query.startsWith("/")) {
400
			                		query = query.substring(1);
401
			                    }
402
			                }
403
		                }
404
		                
405
		                query = decode(query);
406
		                logMetacat.debug("query: " + query);
407

    
408
	                }
409
	                logMetacat.debug("verb:" + httpVerb);
410
	                if (httpVerb == GET) {
411
	                    doQuery(engine, query);
412
	                    status = true;
413
	                }
414
                } else if (resource.startsWith(RESOURCE_GENERATE_ID)) {
415
                	// generate an id
416
                    if (httpVerb == POST) {
417
                        generateIdentifier();
418
                        status = true;
419
                    }
420
                } else if (resource.startsWith(RESOURCE_PUBLISH)) {
421
                    logMetacat.debug("Using resource: " + RESOURCE_PUBLISH);
422
                    // PUT
423
                    if (httpVerb == PUT) {
424
                    	// after the command
425
                        extra = parseTrailing(resource, RESOURCE_PUBLISH);
426
                        extra = decode(extra);
427
                        publish(extra);
428
                        status = true;
429
                    }  
430
                } else if (resource.startsWith(RESOURCE_PACKAGE)) {
431
                    logMetacat.debug("Using resource: " + RESOURCE_PACKAGE);
432
                    // after the command
433
                    extra = parseTrailing(resource, RESOURCE_PACKAGE);
434
                    
435
                    String format = null;
436
	                String pid = null;
437

    
438
	                if (extra != null) {
439
		                // get the format
440
		                int formatIndex = extra.length();
441
		                if (extra.indexOf("/") > -1) {
442
		                	formatIndex = extra.indexOf("/");
443
		                }
444
		                format = extra.substring(0, formatIndex);
445
		                format = decode(format);
446
		                logMetacat.debug("package format: " + format);
447
		                
448
		                // get the pid if it is there
449
		                pid = extra.substring(formatIndex, extra.length());
450
		                if (pid != null && pid.length() == 0) {
451
		                	pid = null;
452
		                } else {
453
		                	if (pid.startsWith("/")) {
454
		                		pid = pid.substring(1);
455
		                    }
456
		                }
457
		                pid = decode(pid);
458
		                logMetacat.debug("pid: " + pid);
459

    
460
	                }
461
                    
462
                    // get
463
                    if (httpVerb == GET) {
464
                        
465
                        getPackage(format, pid);
466
                        status = true;
467
                    }  
468
                } else if (resource.startsWith(RESOURCE_VIEWS)) {
469
	                logMetacat.debug("Using resource " + RESOURCE_VIEWS);
470
	                // after the command
471
	                extra = parseTrailing(resource, RESOURCE_VIEWS);
472
	                logMetacat.debug("view extra: " + extra);
473

    
474
	                String format = null;
475
	                String pid = null;
476

    
477
	                if (extra != null) {
478
		                // get the format
479
		                int formatIndex = extra.length();
480
		                if (extra.indexOf("/") > -1) {
481
		                	formatIndex = extra.indexOf("/");
482
		                }
483
		                format = extra.substring(0, formatIndex);
484
		                format = decode(format);
485
		                logMetacat.debug("view format: " + format);
486
		                
487
		                // get the pid if it is there
488
		                pid = extra.substring(formatIndex, extra.length());
489
		                if (pid != null && pid.length() == 0) {
490
		                	pid = null;
491
		                } else {
492
		                	if (pid.startsWith("/")) {
493
		                		pid = pid.substring(1);
494
		                    }
495
		                }
496
		                pid = decode(pid);
497
		                logMetacat.debug("pid: " + pid);
498

    
499
	                }
500
	                logMetacat.debug("verb:" + httpVerb);
501
	                if (httpVerb == GET) {
502
	                    doViews(format, pid);
503
	                    status = true;
504
	                }
505
                } else {
506
                    throw new InvalidRequest("0000", "No resource matched for " + resource);
507
                }
508
                
509
                if (!status) {
510
                	throw new ServiceFailure("0000", "Unknown error, status = " + status);
511
                }
512
            } else {
513
            	throw new InvalidRequest("0000", "No resource matched for " + resource);
514
            }
515
        } catch (BaseException be) {
516
        	// report Exceptions as clearly as possible
517
        	OutputStream out = null;
518
			try {
519
				out = response.getOutputStream();
520
			} catch (IOException e) {
521
				logMetacat.error("Could not get output stream from response", e);
522
			}
523
            serializeException(be, out);
524
        } catch (Exception e) {
525
            // report Exceptions as clearly and generically as possible
526
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
527
        	OutputStream out = null;
528
			try {
529
				out = response.getOutputStream();
530
			} catch (IOException ioe) {
531
				logMetacat.error("Could not get output stream from response", ioe);
532
			}
533
			ServiceFailure se = new ServiceFailure("0000", e.getMessage());
534
            serializeException(se, out);
535
        }
536
    }
537
    
538

    
539
    private void doQuery(String engine, String query) {
540
    	
541
		OutputStream out = null;
542

    
543
    	try {
544
    		// NOTE: we set the session explicitly for the MNode instance since these methods do not provide a parameter			
545
	    	if (engine == null) {
546
	    		// just looking for list of engines
547
	    		MNodeService mnode = MNodeService.getInstance(request);
548
    			mnode.setSession(session);
549
	    		QueryEngineList qel = mnode.listQueryEngines(session);
550
	    		response.setContentType("text/xml");
551
	            response.setStatus(200);
552
	            out = response.getOutputStream();
553
	            TypeMarshaller.marshalTypeToOutputStream(qel, out);
554
	            return;
555
	    	} else {
556
	    		if (query != null) {
557
	    			MNodeService mnode = MNodeService.getInstance(request);
558
	    			mnode.setSession(session);
559
	    			InputStream stream = mnode.query(session, engine, query);
560

    
561
	    			// set the content-type if we have it from the implementation
562
	    			if (stream instanceof ContentTypeInputStream) {
563
		    			//response.setContentType("application/octet-stream");
564
		    			//response.setContentType("text/xml");
565
	    				response.setContentType(((ContentTypeInputStream) stream).getContentType());
566
	    			}
567
	                response.setStatus(200);
568
	                out = response.getOutputStream();
569
	                // write the results to the output stream
570
	                IOUtils.copyLarge(stream, out);
571
	                return;
572
	    		} else {
573
	    			MNodeService mnode = MNodeService.getInstance(request);
574
	    			mnode.setSession(session);
575
	    			QueryEngineDescription qed = mnode.getQueryEngineDescription(session, engine);
576
	    			response.setContentType("text/xml");
577
		            response.setStatus(200);
578
		            out = response.getOutputStream();
579
		            TypeMarshaller.marshalTypeToOutputStream(qed, out);
580
		            return;
581
	    		}
582
	    	}
583
	        
584
	        
585
    	} catch (BaseException be) {
586
        	// report Exceptions as clearly as possible
587
			try {
588
				out = response.getOutputStream();
589
			} catch (IOException e) {
590
				logMetacat.error("Could not get output stream from response", e);
591
			}
592
            serializeException(be, out);
593
        } catch (Exception e) {
594
            // report Exceptions as clearly and generically as possible
595
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
596
			try {
597
				out = response.getOutputStream();
598
			} catch (IOException ioe) {
599
				logMetacat.error("Could not get output stream from response", ioe);
600
			}
601
			ServiceFailure se = new ServiceFailure("0000", e.getMessage());
602
            serializeException(se, out);
603
        }
604
    }
605
    
606
    private void doViews(String format, String pid) {
607
    	
608
		OutputStream out = null;
609
		MNodeService mnode = MNodeService.getInstance(request);
610

    
611
    	try {
612
    		// get a list of views
613
    		if (pid != null) {
614
    			Identifier identifier = new Identifier();
615
    			identifier.setValue(pid);
616
				InputStream stream = mnode.view(session, format, identifier);
617

    
618
    			// set the content-type if we have it from the implementation
619
    			if (stream instanceof ContentTypeInputStream) {
620
    				response.setContentType(((ContentTypeInputStream) stream).getContentType());
621
    			}
622
                response.setStatus(200);
623
                out = response.getOutputStream();
624
                // write the results to the output stream
625
                IOUtils.copyLarge(stream, out);
626
                return;
627
    		} else {
628
    			// TODO: list the registered views
629
                //BaseException ni = new NotImplemented("9999", "MN.listViews() is not implemented at this node");
630
				//throw ni;
631
    		    OptionList list = mnode.listViews(session);
632
    	        
633
    	        response.setContentType("text/xml");
634
    	        response.setStatus(200);
635
    	        TypeMarshaller.marshalTypeToOutputStream(list, response.getOutputStream());
636
    		}
637
	    	
638
	        
639
    	} catch (BaseException be) {
640
        	// report Exceptions as clearly as possible
641
			try {
642
				out = response.getOutputStream();
643
			} catch (IOException e) {
644
				logMetacat.error("Could not get output stream from response", e);
645
			}
646
            serializeException(be, out);
647
        } catch (Exception e) {
648
            // report Exceptions as clearly and generically as possible
649
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
650
			try {
651
				out = response.getOutputStream();
652
			} catch (IOException ioe) {
653
				logMetacat.error("Could not get output stream from response", ioe);
654
			}
655
			ServiceFailure se = new ServiceFailure("0000", e.getMessage());
656
            serializeException(se, out);
657
        }
658
    }
659
    
660
    /**
661
     * Handles notification of system metadata changes for the given identifier
662
     * 
663
     * @param id  the identifier for the object
664
     * @throws InvalidToken 
665
     * @throws InvalidRequest 
666
     * @throws NotAuthorized 
667
     * @throws ServiceFailure 
668
     * @throws NotImplemented 
669
     */
670
    private void systemMetadataChanged() 
671
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, 
672
        InvalidToken {
673
        
674
        ReadOnlyChecker checker = new ReadOnlyChecker();
675
        boolean isReadOnlyMode = checker.isReadOnly();
676
        if(isReadOnlyMode) {
677
            throw new InvalidRequest("1334", "The Metacat member node is on the read-only mode and your request can't be fulfiled. Please try again later.");
678
        }
679
        
680
        //final long serialVersion = 0L;
681
        String serialVersionStr = null;
682
        String dateSysMetaLastModifiedStr = null;
683
        
684
        // mkae sure we have the multipart params
685
        try {
686
			initMultipartParams();
687
		} catch (Exception e1) {
688
			throw new ServiceFailure("1333", "Could not collect the multipart params for the request");
689
		}
690
        
691
        // get the pid
692
        String id = null;
693
        try {
694
        	id = multipartparams.get("pid").get(0);
695
        } catch (NullPointerException e) {
696
            String msg = "The 'pid' must be provided as a parameter and was not.";
697
            logMetacat.error(msg);
698
            throw new InvalidRequest("1334", msg);
699
        }  
700
        final Identifier pid = new Identifier();
701
        pid.setValue(id);
702
        
703
        // get the serialVersion
704
        try {
705
            serialVersionStr = multipartparams.get("serialVersion").get(0);
706
        } catch (NullPointerException e) {
707
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
708
            logMetacat.error(msg);
709
            throw new InvalidRequest("1334", msg);
710
            
711
        }  
712
        
713
        final long serialVersion = (new Long(serialVersionStr)).longValue();
714
        
715
        // get the dateSysMetaLastModified
716
        try {
717
            dateSysMetaLastModifiedStr = multipartparams.get("dateSysMetaLastModified").get(0);
718
            
719
            
720
        } catch (NullPointerException e) {
721
            String msg = 
722
                "The 'dateSysMetaLastModified' must be provided as a " + 
723
                "parameter and was not, or was an invalid representation of the timestamp.";
724
            logMetacat.error(msg);
725
            throw new InvalidRequest("1334", msg);
726
            
727
        }      
728
        final Date dateSysMetaLastModified = DateTimeMarshaller.deserializeDateToUTC(dateSysMetaLastModifiedStr);
729
        
730
        // check authorization before sending to implementation
731
        boolean authorized = MNodeService.getInstance(request).isAdminAuthorized(session);
732
        if (!authorized) {
733
        	String msg = "User is not authorized to call systemMetadataChanged";
734
            NotAuthorized failure = new NotAuthorized("1331", msg);
735
        	throw failure;
736
        }
737
        
738
        // run it in a thread to avoid connection timeout
739
        Runnable runner = new Runnable() {
740
            @Override
741
            public void run() {
742
                try {
743
                   // call the service
744
                    MNodeService.getInstance(request).systemMetadataChanged(session, pid, serialVersion, dateSysMetaLastModified);
745
                } catch (Exception e) {
746
                    logMetacat.error("Error running replication: " + e.getMessage(), e);
747
                    throw new RuntimeException(e.getMessage(), e);
748
                }
749
            }
750
        };
751
        // submit the task, and that's it
752
        executor.submit(runner);
753
        
754
        // thread was started, so we return success
755
        response.setStatus(200);
756
    }
757
    
758
    /**
759
     * Handles identifier generation calls
760
     * 
761
     * @throws InvalidRequest 
762
     * @throws NotImplemented 
763
     * @throws NotAuthorized 
764
     * @throws ServiceFailure 
765
     * @throws InvalidToken 
766
     * @throws IOException 
767
     * @throws JiBXException 
768
     */
769
    private void generateIdentifier() throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, IOException, JiBXException {
770
        
771
        // make sure we have the multipart params
772
        try {
773
			initMultipartParams();
774
		} catch (Exception e1) {
775
			throw new ServiceFailure("1333", "Could not collect the multipart params for the request");
776
		}
777
        
778
        // get the scheme
779
		String scheme = null;
780
        try {
781
        	scheme = multipartparams.get("scheme").get(0);	           
782
        } catch (NullPointerException e) {
783
            String msg = "The 'scheme' parameter was not provided, using default";
784
            logMetacat.warn(msg);
785
        }
786
        
787
        // get the fragment
788
		String fragment = null;
789
        try {
790
        	fragment = multipartparams.get("fragment").get(0);	           
791
        } catch (NullPointerException e) {
792
            String msg = "The 'fragment' parameter was not provided, using default";
793
            logMetacat.warn(msg);
794
        }
795

    
796
        // call the service
797
        Identifier identifier = MNodeService.getInstance(request).generateIdentifier(session, scheme, fragment);
798
        response.setStatus(200);
799
        response.setContentType("text/xml");
800
        OutputStream out = response.getOutputStream();
801
        TypeMarshaller.marshalTypeToOutputStream(identifier, out);
802
    }
803

    
804
    /**
805
     * Checks the access policy
806
     * @param id
807
     * @return
808
     * @throws ServiceFailure
809
     * @throws InvalidToken
810
     * @throws NotFound
811
     * @throws NotAuthorized
812
     * @throws NotImplemented
813
     * @throws InvalidRequest
814
     */
815
    private boolean isAuthorized(String id) throws ServiceFailure, InvalidToken, NotFound, NotAuthorized, NotImplemented, InvalidRequest {
816
		Identifier pid = new Identifier();
817
		pid.setValue(id);
818
		Permission permission = null;
819
		try {
820
			String perm = params.get("action")[0];
821
			permission = Permission.convert(perm);
822
		} catch (Exception e) {
823
			logMetacat.warn("No permission specified");
824
		}
825
		boolean result = MNodeService.getInstance(request).isAuthorized(session, pid, permission);
826
		response.setStatus(200);
827
		response.setContentType("text/xml");
828
		return result;
829
    }
830
    
831
    private void getToken() throws Exception {
832
		
833
		if (this.session != null) {
834
			String userId = this.session.getSubject().getValue();
835
			String fullName = null;
836
			try {
837
				Person person = this.session.getSubjectInfo().getPerson(0);
838
				fullName = person.getGivenName(0) + " " + person.getFamilyName();
839
			} catch (Exception e) {
840
				logMetacat.warn(e.getMessage(), e);
841
			}
842
			
843
			String token = null;
844
			token = TokenGenerator.getInstance().getJWT(userId, fullName);
845
			
846
			response.setStatus(200);
847
			response.setContentType("text/plain");
848
	        OutputStream out = response.getOutputStream();
849
	        out.write(token.getBytes(MetaCatServlet.DEFAULT_ENCODING));
850
	        out.close();
851
		} else {
852
			response.setStatus(401);
853
			response.setContentType("text/plain");
854
			OutputStream out = response.getOutputStream();
855
	        out.write("No session information found".getBytes(MetaCatServlet.DEFAULT_ENCODING));
856
	        out.close();
857
		}
858
		
859
    }
860
    
861
    private void whoami() throws Exception {
862
		
863
		if (this.session != null) {
864
			Subject subject = this.session.getSubject();
865
			SubjectInfo subjectInfo = null;
866
			try {
867
				subjectInfo = this.session.getSubjectInfo();
868
			} catch (Exception e) {
869
				logMetacat.warn(e.getMessage(), e);
870
			}
871
			
872
			response.setStatus(200);
873
			response.setContentType("text/plain");
874
	        OutputStream out = response.getOutputStream();
875
	        
876
	        if (subjectInfo != null) {
877
		        TypeMarshaller.marshalTypeToOutputStream(subjectInfo, out);
878
	        } else {
879
		        TypeMarshaller.marshalTypeToOutputStream(subject, out);
880
	        }
881
	        
882
	        out.close();
883
		} else {
884
			response.setStatus(401);
885
			response.setContentType("text/plain");
886
			OutputStream out = response.getOutputStream();
887
	        out.write("No session information found".getBytes(MetaCatServlet.DEFAULT_ENCODING));
888
	        out.close();
889
		}
890
		
891
    }
892
    
893
    /**
894
     * Processes failed synchronization message
895
     * @throws NotImplemented
896
     * @throws ServiceFailure
897
     * @throws NotAuthorized
898
     * @throws InvalidRequest
899
     * @throws JiBXException
900
     * @throws IllegalAccessException 
901
     * @throws InstantiationException 
902
     * @throws IOException 
903
     */
904
    private void syncError() throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest, JiBXException, IOException, InstantiationException, IllegalAccessException {
905
    	SynchronizationFailed syncFailed = null;
906
		try {
907
			syncFailed = collectSynchronizationFailed();
908
		} catch (ParserConfigurationException e) {
909
			throw new ServiceFailure("2161", e.getMessage());
910
		} catch (SAXException e) {
911
			throw new ServiceFailure("2161", e.getMessage());
912
		}
913
		
914
		MNodeService.getInstance(request).synchronizationFailed(session, syncFailed);
915
    }
916

    
917
	/**
918
     * Calculate the checksum 
919
     * @throws NotImplemented
920
     * @throws JiBXException
921
     * @throws IOException
922
     * @throws InvalidToken
923
     * @throws ServiceFailure
924
     * @throws NotAuthorized
925
     * @throws NotFound
926
     * @throws InvalidRequest
927
     */
928
    private void checksum(String pid) throws NotImplemented, JiBXException, IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest {
929
    	String checksumAlgorithm = "MD5";
930
    	try {
931
    		checksumAlgorithm = PropertyService.getProperty("dataone.checksumAlgorithm.default");
932
        } catch(Exception e) {
933
        	logMetacat.warn("Could not lookup configured default checksum algorithm, using: " + checksumAlgorithm);
934
        }    
935
        
936
        Identifier pidid = new Identifier();
937
        pidid.setValue(pid);
938
        try {
939
            checksumAlgorithm = params.get("checksumAlgorithm")[0];
940
        } catch(Exception e) {
941
            //do nothing.  use the default
942
        	logMetacat.warn("No algorithm specified, using default: " + checksumAlgorithm);
943
        }
944
        logMetacat.debug("getting checksum for object " + pid + " with algorithm " + checksumAlgorithm);
945
        
946
        Checksum c = MNodeService.getInstance(request).getChecksum(session, pidid, checksumAlgorithm);
947
        logMetacat.debug("got checksum " + c.getValue());
948
        response.setStatus(200);
949
        logMetacat.debug("serializing response");
950
        TypeMarshaller.marshalTypeToOutputStream(c, response.getOutputStream());
951
        logMetacat.debug("done serializing response.");
952
        
953
    }
954
    
955
	/**
956
     * handle the replicate action for MN
957
	 * @throws JiBXException 
958
	 * @throws FileUploadException 
959
	 * @throws IOException 
960
	 * @throws InvalidRequest 
961
	 * @throws ServiceFailure 
962
	 * @throws UnsupportedType 
963
	 * @throws InsufficientResources 
964
	 * @throws NotAuthorized 
965
	 * @throws NotImplemented 
966
	 * @throws IllegalAccessException 
967
	 * @throws InstantiationException 
968
	 * @throws InvalidToken 
969
     */
970
    private void replicate() 
971
        throws ServiceFailure, InvalidRequest, IOException, FileUploadException, 
972
        JiBXException, NotImplemented, NotAuthorized, InsufficientResources, 
973
        UnsupportedType, InstantiationException, IllegalAccessException, InvalidToken {
974

    
975
        logMetacat.debug("in POST replicate()");
976
        
977
        // somewhat unorthodox, but the call is asynchronous and we'd like to return this info sooner
978
        boolean allowed = false;
979
        if (session == null) {
980
        	String msg = "No session was provided.";
981
            NotAuthorized failure = new NotAuthorized("2152", msg);
982
        	throw failure;
983
        } else {
984
        	allowed = MNodeService.getInstance(request).isAdminAuthorized(session);
985
        	if (!allowed) {
986
        		String msg = "User is not an admin user";
987
                NotAuthorized failure = new NotAuthorized("2152", msg);
988
            	throw failure;
989
        	}
990
        }
991
        
992
        // parse the systemMetadata
993
        Map<String, File> files = collectMultipartFiles();        
994
        final SystemMetadata sysmeta = TypeMarshaller.unmarshalTypeFromFile(SystemMetadata.class, files.get("sysmeta"));
995
        
996
        String sn = multipartparams.get("sourceNode").get(0);
997
        logMetacat.debug("sourceNode: " + sn);
998
        final NodeReference sourceNode = new NodeReference();
999
        sourceNode.setValue(sn);
1000
        
1001
        // run it in a thread to avoid connection timeout
1002
        Runnable runner = new Runnable() {
1003
			@Override
1004
			public void run() {
1005
				try {
1006
			        MNodeService.getInstance(request).replicate(session, sysmeta, sourceNode);
1007
				} catch (Exception e) {
1008
					logMetacat.error("Error running replication: " + e.getMessage(), e);
1009
					throw new RuntimeException(e.getMessage(), e);
1010
				}
1011
			}
1012
    	};
1013
    	// submit the task, and that's it
1014
    	executor.submit(runner);
1015
        
1016
    	// thread was started, so we return success
1017
        response.setStatus(200);
1018
        
1019
    }
1020

    
1021
    /**
1022
     * Handle the getReplica action for the MN
1023
     * @param id  the identifier for the object
1024
     * @throws NotFound 
1025
     * @throws ServiceFailure 
1026
     * @throws NotImplemented 
1027
     * @throws NotAuthorized 
1028
     * @throws InvalidToken 
1029
     * @throws InvalidRequest 
1030
     */
1031
    private void getReplica(String id) 
1032
        throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented, 
1033
        ServiceFailure, NotFound {
1034
        
1035
        Identifier pid = new Identifier();
1036
        pid.setValue(id);
1037
        OutputStream out = null;
1038
        InputStream dataBytes = null;
1039
                
1040
        try {
1041
            // call the service
1042
            dataBytes = MNodeService.getInstance(request).getReplica(session, pid);
1043

    
1044
            response.setContentType("application/octet-stream");
1045
            response.setStatus(200);
1046
            out = response.getOutputStream();
1047
            // write the object to the output stream
1048
            IOUtils.copyLarge(dataBytes, out);
1049
            
1050
        } catch (IOException e) {
1051
            String msg = "There was an error writing the output: " + e.getMessage();
1052
            logMetacat.error(msg);
1053
            throw new ServiceFailure("2181", msg);
1054
        
1055
        }
1056

    
1057
    }
1058

    
1059
    /**
1060
     * Get the Node information
1061
     * 
1062
     * @throws JiBXException
1063
     * @throws IOException
1064
     * @throws InvalidRequest 
1065
     * @throws ServiceFailure 
1066
     * @throws NotAuthorized 
1067
     * @throws NotImplemented 
1068
     */
1069
    private void node() 
1070
        throws JiBXException, IOException, NotImplemented, NotAuthorized, ServiceFailure, InvalidRequest {
1071
        
1072
        Node n = MNodeService.getInstance(request).getCapabilities();
1073
        
1074
        response.setContentType("text/xml");
1075
        response.setStatus(200);
1076
        TypeMarshaller.marshalTypeToOutputStream(n, response.getOutputStream());
1077
        
1078
    }
1079
    
1080
    /**
1081
     * MN_crud.describe()
1082
     * http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.describe
1083
     * @param pid
1084
     * @throws InvalidRequest 
1085
     * @throws NotImplemented 
1086
     * @throws NotFound 
1087
     * @throws NotAuthorized 
1088
     * @throws ServiceFailure 
1089
     * @throws InvalidToken 
1090
     */
1091
    private void describeObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest
1092
    {
1093
        
1094
        response.setContentType("text/xml");
1095

    
1096
        Identifier id = new Identifier();
1097
        id.setValue(pid);
1098

    
1099
        DescribeResponse dr = null;
1100
        try {
1101
        	dr = MNodeService.getInstance(request).describe(session, id);
1102
        } catch (BaseException e) {
1103
        	response.setStatus(e.getCode());
1104
        	response.addHeader("DataONE-Exception-Name", e.getClass().getName());
1105
            response.addHeader("DataONE-Exception-DetailCode", e.getDetail_code());
1106
            response.addHeader("DataONE-Exception-Description", e.getDescription());
1107
            response.addHeader("DataONE-Exception-PID", id.getValue());
1108
            return;
1109
		}
1110
        
1111
        response.setStatus(200);
1112
        
1113
        //response.addHeader("pid", pid);
1114
        response.addHeader("DataONE-Checksum", dr.getDataONE_Checksum().getAlgorithm() + "," + dr.getDataONE_Checksum().getValue());
1115
        response.addHeader("Content-Length", dr.getContent_Length() + "");
1116
        response.addHeader("Last-Modified", DateTimeMarshaller.serializeDateToUTC(dr.getLast_Modified()));
1117
        response.addHeader("DataONE-ObjectFormat", dr.getDataONE_ObjectFormatIdentifier().getValue());
1118
        response.addHeader("DataONE-SerialVersion", dr.getSerialVersion().toString());
1119

    
1120
        
1121
    }
1122
    
1123
    /**
1124
     * get the logs based on passed params.  Available 
1125
     * See http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.getLogRecords
1126
     * for more info
1127
     * @throws NotImplemented 
1128
     * @throws InvalidRequest 
1129
     * @throws NotAuthorized 
1130
     * @throws ServiceFailure 
1131
     * @throws InvalidToken 
1132
     * @throws IOException 
1133
     * @throws JiBXException 
1134
     */
1135
    private void getLog() throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented, IOException, JiBXException
1136
    {
1137
            
1138
        Date fromDate = null;
1139
        Date toDate = null;
1140
        String event = null;
1141
        Integer start = null;
1142
        Integer count = null;
1143
        String pidFilter = null;
1144
        
1145
        try {
1146
        	String fromDateS = params.get("fromDate")[0];
1147
            logMetacat.debug("param fromDateS: " + fromDateS);
1148
            fromDate = DateTimeMarshaller.deserializeDateToUTC(fromDateS);
1149
        } catch (Exception e) {
1150
        	logMetacat.warn("Could not parse fromDate: " + e.getMessage());
1151
        }
1152
        try {
1153
        	String toDateS = params.get("toDate")[0];
1154
            logMetacat.debug("param toDateS: " + toDateS);
1155
            toDate = DateTimeMarshaller.deserializeDateToUTC(toDateS);
1156
        } catch (Exception e) {
1157
        	logMetacat.warn("Could not parse toDate: " + e.getMessage());
1158
		}
1159
        try {
1160
        	event = params.get("event")[0];
1161
        } catch (Exception e) {
1162
        	logMetacat.warn("Could not parse event: " + e.getMessage());
1163
		}
1164
        logMetacat.debug("fromDate: " + fromDate + " toDate: " + toDate);
1165
        
1166
        try {
1167
        	start =  Integer.parseInt(params.get("start")[0]);
1168
        } catch (Exception e) {
1169
			logMetacat.warn("Could not parse start: " + e.getMessage());
1170
		}
1171
        try {
1172
        	count =  Integer.parseInt(params.get("count")[0]);
1173
        } catch (Exception e) {
1174
			logMetacat.warn("Could not parse count: " + e.getMessage());
1175
		}
1176
        
1177
        try {
1178
            pidFilter = params.get("idFilter")[0];
1179
        } catch (Exception e) {
1180
            logMetacat.warn("Could not parse pidFilter: " + e.getMessage());
1181
        }
1182
        
1183
        logMetacat.debug("calling getLogRecords");
1184
        Log log = MNodeService.getInstance(request).getLogRecords(session, fromDate, toDate, event, pidFilter, start, count);
1185
        
1186
        OutputStream out = response.getOutputStream();
1187
        response.setStatus(200);
1188
        response.setContentType("text/xml");
1189
        
1190
        TypeMarshaller.marshalTypeToOutputStream(log, out);
1191
        
1192
    }
1193
    
1194
    
1195
    
1196
    /**
1197
     * Implements REST version of DataONE CRUD API --> get
1198
     * @param pid ID of data object to be read
1199
     * @throws NotImplemented 
1200
     * @throws InvalidRequest 
1201
     * @throws NotFound 
1202
     * @throws NotAuthorized 
1203
     * @throws ServiceFailure 
1204
     * @throws InvalidToken 
1205
     * @throws IOException 
1206
     * @throws JiBXException 
1207
     */
1208
    protected void getObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
1209
        OutputStream out = null;
1210
        
1211
        if (pid != null) { //get a specific document                
1212
            Identifier id = new Identifier();
1213
            id.setValue(pid);
1214
                
1215
            SystemMetadata sm = MNodeService.getInstance(request).getSystemMetadata(session, id);
1216
            
1217
            // set the headers for the content
1218
            String mimeType = null;
1219
            String charset = null;
1220
            ObjectFormat objectFormat = null;
1221
            
1222
            try {
1223
            	objectFormat = ObjectFormatCache.getInstance().getFormat(sm.getFormatId());
1224
        	} catch (BaseException be) {
1225
        		logMetacat.warn("Could not lookup ObjectFormat for: " + sm.getFormatId(), be);
1226
        	}
1227
            // do we have mediaType/encoding in SM?
1228
            MediaType mediaType = sm.getMediaType();
1229
            if (mediaType == null) {
1230
            	try {
1231
            		mediaType = objectFormat.getMediaType();
1232
            	} catch (Exception e) {
1233
            		logMetacat.warn("Could not lookup MediaType for: " + sm.getFormatId(), e);
1234
            	}
1235
            }
1236
            if (mediaType != null) {
1237
                mimeType = mediaType.getName();
1238
                if (mediaType.getPropertyList() != null) {
1239
                	Iterator<MediaTypeProperty> iter = mediaType.getPropertyList().iterator();
1240
                	while (iter.hasNext()) {
1241
                		MediaTypeProperty mtp = iter.next();
1242
                		if (mtp.getName().equalsIgnoreCase("charset")) {
1243
                			charset = mtp.getValue();
1244
                			mimeType += "; charset=" + charset;
1245
                			break;
1246
                		}
1247
                	}
1248
                }
1249
            }
1250
            // check object format
1251
            
1252
            // use the fallback from v1 impl
1253
            if (mimeType == null) {
1254
	            mimeType = ObjectFormatInfo.instance().getMimeType(sm.getFormatId().getValue());
1255
	            
1256
	            // still null?
1257
	            if (mimeType == null) {
1258
	            	mimeType = "application/octet-stream";
1259
	            }
1260
            }
1261
            
1262
            // check for filename in SM first
1263
            String filename = sm.getFileName();
1264
            // then fallback to using id and extension
1265
            if (filename == null) {
1266
	            String extension = objectFormat.getExtension();
1267
	            if (extension == null) {
1268
	            	extension = ObjectFormatInfo.instance().getExtension(sm.getFormatId().getValue());
1269
	            }
1270
	            filename = id.getValue();
1271
	            if (extension != null) {
1272
	            	filename = id.getValue() + extension;
1273
	            }
1274
            }
1275
            response.setContentType(mimeType);
1276
            response.setHeader("Content-Disposition", "inline; filename=" + filename);
1277
            
1278
            InputStream data = MNodeService.getInstance(request).get(session, id);
1279

    
1280
            out = response.getOutputStream();  
1281
            IOUtils.copyLarge(data, out);
1282
            
1283
        }
1284
        else
1285
        { //call listObjects with specified params
1286
            Date startTime = null;
1287
            Date endTime = null;
1288
            ObjectFormatIdentifier formatId = null;
1289
            Identifier identifier = null;
1290
            boolean replicaStatus = true;
1291
            int start = 0;
1292
            //TODO: make the max count into a const
1293
            int count = 1000;
1294
            Enumeration paramlist = request.getParameterNames();
1295
            while (paramlist.hasMoreElements()) 
1296
            { //parse the params and make the crud call
1297
                String name = (String) paramlist.nextElement();
1298
                String[] value = (String[])request.getParameterValues(name);
1299

    
1300
                if (name.equals("fromDate") && value != null)
1301
                {
1302
                    try
1303
                    {
1304
                      //startTime = dateFormat.parse(value[0]);
1305
                    	startTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
1306
                        //startTime = parseDateAndConvertToGMT(value[0]);
1307
                    }
1308
                    catch(Exception e)
1309
                    {  //if we can't parse it, just don't use the fromDate param
1310
                        logMetacat.warn("Could not parse fromDate: " + value[0]);
1311
                        startTime = null;
1312
                    }
1313
                }
1314
                else if(name.equals("toDate") && value != null)
1315
                {
1316
                    try
1317
                    {
1318
                    	endTime = DateTimeMarshaller.deserializeDateToUTC(value[0]);
1319
                    }
1320
                    catch(Exception e)
1321
                    {  //if we can't parse it, just don't use the toDate param
1322
                        logMetacat.warn("Could not parse toDate: " + value[0]);
1323
                        endTime = null;
1324
                    }
1325
                }
1326
                else if(name.equals("formatId") && value != null) 
1327
                {
1328
                	formatId = new ObjectFormatIdentifier();
1329
                	formatId.setValue(value[0]);
1330
                }
1331
                else if(name.equals("identifier") && value != null) 
1332
                {
1333
                	identifier = new Identifier();
1334
                	identifier.setValue(value[0]);
1335
                }
1336
                else if(name.equals("replicaStatus") && value != null)
1337
                {
1338
                    if(value != null && 
1339
                       value.length > 0 && 
1340
                       (value[0].equalsIgnoreCase("false") || value[0].equalsIgnoreCase("no")))
1341
                    {
1342
                        replicaStatus = false;
1343
                    }
1344
                }
1345
                else if(name.equals("start") && value != null)
1346
                {
1347
                    start = new Integer(value[0]).intValue();
1348
                }
1349
                else if(name.equals("count") && value != null)
1350
                {
1351
                    count = new Integer(value[0]).intValue();
1352
                }
1353
            }
1354
            //make the crud call
1355
            logMetacat.debug("session: " + session + " startTime: " + startTime +
1356
                    " endTime: " + endTime + " formatId: " + 
1357
                    formatId + " replicaStatus: " + replicaStatus + 
1358
                    " start: " + start + " count: " + count);
1359
           
1360
            ObjectList ol = 
1361
           	 MNodeService.getInstance(request).listObjects(session, startTime, endTime, 
1362
           			formatId, identifier, replicaStatus, start, count);
1363
           
1364
            out = response.getOutputStream();  
1365
            response.setStatus(200);
1366
            response.setContentType("text/xml");
1367
            // Serialize and write it to the output stream
1368
            TypeMarshaller.marshalTypeToOutputStream(ol, out);
1369
            
1370
        }
1371
        
1372
    }
1373
    
1374

    
1375
    /**
1376
     * Retrieve data package as Bagit zip
1377
     * @param pid
1378
     * @throws NotImplemented 
1379
     * @throws NotFound 
1380
     * @throws NotAuthorized 
1381
     * @throws ServiceFailure 
1382
     * @throws InvalidToken 
1383
     * @throws IOException 
1384
     * @throws InvalidRequest 
1385
     */
1386
    protected void getPackage(String format, String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, IOException, InvalidRequest {
1387

    
1388
        Identifier id = new Identifier();
1389
        id.setValue(pid);
1390
        ObjectFormatIdentifier formatId = null;
1391
        if (format != null) {
1392
        	formatId = new ObjectFormatIdentifier();
1393
        	formatId.setValue(format);
1394
        }
1395
		InputStream is = MNodeService.getInstance(request).getPackage(session, formatId , id);
1396
        
1397
        // use the provided filename
1398
        String filename = null;
1399
        if (is instanceof DeleteOnCloseFileInputStream) {
1400
            filename = ((DeleteOnCloseFileInputStream)is).getFile().getName();
1401
        } else {
1402
        	filename = "dataPackage-" + System.currentTimeMillis() + ".zip";
1403
        }
1404
        response.setHeader("Content-Disposition", "inline; filename=" + filename);
1405
        response.setContentType("application/zip");
1406
        response.setStatus(200);
1407
        OutputStream out = response.getOutputStream();
1408
        
1409
        // write it to the output stream
1410
        IOUtils.copyLarge(is, out);
1411
   }
1412
    
1413
	protected void publish(String pid) throws InvalidToken, ServiceFailure,
1414
			NotAuthorized, NotFound, NotImplemented, IOException,
1415
			JiBXException, InvalidRequest, IdentifierNotUnique,
1416
			UnsupportedType, InsufficientResources, InvalidSystemMetadata {
1417

    
1418
		// publish the object
1419
		Identifier originalIdentifier = new Identifier();
1420
		originalIdentifier.setValue(pid);
1421
		Identifier newIdentifier = MNodeService.getInstance(request).publish(session, originalIdentifier);
1422

    
1423
		response.setStatus(200);
1424
		response.setContentType("text/xml");
1425
		OutputStream out = response.getOutputStream();
1426

    
1427
		// write new identifier to the output stream
1428
		TypeMarshaller.marshalTypeToOutputStream(newIdentifier, out);
1429
	}
1430
    
1431
    /**
1432
     * Retrieve System Metadata
1433
     * @param pid
1434
     * @throws InvalidToken
1435
     * @throws ServiceFailure
1436
     * @throws NotAuthorized
1437
     * @throws NotFound
1438
     * @throws InvalidRequest
1439
     * @throws NotImplemented
1440
     * @throws IOException
1441
     * @throws JiBXException
1442
     */
1443
    protected void getSystemMetadataObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, InvalidRequest, NotImplemented, IOException, JiBXException {
1444

    
1445
        Identifier id = new Identifier();
1446
        id.setValue(pid);
1447
        SystemMetadata sysmeta = MNodeService.getInstance(request).getSystemMetadata(session, id);
1448
        
1449
        response.setContentType("text/xml");
1450
        response.setStatus(200);
1451
        OutputStream out = response.getOutputStream();
1452
        
1453
        // Serialize and write it to the output stream
1454
        TypeMarshaller.marshalTypeToOutputStream(sysmeta, out);
1455
   }
1456
    
1457
    
1458
    /**
1459
     * Inserts or updates the object
1460
     * 
1461
     * @param pid - ID of data object to be inserted or updated.  If action is update, the pid
1462
     *               is the existing pid.  If insert, the pid is the new one
1463
     * @throws InvalidRequest 
1464
     * @throws ServiceFailure 
1465
     * @throws JiBXException 
1466
     * @throws NotImplemented 
1467
     * @throws InvalidSystemMetadata 
1468
     * @throws InsufficientResources 
1469
     * @throws UnsupportedType 
1470
     * @throws IdentifierNotUnique 
1471
     * @throws NotAuthorized 
1472
     * @throws InvalidToken 
1473
     * @throws NotFound 
1474
     * @throws IOException 
1475
     * @throws IllegalAccessException 
1476
     * @throws InstantiationException 
1477
     */
1478
    protected void putObject(String trailingPid, String action) throws ServiceFailure, InvalidRequest, JiBXException, InvalidToken, NotAuthorized, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, NotFound, IOException, InstantiationException, IllegalAccessException {
1479
       
1480
    	// Read the incoming data from its Mime Multipart encoding
1481
    	Map<String, File> files = collectMultipartFiles();
1482
               
1483
    	Identifier pid = new Identifier();
1484
        if (trailingPid == null) {
1485
	        // get the pid string from the body and set the value
1486
	        String pidString = multipartparams.get("pid").get(0);
1487
	        if (pidString != null) {
1488
            pid.setValue(pidString);
1489
            
1490
          } else {
1491
              throw new InvalidRequest("1102", "The pid param must be included and contain the identifier.");
1492
              
1493
          }
1494
        } else {
1495
        	// use the pid included in the URL
1496
        	pid.setValue(trailingPid);
1497
        }
1498
        logMetacat.debug("putObject with pid " + pid.getValue());
1499
        logMetacat.debug("Entering putObject: " + pid.getValue() + "/" + action);
1500

    
1501
        InputStream object = null;
1502
        InputStream sysmeta = null;
1503
        File smFile = files.get("sysmeta");
1504
        sysmeta = new FileInputStream(smFile);
1505
        File objFile = files.get("object");
1506
        object = new FileInputStream(objFile);
1507
        
1508
        // ensure we have the object bytes
1509
        if  ( objFile == null ) {
1510
            throw new InvalidRequest("1102", "The object param must contain the object bytes.");
1511
            
1512
        }
1513
        
1514
        // ensure we have the system metadata
1515
        if  ( smFile == null ) {
1516
            throw new InvalidRequest("1102", "The sysmeta param must contain the system metadata document.");
1517
            
1518
        }
1519
        
1520
        response.setStatus(200);
1521
        response.setContentType("text/xml");
1522
        OutputStream out = response.getOutputStream();
1523
        
1524
        if (action.equals(FUNCTION_NAME_INSERT)) { 
1525
            // handle inserts
1526
            logMetacat.debug("Commence creation...");
1527
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1528

    
1529
            logMetacat.debug("creating object with pid " + pid.getValue());
1530
            Identifier rId = MNodeService.getInstance(request).create(session, pid, object, smd);
1531
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1532
            
1533
        } else if (action.equals(FUNCTION_NAME_UPDATE)) {
1534
        	// handle updates
1535
        	
1536
            // construct pids
1537
            Identifier newPid = null;
1538
            try {
1539
            	String newPidString = multipartparams.get("newPid").get(0);
1540
            	newPid = new Identifier();
1541
            	newPid.setValue(newPidString);
1542
            } catch (Exception e) {
1543
				logMetacat.error("Could not get newPid from request");
1544
			}
1545
            logMetacat.debug("Commence update...");
1546
            
1547
            // get the systemmetadata object
1548
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1549

    
1550
            Identifier rId = MNodeService.getInstance(request).update(session, pid, object, newPid, smd);
1551
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
1552
        } else {
1553
            throw new InvalidRequest("1000", "Operation must be create or update.");
1554
        }
1555
   
1556
    }
1557

    
1558
    /**
1559
     * Handle delete 
1560
     * @param pid ID of data object to be deleted
1561
     * @throws IOException
1562
     * @throws InvalidRequest 
1563
     * @throws NotImplemented 
1564
     * @throws NotFound 
1565
     * @throws NotAuthorized 
1566
     * @throws ServiceFailure 
1567
     * @throws InvalidToken 
1568
     * @throws JiBXException 
1569
     */
1570
    private void deleteObject(String pid) throws IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest, JiBXException 
1571
    {
1572

    
1573
        OutputStream out = response.getOutputStream();
1574
        response.setStatus(200);
1575
        response.setContentType("text/xml");
1576

    
1577
        Identifier id = new Identifier();
1578
        id.setValue(pid);
1579

    
1580
        logMetacat.debug("Calling delete");
1581
        MNodeService.getInstance(request).delete(session, id);
1582
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1583
        
1584
    }
1585
    
1586
    /**
1587
     * Archives the given pid
1588
     * @param pid
1589
     * @throws InvalidToken
1590
     * @throws ServiceFailure
1591
     * @throws NotAuthorized
1592
     * @throws NotFound
1593
     * @throws NotImplemented
1594
     * @throws IOException
1595
     * @throws JiBXException
1596
     */
1597
    private void archive(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, IOException, JiBXException {
1598

    
1599
        OutputStream out = response.getOutputStream();
1600
        response.setStatus(200);
1601
        response.setContentType("text/xml");
1602

    
1603
        Identifier id = new Identifier();
1604
        id.setValue(pid);
1605

    
1606
        logMetacat.debug("Calling archive");
1607
        MNodeService.getInstance(request).archive(session, id);
1608
        
1609
        TypeMarshaller.marshalTypeToOutputStream(id, out);
1610
        
1611
    }
1612

    
1613
	protected SynchronizationFailed collectSynchronizationFailed() throws IOException, ServiceFailure, InvalidRequest, JiBXException, InstantiationException, IllegalAccessException, ParserConfigurationException, SAXException  {
1614
		
1615
		// Read the incoming data from its Mime Multipart encoding
1616
		logMetacat.debug("Disassembling MIME multipart form");
1617
		InputStream sf = null;
1618
	
1619
		// handle MMP inputs
1620
		File tmpDir = getTempDirectory();
1621
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
1622
		MultipartRequestResolver mrr = 
1623
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), MAX_UPLOAD_SIZE, 0);
1624
		MultipartRequest mr = null;
1625
		try {
1626
			mr = mrr.resolveMultipart(request);
1627
		} catch (Exception e) {
1628
			throw new ServiceFailure("2161", 
1629
					"Could not resolve multipart: " + e.getMessage());
1630
		}
1631
		logMetacat.debug("resolved multipart request");
1632
		Map<String, File> files = mr.getMultipartFiles();
1633
		if (files == null || files.keySet() == null) {
1634
			throw new InvalidRequest("2163",
1635
					"must have multipart file with name 'message'");
1636
		}
1637
		logMetacat.debug("got multipart files");
1638
	
1639
		multipartparams = mr.getMultipartParameters();
1640
	
1641
		File sfFile = files.get("message");
1642
		if (sfFile == null) {
1643
			throw new InvalidRequest("2163",
1644
					"Missing the required file-part 'message' from the multipart request.");
1645
		}
1646
		logMetacat.debug("sfFile: " + sfFile.getAbsolutePath());
1647
		sf = new FileInputStream(sfFile);
1648
	
1649
		SynchronizationFailed syncFailed = (SynchronizationFailed) ExceptionHandler.deserializeXml(sf, "Error deserializing exception");
1650
		return syncFailed;
1651
	}
1652
	
1653
	/**
1654
	 * Update the system metadata for a specified identifier
1655
	 * @throws ServiceFailure
1656
	 * @throws InvalidRequest
1657
	 * @throws InstantiationException
1658
	 * @throws IllegalAccessException
1659
	 * @throws IOException
1660
	 * @throws JiBXException
1661
	 * @throws NotImplemented
1662
	 * @throws NotAuthorized
1663
	 * @throws InvalidSystemMetadata
1664
	 * @throws InvalidToken
1665
	 */
1666
	protected void updateSystemMetadata() throws ServiceFailure, InvalidRequest, 
1667
	                        InstantiationException, IllegalAccessException, IOException, JiBXException, NotImplemented, 
1668
	                        NotAuthorized, InvalidSystemMetadata, InvalidToken {
1669
	    // Read the incoming data from its Mime Multipart encoding
1670
        Map<String, File> files = collectMultipartFiles();
1671
        
1672
        // get the encoded pid string from the body and make the object
1673
        String pidString = multipartparams.get("pid").get(0);
1674
        Identifier pid = new Identifier();
1675
        pid.setValue(pidString);
1676
        
1677
        logMetacat.debug("updateSystemMetadata: " + pid);
1678

    
1679
        // get the system metadata from the request
1680
        File smFile = files.get("sysmeta");
1681
        FileInputStream sysmeta = new FileInputStream(smFile);
1682
        SystemMetadata systemMetadata = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1683

    
1684
        logMetacat.debug("updating system metadata with pid " + pid.getValue());
1685
        
1686
        MNodeService.getInstance(request).updateSystemMetadata(session, pid, systemMetadata);
1687
	}
1688

    
1689
}
(3-3/4)