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: 2015-10-20 11:16:21 -0700 (Tue, 20 Oct 2015) $'
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.Map;
34

    
35
import javax.servlet.ServletContext;
36
import javax.servlet.http.HttpServletRequest;
37
import javax.servlet.http.HttpServletResponse;
38
import javax.xml.parsers.ParserConfigurationException;
39

    
40
import org.apache.commons.fileupload.FileUploadException;
41
import org.apache.commons.io.IOUtils;
42
import org.apache.log4j.Logger;
43
import org.dataone.client.v2.formats.ObjectFormatInfo;
44
import org.dataone.service.exceptions.BaseException;
45
import org.dataone.service.exceptions.IdentifierNotUnique;
46
import org.dataone.service.exceptions.InsufficientResources;
47
import org.dataone.service.exceptions.InvalidRequest;
48
import org.dataone.service.exceptions.InvalidSystemMetadata;
49
import org.dataone.service.exceptions.InvalidToken;
50
import org.dataone.service.exceptions.NotAuthorized;
51
import org.dataone.service.exceptions.NotFound;
52
import org.dataone.service.exceptions.NotImplemented;
53
import org.dataone.service.exceptions.ServiceFailure;
54
import org.dataone.service.exceptions.UnsupportedType;
55
import org.dataone.service.exceptions.VersionMismatch;
56
import org.dataone.service.types.v1.AccessPolicy;
57
import org.dataone.service.types.v1.Checksum;
58
import org.dataone.service.types.v1.ChecksumAlgorithmList;
59
import org.dataone.service.types.v1.DescribeResponse;
60
import org.dataone.service.types.v1.Event;
61
import org.dataone.service.types.v1.Identifier;
62
import org.dataone.service.types.v2.Log;
63
import org.dataone.service.types.v2.OptionList;
64
import org.dataone.service.types.v1.NodeReference;
65
import org.dataone.service.types.v2.ObjectFormat;
66
import org.dataone.service.types.v1.ObjectFormatIdentifier;
67
import org.dataone.service.types.v2.ObjectFormatList;
68
import org.dataone.service.types.v1.ObjectList;
69
import org.dataone.service.types.v1.ObjectLocationList;
70
import org.dataone.service.types.v1.Permission;
71
import org.dataone.service.types.v1.Replica;
72
import org.dataone.service.types.v1.ReplicationPolicy;
73
import org.dataone.service.types.v1.ReplicationStatus;
74
import org.dataone.service.types.v1.Subject;
75
import org.dataone.service.types.v2.SystemMetadata;
76
import org.dataone.service.util.Constants;
77
import org.dataone.service.util.DateTimeMarshaller;
78
import org.dataone.service.util.EncodingUtilities;
79
import org.dataone.service.util.ExceptionHandler;
80
import org.dataone.service.util.TypeMarshaller;
81
import org.jibx.runtime.JiBXException;
82
import org.xml.sax.SAXException;
83

    
84
import edu.ucsb.nceas.metacat.common.query.stream.ContentTypeInputStream;
85
import edu.ucsb.nceas.metacat.dataone.CNodeService;
86
import edu.ucsb.nceas.metacat.properties.PropertyService;
87
import edu.ucsb.nceas.metacat.restservice.D1ResourceHandler;
88
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
89

    
90
/**
91
 * CN REST service implementation handler
92
 * 
93
 * ****************** CNCore -- DONE create() - POST /d1/cn/object/PID
94
 * listFormats() - GET /d1/cn/formats getFormat() - GET /d1/cn/formats/FMTID
95
 * getLogRecords - GET /d1/cn/log reserveIdentifier() - POST /d1/cn/reserve
96
 * listNodes() - Not implemented registerSystemMetadata() - POST /d1/meta/PID
97
 * 
98
 * CNRead -- DONE get() - GET /d1/cn/object/PID getSystemMetadata() - GET
99
 * /d1/cn/meta/PID resolve() - GET /d1/cn/resolve/PID assertRelation() - GET
100
 * /d1/cn/assertRelation/PID getChecksum() - GET /d1/cn/checksum search() - Not
101
 * implemented in Metacat
102
 * 
103
 * CNAuthorization setOwner() - PUT /d1/cn/owner/PID isAuthorized() - GET
104
 * /d1/cn/isAuthorized/PID setAccessPolicy() - POST /d1/cn/accessRules
105
 * 
106
 * CNIdentity - not implemented at all on Metacat
107
 * 
108
 * CNReplication setReplicationStatus() - PUT /replicaNotifications/PID
109
 * updateReplicationMetadata() - PUT /replicaMetadata/PID setReplicationPolicy()
110
 * - PUT /replicaPolicies/PID isNodeAuthorized() - GET
111
 * /replicaAuthorizations/PID
112
 * 
113
 * CNRegister -- not implemented at all in Metacat ******************
114
 * 
115
 * @author leinfelder
116
 * 
117
 */
118
public class CNResourceHandler extends D1ResourceHandler {
119

    
120
    /** CN-specific operations **/
121
    protected static final String RESOURCE_RESERVE = "reserve";
122
    protected static final String RESOURCE_FORMATS = "formats";
123
    protected static final String RESOURCE_RESOLVE = "resolve";
124
    protected static final String RESOURCE_OWNER = "owner";
125
    protected static final String RESOURCE_REPLICATION_POLICY = "replicaPolicies";
126
    protected static final String RESOURCE_REPLICATION_META = "replicaMetadata";
127
    protected static final String RESOURCE_REPLICATION_AUTHORIZED = "replicaAuthorizations";
128
    protected static final String RESOURCE_REPLICATION_NOTIFY = "replicaNotifications";
129

    
130
    public CNResourceHandler(ServletContext servletContext,
131
            HttpServletRequest request, HttpServletResponse response) {
132
        super(servletContext, request, response);
133
        logMetacat = Logger.getLogger(CNResourceHandler.class);
134
    }
135

    
136
    /**
137
     * This function is called from REST API servlet and handles each request to
138
     * the servlet
139
     * 
140
     * @param httpVerb
141
     *            (GET, POST, PUT or DELETE)
142
     */
143
    @Override
144
    public void handle(byte httpVerb) {
145
        // prepare the handler
146
        super.handle(httpVerb);
147

    
148
        try {
149

    
150
        	// only service requests if we have D1 configured
151
        	if (!isD1Enabled()) {
152
        		ServiceFailure se = new ServiceFailure("0000", "DataONE services are not enabled on this node");
153
                serializeException(se, response.getOutputStream());
154
                return;
155
        	}
156
        	
157
            // get the resource
158
            String resource = request.getPathInfo();
159
            resource = resource.substring(resource.indexOf("/") + 1);
160

    
161
            // for the rest of the resouce
162
            String extra = null;
163

    
164
            logMetacat.debug("handling verb " + httpVerb
165
                    + " request with resource '" + resource + "'");
166
            boolean status = false;
167

    
168
            if (resource != null) {
169

    
170
                if (resource.startsWith(RESOURCE_ACCESS_RULES)
171
                        && httpVerb == PUT) {
172
                    logMetacat.debug("Setting access policy");
173
                    // after the command
174
                    extra = parseTrailing(resource, RESOURCE_ACCESS_RULES);
175
                    extra = decode(extra);
176
                    setAccess(extra);
177
                    status = true;
178
                    logMetacat.debug("done setting access");
179

    
180
                } else if (resource.startsWith(RESOURCE_META)) {
181
                    logMetacat.debug("Using resource: " + RESOURCE_META);
182

    
183
                    // after the command
184
                    extra = parseTrailing(resource, RESOURCE_META);
185
                    extra = decode(extra);
186
                    // get
187
                    if (httpVerb == GET) {
188
                        getSystemMetadataObject(extra);
189
                        status = true;
190
                    }
191
                    // post to register system metadata
192
                    if (httpVerb == POST) {
193
                        registerSystemMetadata();
194
                        status = true;
195
                    }
196
                    else if (httpVerb == PUT) {
197
                        logMetacat.debug("Using resource 'meta' for PUT");
198
                        updateSystemMetadata();
199
                        status = true;
200
                    }
201

    
202
                } else if (resource.startsWith(RESOURCE_RESERVE)) {
203
                    // reserve the ID (in params)
204
                    if (httpVerb == POST) {
205
                        reserve();
206
                        status = true;
207
                    }
208
                } else if (resource.startsWith(RESOURCE_RESOLVE)) {
209

    
210
                    // after the command
211
                    extra = parseTrailing(resource, RESOURCE_RESOLVE);
212
                    extra = decode(extra);
213
                    // resolve the object location
214
                    if (httpVerb == GET) {
215
                        resolve(extra);
216
                        status = true;
217
                    }
218
                } else if (resource.startsWith(RESOURCE_OWNER)) {
219

    
220
                    // after the command
221
                    extra = parseTrailing(resource, RESOURCE_OWNER);
222
                    extra = decode(extra);
223
                    // set the owner
224
                    if (httpVerb == PUT) {
225
                        owner(extra);
226
                        status = true;
227
                    }
228
                } else if (resource.startsWith(RESOURCE_IS_AUTHORIZED)) {
229

    
230
                    // after the command
231
                    extra = parseTrailing(resource, RESOURCE_IS_AUTHORIZED);
232
                    extra = decode(extra);
233
                    // authorized?
234
                    if (httpVerb == GET) {
235
                        isAuthorized(extra);
236
                        status = true;
237
                    }
238
                } else if (resource.startsWith(RESOURCE_OBJECTS)) {
239
                    logMetacat.debug("Using resource 'object'");
240
                    logMetacat
241
                            .debug("D1 Rest: Starting resource processing...");
242

    
243
                    // after the command
244
                    extra = parseTrailing(resource, RESOURCE_OBJECTS);
245
                    extra = decode(extra);
246
                    logMetacat.debug("objectId: " + extra);
247
                    logMetacat.debug("verb:" + httpVerb);
248

    
249
                    if (httpVerb == GET) {
250
                        if (extra != null) {
251
                            getObject(extra);
252
                        } else {
253
                            listObjects();
254
                        }
255
                        status = true;
256
                    } else if (httpVerb == POST) {
257
                        putObject(FUNCTION_NAME_INSERT);
258
                        status = true;
259
                    } else if (httpVerb == HEAD) {
260
                        describeObject(extra);
261
                        status = true;
262
                    } else if (httpVerb == DELETE) {
263
                        deleteObject(extra);
264
                        status = true;
265
                    } 
266

    
267
                } else if (resource.startsWith(RESOURCE_FORMATS)) {
268
                    logMetacat.debug("Using resource: " + RESOURCE_FORMATS);
269

    
270
                    // after the command
271
                    extra = parseTrailing(resource, RESOURCE_FORMATS);
272
                    extra = decode(extra);
273
                    // handle each verb
274
                    if (httpVerb == GET) {
275
                        if (extra == null) {
276
                            // list the formats collection
277
                            listFormats();
278
                        } else {
279
                            // get the specified format
280
                            getFormat(extra);
281
                        }
282
                        status = true;
283
                    } else if (httpVerb == PUT) {
284
                        addFormat(extra);
285
                    }
286

    
287
                } else if (resource.startsWith(RESOURCE_LOG)) {
288
                    logMetacat.debug("Using resource: " + RESOURCE_LOG);
289
                    // handle log events
290
                    if (httpVerb == GET) {
291
                        getLog();
292
                        status = true;
293
                    }
294

    
295
                } else if (resource.startsWith(Constants.RESOURCE_ARCHIVE)) {
296
                    logMetacat.debug("Using resource " + Constants.RESOURCE_ARCHIVE);
297
                    // handle archive events
298
                    if (httpVerb == PUT) {
299
                        extra = parseTrailing(resource, Constants.RESOURCE_ARCHIVE);
300
                        extra = decode(extra);
301
                        archive(extra);
302
                        status = true;
303
                    }
304
                } else if (resource.startsWith(Constants.RESOURCE_CHECKSUM)) {
305
                    logMetacat.debug("Using resource: " + Constants.RESOURCE_CHECKSUM);
306

    
307
                    // after the command
308
                    extra = parseTrailing(resource, Constants.RESOURCE_CHECKSUM);
309
                    extra = decode(extra);
310
                    // handle checksum requests
311
                    if (httpVerb == GET) {
312

    
313
                    	if (extra != null && extra.length() > 0) {
314
	                        checksum(extra);
315
	                        status = true;
316
                    	} else {
317
                    		listChecksumAlgorithms();
318
                    		status = true;
319
                    	}
320

    
321
                    }
322

    
323
                } else if (resource.startsWith(RESOURCE_REPLICATION_POLICY)
324
                        && httpVerb == PUT) {
325

    
326
                    logMetacat.debug("Using resource: "
327
                            + RESOURCE_REPLICATION_POLICY);
328
                    // get the trailing pid
329
                    extra = parseTrailing(resource, RESOURCE_REPLICATION_POLICY);
330
                    extra = decode(extra);
331
                    setReplicationPolicy(extra);
332
                    status = true;
333

    
334
                } else if (resource.startsWith(RESOURCE_REPLICATION_META)
335
                        && httpVerb == PUT) {
336

    
337
                    logMetacat.debug("Using resource: "
338
                            + RESOURCE_REPLICATION_META);
339
                    // get the trailing pid
340
                    extra = parseTrailing(resource, RESOURCE_REPLICATION_META);
341
                    extra = decode(extra);
342
                    updateReplicationMetadata(extra);
343
                    status = true;
344

    
345
                } else if (resource.startsWith(RESOURCE_REPLICATION_NOTIFY)
346
                        && httpVerb == PUT) {
347

    
348
                    logMetacat.debug("Using resource: "
349
                            + RESOURCE_REPLICATION_NOTIFY);
350
                    // get the trailing pid
351
                    extra = parseTrailing(resource, RESOURCE_REPLICATION_NOTIFY);
352
                    extra = decode(extra);
353
                    setReplicationStatus(extra);
354
                    status = true;
355

    
356
                } else if (resource.startsWith(RESOURCE_REPLICATION_AUTHORIZED)
357
                        && httpVerb == GET) {
358

    
359
                    logMetacat.debug("Using resource: "
360
                            + RESOURCE_REPLICATION_AUTHORIZED);
361
                    // get the trailing pid
362
                    extra = parseTrailing(resource,
363
                            RESOURCE_REPLICATION_AUTHORIZED);
364
                    extra = decode(extra);
365
                    isNodeAuthorized(extra);
366
                    status = true;
367

    
368
                } else if (resource.startsWith(Constants.RESOURCE_MONITOR_PING)) {
369
                    if (httpVerb == GET) {
370
                    	// after the command
371
                        extra = parseTrailing(resource, Constants.RESOURCE_MONITOR_PING);
372
                        extra = decode(extra);
373
                        logMetacat.debug("processing ping request");
374
                        Date result = CNodeService.getInstance(request).ping();
375
                        // TODO: send to output	
376
                        status = true;
377
                    }
378
                } else if (resource.startsWith(Constants.RESOURCE_META_OBSOLETEDBY)
379
                        && httpVerb == PUT) {
380

    
381
                    logMetacat.debug("Using resource: "
382
                            + Constants.RESOURCE_META_OBSOLETEDBY);
383
                    // get the trailing pid
384
                    extra = parseTrailing(resource, Constants.RESOURCE_META_OBSOLETEDBY);
385
                    extra = decode(extra);
386
                    setObsoletedBy(extra);
387
                    status = true;
388
                } else if (resource.startsWith(Constants.RESOURCE_REPLICATION_DELETE_REPLICA)
389
                        && httpVerb == PUT) {
390

    
391
                    logMetacat.debug("Using resource: "
392
                            + Constants.RESOURCE_REPLICATION_DELETE_REPLICA);
393
                    // get the trailing pid
394
                    extra = parseTrailing(resource, Constants.RESOURCE_REPLICATION_DELETE_REPLICA);
395
                    extra = decode(extra);
396
                    deleteReplica(extra);
397
                    status = true;
398
                } else if (resource.startsWith(RESOURCE_VIEWS)) {
399
                    logMetacat.info("Using resource " + RESOURCE_VIEWS);
400
                    // after the command
401
                    extra = parseTrailing(resource, RESOURCE_VIEWS);
402
                    logMetacat.info("view extra: " + extra);
403

    
404
                    String format = null;
405
                    String pid = null;
406

    
407
                    if (extra != null) {
408
                        // get the format
409
                        int formatIndex = extra.length();
410
                        if (extra.indexOf("/") > -1) {
411
                            formatIndex = extra.indexOf("/");
412
                            format = extra.substring(0, formatIndex);
413
                        } else {
414
                            throw new InvalidRequest("2853", "The request doesn't specify the name of theme.");
415
                        }
416
                        format = decode(format);
417
                        logMetacat.info("view format: " + format);
418
                        
419
                        // get the pid if it is there
420
                        pid = extra.substring(formatIndex, extra.length());
421
                        if (pid != null && pid.length() == 0) {
422
                            pid = null;
423
                        } else {
424
                            if (pid.startsWith("/")) {
425
                                pid = pid.substring(1);
426
                            }
427
                        }
428
                        pid=decode(pid);
429
                        logMetacat.debug("pid: " + pid);
430

    
431
                    }
432
                    logMetacat.debug("verb:" + httpVerb);
433
                    if (httpVerb == GET) {
434
                        doViews(format, pid);
435
                        status = true;
436
                    }
437
                } 
438

    
439
                if (!status) {
440
                    throw new ServiceFailure("0000", "Unknown error, status = "
441
                            + status);
442
                }
443
            } else {
444
                throw new InvalidRequest("0000", "No resource matched for "
445
                        + resource);
446
            }
447
        } catch (BaseException be) {
448
            // report Exceptions as clearly and generically as possible
449
            OutputStream out = null;
450
            try {
451
                out = response.getOutputStream();
452
            } catch (IOException ioe) {
453
                logMetacat.error("Could not get output stream from response",
454
                        ioe);
455
            }
456
            serializeException(be, out);
457
        } catch (Exception e) {
458
            // report Exceptions as clearly and generically as possible
459
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
460
            OutputStream out = null;
461
            try {
462
                out = response.getOutputStream();
463
            } catch (IOException ioe) {
464
                logMetacat.error("Could not get output stream from response",
465
                        ioe);
466
            }
467
            ServiceFailure se = new ServiceFailure("0000", e.getMessage());
468
            serializeException(se, out);
469
        }
470
    }
471

    
472
    /**
473
     * Get the checksum for the given guid
474
     * 
475
     * @param guid
476
     * @throws NotImplemented
477
     * @throws InvalidRequest
478
     * @throws NotFound
479
     * @throws NotAuthorized
480
     * @throws ServiceFailure
481
     * @throws InvalidToken
482
     * @throws IOException
483
     * @throws JiBXException
484
     */
485
    private void checksum(String guid) throws InvalidToken, ServiceFailure,
486
            NotAuthorized, NotFound, InvalidRequest, NotImplemented,
487
            JiBXException, IOException {
488
        Identifier guidid = new Identifier();
489
        guidid.setValue(guid);
490
        logMetacat.debug("getting checksum for object " + guid);
491
        Checksum c = CNodeService.getInstance(request).getChecksum(session,
492
                guidid);
493
        logMetacat.debug("got checksum " + c.getValue());
494
        response.setStatus(200);
495
        logMetacat.debug("serializing response");
496
        TypeMarshaller.marshalTypeToOutputStream(c, response.getOutputStream());
497
        logMetacat.debug("done serializing response.");
498

    
499
    }
500

    
501
    /**
502
     * get the logs based on passed params. Available params are token,
503
     * fromDate, toDate, event. See
504
     * http://mule1.dataone.org/ArchitectureDocs/mn_api_crud
505
     * .html#MN_crud.getLogRecords for more info
506
     * 
507
     * @throws NotImplemented
508
     * @throws InvalidRequest
509
     * @throws NotAuthorized
510
     * @throws ServiceFailure
511
     * @throws InvalidToken
512
     * @throws IOException
513
     * @throws JiBXException
514
     */
515
    private void getLog() throws InvalidToken, ServiceFailure, NotAuthorized,
516
            InvalidRequest, NotImplemented, IOException, JiBXException {
517

    
518
        Date fromDate = null;
519
        Date toDate = null;
520
        String event = null;
521
        Integer start = null;
522
        Integer count = null;
523
        String pidFilter = null;
524

    
525
        try {
526
            String fromDateS = params.get("fromDate")[0];
527
            logMetacat.debug("param fromDateS: " + fromDateS);
528
            fromDate = DateTimeMarshaller.deserializeDateToUTC(fromDateS);
529
        } catch (Exception e) {
530
            logMetacat.warn("Could not parse fromDate: " + e.getMessage());
531
        }
532
        try {
533
            String toDateS = params.get("toDate")[0];
534
            logMetacat.debug("param toDateS: " + toDateS);
535
            toDate = DateTimeMarshaller.deserializeDateToUTC(toDateS);
536
        } catch (Exception e) {
537
            logMetacat.warn("Could not parse toDate: " + e.getMessage());
538
        }
539
        try {
540
            event = params.get("event")[0];
541
        } catch (Exception e) {
542
            logMetacat.warn("Could not parse event: " + e.getMessage());
543
        }
544
        logMetacat.debug("fromDate: " + fromDate + " toDate: " + toDate);
545

    
546
        try {
547
            start = Integer.parseInt(params.get("start")[0]);
548
        } catch (Exception e) {
549
            logMetacat.warn("Could not parse start: " + e.getMessage());
550
        }
551
        try {
552
            count = Integer.parseInt(params.get("count")[0]);
553
        } catch (Exception e) {
554
            logMetacat.warn("Could not parse count: " + e.getMessage());
555
        }
556

    
557
        try {
558
            pidFilter = params.get("idFilter")[0];
559
        } catch (Exception e) {
560
            logMetacat.warn("Could not parse pidFilter: " + e.getMessage());
561
        }
562
        
563
        logMetacat.debug("calling getLogRecords");
564
        Log log = CNodeService.getInstance(request).getLogRecords(session,
565
                fromDate, toDate, event, pidFilter, start, count);
566

    
567
        OutputStream out = response.getOutputStream();
568
        response.setStatus(200);
569
        response.setContentType("text/xml");
570

    
571
        TypeMarshaller.marshalTypeToOutputStream(log, out);
572

    
573
    }
574

    
575
    /**
576
     * Implements REST version of DataONE CRUD API --> get
577
     * 
578
     * @param guid
579
     *            ID of data object to be read
580
     * @throws NotImplemented
581
     * @throws InvalidRequest
582
     * @throws NotFound
583
     * @throws NotAuthorized
584
     * @throws ServiceFailure
585
     * @throws InvalidToken
586
     * @throws IOException
587
     */
588
    protected void getObject(String guid) throws InvalidToken, ServiceFailure,
589
            NotAuthorized, NotFound, InvalidRequest, NotImplemented,
590
            IOException {
591

    
592
        Identifier id = new Identifier();
593
        id.setValue(guid);
594

    
595
        SystemMetadata sm = CNodeService.getInstance(request)
596
                .getSystemMetadata(session, id);
597

    
598
        // set the headers for the content
599
        String mimeType = ObjectFormatInfo.instance().getMimeType(sm.getFormatId().getValue());
600
        if (mimeType == null) {
601
        	mimeType = "application/octet-stream";
602
        }
603
        String extension = ObjectFormatInfo.instance().getExtension(sm.getFormatId().getValue());
604
        String filename = id.getValue();
605
        if (extension != null) {
606
        	filename = id.getValue() + extension;
607
        }
608
        response.setContentType(mimeType);
609
        response.setHeader("Content-Disposition", "inline; filename=" + filename);
610

    
611
        InputStream data = CNodeService.getInstance(request).get(session, id);
612

    
613
        OutputStream out = response.getOutputStream();
614
        response.setStatus(200);
615
        IOUtils.copyLarge(data, out);
616

    
617
    }
618

    
619
    /**
620
     * Implements REST version of DataONE CRUD API --> getSystemMetadata
621
     * 
622
     * @param guid
623
     *            ID of data object to be read
624
     * @throws NotImplemented
625
     * @throws InvalidRequest
626
     * @throws NotFound
627
     * @throws NotAuthorized
628
     * @throws ServiceFailure
629
     * @throws InvalidToken
630
     * @throws IOException
631
     * @throws JiBXException
632
     */
633
    protected void getSystemMetadataObject(String guid) throws InvalidToken,
634
            ServiceFailure, NotAuthorized, NotFound, InvalidRequest,
635
            NotImplemented, IOException, JiBXException {
636

    
637
        Identifier id = new Identifier();
638
        id.setValue(guid);
639
        SystemMetadata sysmeta = CNodeService.getInstance(request)
640
                .getSystemMetadata(session, id);
641

    
642
        response.setContentType("text/xml");
643
        response.setStatus(200);
644
        OutputStream out = response.getOutputStream();
645

    
646
        // Serialize and write it to the output stream
647
        TypeMarshaller.marshalTypeToOutputStream(sysmeta, out);
648
    }
649

    
650
    /**
651
     * Earthgrid API > Put Service >Put Function : calls MetacatHandler >
652
     * handleInsertOrUpdateAction
653
     * 
654
     * @param guid
655
     *            - ID of data object to be inserted or updated. If action is
656
     *            update, the pid is the existing pid. If insert, the pid is the
657
     *            new one
658
     * @throws InvalidRequest
659
     * @throws ServiceFailure
660
     * @throws IdentifierNotUnique
661
     * @throws JiBXException
662
     * @throws NotImplemented
663
     * @throws InvalidSystemMetadata
664
     * @throws InsufficientResources
665
     * @throws UnsupportedType
666
     * @throws NotAuthorized
667
     * @throws InvalidToken
668
     * @throws IOException
669
     * @throws IllegalAccessException
670
     * @throws InstantiationException
671
     */
672
    protected void putObject(String action) throws ServiceFailure,
673
            InvalidRequest, IdentifierNotUnique, JiBXException, InvalidToken,
674
            NotAuthorized, UnsupportedType, InsufficientResources,
675
            InvalidSystemMetadata, NotImplemented, IOException,
676
            InstantiationException, IllegalAccessException {
677
    	
678
        // Read the incoming data from its Mime Multipart encoding
679
        Map<String, File> files = collectMultipartFiles();
680
        
681
	    // get the encoded pid string from the body and make the object
682
        String pidString = multipartparams.get("pid").get(0);
683
        Identifier pid = new Identifier();
684
        pid.setValue(pidString);
685
        
686
        logMetacat.debug("putObject: " + pid.getValue() + "/" + action);
687
        
688
        InputStream object = null;
689
        InputStream sysmeta = null;
690

    
691
        File smFile = files.get("sysmeta");
692
        sysmeta = new FileInputStream(smFile);
693
        File objFile = files.get("object");
694
        object = new FileInputStream(objFile);
695

    
696
        if (action.equals(FUNCTION_NAME_INSERT)) { // handle inserts
697

    
698
            logMetacat.debug("Commence creation...");
699
            SystemMetadata smd = TypeMarshaller.unmarshalTypeFromStream(
700
                    SystemMetadata.class, sysmeta);
701

    
702
           
703
            logMetacat.debug("creating object with pid " + pid.getValue());
704
            Identifier rId = CNodeService.getInstance(request).create(session, pid, object, smd);
705

    
706
            OutputStream out = response.getOutputStream();
707
            response.setStatus(200);
708
            response.setContentType("text/xml");
709

    
710
            TypeMarshaller.marshalTypeToOutputStream(rId, out);
711

    
712
        } else {
713
            throw new InvalidRequest("1000", "Operation must be create.");
714
        }
715
    }
716

    
717
    /**
718
     * List the object formats registered with the system
719
     * 
720
     * @throws NotImplemented
721
     * @throws InsufficientResources
722
     * @throws NotFound
723
     * @throws ServiceFailure
724
     * @throws InvalidRequest
725
     * @throws IOException
726
     * @throws JiBXException
727
     */
728
    private void listFormats() throws InvalidRequest, ServiceFailure, NotFound,
729
            InsufficientResources, NotImplemented, IOException, JiBXException {
730
        logMetacat.debug("Entering listFormats()");
731

    
732
        ObjectFormatList objectFormatList = CNodeService.getInstance(request)
733
                .listFormats();
734
        // get the response output stream
735
        OutputStream out = response.getOutputStream();
736
        response.setStatus(200);
737
        response.setContentType("text/xml");
738

    
739
        // style the object with a processing directive
740
        String stylesheet = null;
741
        try {
742
            stylesheet = PropertyService.getProperty("dataone.types.xsl.v2");
743
        } catch (PropertyNotFoundException e) {
744
            logMetacat.warn("Could not locate DataONE types XSLT: "
745
                    + e.getMessage());
746
        }
747

    
748
        TypeMarshaller.marshalTypeToOutputStream(objectFormatList, out,
749
                stylesheet);
750

    
751
    }
752
    
753
    private void listChecksumAlgorithms() throws IOException, ServiceFailure,
754
			NotImplemented, JiBXException {
755
		logMetacat.debug("Entering listFormats()");
756

    
757
		ChecksumAlgorithmList result = CNodeService.getInstance(request).listChecksumAlgorithms();
758

    
759
		// get the response output stream
760
		OutputStream out = response.getOutputStream();
761
		response.setStatus(200);
762
		response.setContentType("text/xml");
763

    
764
		TypeMarshaller.marshalTypeToOutputStream(result, out);
765

    
766
	}
767
    
768
    /**
769
     * http://mule1.dataone.org/ArchitectureDocs-current/apis/CN_APIs.html#CNRead.describe
770
     * @param pid
771
     * @throws InvalidToken
772
     * @throws ServiceFailure
773
     * @throws NotAuthorized
774
     * @throws NotFound
775
     * @throws NotImplemented
776
     * @throws InvalidRequest
777
     */
778
    private void describeObject(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest
779
    {
780
        response.setContentType("text/xml");
781
        
782
        Identifier id = new Identifier();
783
        id.setValue(pid);
784
        
785
        DescribeResponse dr = null;
786
        try {
787
            dr = CNodeService.getInstance(request).describe(session, id);
788
        } catch (BaseException e) {
789
        	response.setStatus(e.getCode());
790
        	response.addHeader("DataONE-Exception-Name", e.getClass().getName());
791
            response.addHeader("DataONE-Exception-DetailCode", e.getDetail_code());
792
            response.addHeader("DataONE-Exception-Description", e.getDescription());
793
            response.addHeader("DataONE-Exception-PID", id.getValue());
794
            return;
795
		}
796
        
797
        response.setStatus(200);
798
        //response.addHeader("pid", pid);
799
        response.addHeader("DataONE-Checksum", dr.getDataONE_Checksum().getAlgorithm() + "," + dr.getDataONE_Checksum().getValue());
800
        response.addHeader("Content-Length", dr.getContent_Length() + "");
801
        response.addHeader("Last-Modified", DateTimeMarshaller.serializeDateToUTC(dr.getLast_Modified()));
802
        response.addHeader("DataONE-ObjectFormat", dr.getDataONE_ObjectFormatIdentifier().getValue());
803
        response.addHeader("DataONE-SerialVersion", dr.getSerialVersion().toString());
804

    
805
    }
806
    
807
    /**
808
     * Handle delete 
809
     * @param pid ID of data object to be deleted
810
     * @throws IOException
811
     * @throws InvalidRequest 
812
     * @throws NotImplemented 
813
     * @throws NotFound 
814
     * @throws NotAuthorized 
815
     * @throws ServiceFailure 
816
     * @throws InvalidToken 
817
     * @throws JiBXException 
818
     */
819
    private void deleteObject(String pid) throws IOException, InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, InvalidRequest, JiBXException 
820
    {
821

    
822
        OutputStream out = response.getOutputStream();
823
        response.setStatus(200);
824
        response.setContentType("text/xml");
825

    
826
        Identifier id = new Identifier();
827
        id.setValue(pid);
828

    
829
        logMetacat.debug("Calling delete for identifier " + pid);
830
        CNodeService.getInstance(request).delete(session, id);
831
        TypeMarshaller.marshalTypeToOutputStream(id, out);
832
        
833
    }
834
    
835
    /**
836
     * Archives the given pid
837
     * @param pid
838
     * @throws InvalidToken
839
     * @throws ServiceFailure
840
     * @throws NotAuthorized
841
     * @throws NotFound
842
     * @throws NotImplemented
843
     * @throws IOException
844
     * @throws JiBXException
845
     */
846
    private void archive(String pid) throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented, IOException, JiBXException {
847

    
848
        OutputStream out = response.getOutputStream();
849
        response.setStatus(200);
850
        response.setContentType("text/xml");
851

    
852
        Identifier id = new Identifier();
853
        id.setValue(pid);
854

    
855
        logMetacat.debug("Calling archive");
856
        CNodeService.getInstance(request).archive(session, id);
857
        
858
        TypeMarshaller.marshalTypeToOutputStream(id, out);
859
        
860
    }
861

    
862
    /**
863
     * Return the requested object format
864
     * 
865
     * @param fmtidStr
866
     *            the requested format identifier as a string
867
     * @throws NotImplemented
868
     * @throws InsufficientResources
869
     * @throws NotFound
870
     * @throws ServiceFailure
871
     * @throws InvalidRequest
872
     * @throws IOException
873
     * @throws JiBXException
874
     */
875
    private void getFormat(String fmtidStr) throws InvalidRequest,
876
            ServiceFailure, NotFound, InsufficientResources, NotImplemented,
877
            IOException, JiBXException {
878
        logMetacat.debug("Entering listFormats()");
879

    
880
        ObjectFormatIdentifier fmtid = new ObjectFormatIdentifier();
881
        fmtid.setValue(fmtidStr);
882

    
883
        // get the specified object format
884
        ObjectFormat objectFormat = CNodeService.getInstance(request)
885
                .getFormat(fmtid);
886

    
887
        OutputStream out = response.getOutputStream();
888
        response.setStatus(200);
889
        response.setContentType("text/xml");
890

    
891
        TypeMarshaller.marshalTypeToOutputStream(objectFormat, out);
892

    
893
    }
894

    
895
    /**
896
     * Adds a new {@link ObjectFormat} to the object format list.
897
     * 
898
     * @param formatIdStr the format identifier
899
     * 
900
     * @throws NotImplemented 
901
     * @throws InvalidRequest 
902
     * @throws ServiceFailure 
903
     * @throws JiBXException 
904
     * @throws IOException 
905
     * @throws IllegalAccessException 
906
     * @throws InstantiationException 
907
     * @throws NotFound 
908
     * @throws NotAuthorized 
909
     * @throws InvalidToken 
910
     */
911
    private void addFormat(String formatIdStr) 
912
            throws NotImplemented, ServiceFailure, InvalidRequest, InstantiationException, 
913
            IllegalAccessException, IOException, JiBXException, NotFound, NotAuthorized, InvalidToken {
914
        
915
        logMetacat.debug("addFormat: " + formatIdStr);
916
        
917
        Map<String, File> files = collectMultipartFiles();
918
        File formatFile = files.get("format");
919
        FileInputStream formatStream = new FileInputStream(formatFile);
920
        ObjectFormat objectFormat = TypeMarshaller.unmarshalTypeFromStream(ObjectFormat.class, formatStream);
921
        ObjectFormatIdentifier formatId = new ObjectFormatIdentifier();
922
        formatId.setValue(formatIdStr);
923
        
924
        ObjectFormatIdentifier formatID = CNodeService.getInstance(request).addFormat(session, formatId, objectFormat);
925
        
926
        OutputStream out = response.getOutputStream();
927
        response.setStatus(200);
928
        response.setContentType("text/xml");
929
        TypeMarshaller.marshalTypeToOutputStream(formatID, out);
930
    }
931
    
932
    /**
933
     * Reserve the given Identifier
934
     * 
935
     * @throws InvalidToken
936
     * @throws ServiceFailure
937
     * @throws NotAuthorized
938
     * @throws IdentifierNotUnique
939
     * @throws NotImplemented
940
     * @throws InvalidRequest
941
     */
942
    private void reserve() 
943
        throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
944
        NotImplemented, InvalidRequest {
945
        Identifier pid = null;
946
        String scope = null;
947
        String format = null;
948

    
949
        // Parse the params out of the multipart form data
950
        logMetacat.debug("Parsing reserve parameters from the mime multipart entity");
951
        try {
952
            collectMultipartParams();
953
            
954
        } catch (FileUploadException e1) {
955
            String msg = "FileUploadException: Couldn't parse the mime multipart information: " +
956
            e1.getMessage();
957
            logMetacat.debug(msg);
958
            throw new ServiceFailure("4210", msg);
959

    
960
        } catch (IOException e1) {
961
            String msg = "IOException: Couldn't parse the mime multipart information: " +
962
            e1.getMessage();
963
            logMetacat.debug(msg);
964
            throw new ServiceFailure("4210", msg);
965
        
966
        } catch (Exception e1) {
967
            String msg = "Exception: Couldn't parse the mime multipart information: " +
968
            e1.getMessage();
969
            logMetacat.debug(msg);
970
            throw new ServiceFailure("4210", msg);
971

    
972
        }
973
        
974
        // gather the params
975
        try {
976
            String id = multipartparams.get("pid").get(0);
977
            pid = new Identifier();
978
            pid.setValue(id);
979
            
980
        } catch (NullPointerException e) {
981
            String msg = "The 'pid' must be provided as a parameter and was not.";
982
            logMetacat.error(msg);
983
            throw new InvalidRequest("4200", msg);
984
 
985
        }
986
        
987
        // call the implementation
988
        try {
989
            Identifier resultPid = CNodeService.getInstance(request).reserveIdentifier(session, pid);
990
            OutputStream out = response.getOutputStream();
991
            response.setStatus(200);
992
            response.setContentType("text/xml");
993
            // send back the reserved pid
994
            TypeMarshaller.marshalTypeToOutputStream(resultPid, out);
995
            
996
        } catch (IOException e) {
997
            String msg = "Couldn't write the identifier to the response output stream: " +
998
                e.getMessage();
999
            logMetacat.debug(msg);
1000
            throw new ServiceFailure("4210", msg);
1001
        
1002
        } catch (JiBXException e) {
1003
            String msg = "Couldn't marshall the identifier to the response output stream: " +
1004
            e.getMessage();
1005
            logMetacat.debug(msg);
1006
            throw new ServiceFailure("4210", msg);
1007
            
1008
        }
1009
    }
1010

    
1011
    /**
1012
     * 
1013
     * @param id
1014
     * @throws InvalidRequest
1015
     * @throws InvalidToken
1016
     * @throws ServiceFailure
1017
     * @throws NotAuthorized
1018
     * @throws NotFound
1019
     * @throws NotImplemented
1020
     * @throws IOException
1021
     * @throws JiBXException
1022
     */
1023
    private void resolve(String id) throws InvalidRequest, InvalidToken,
1024
            ServiceFailure, NotAuthorized, NotFound, NotImplemented,
1025
            IOException, JiBXException {
1026
        Identifier pid = new Identifier();
1027
        pid.setValue(id);
1028
        ObjectLocationList locationList = CNodeService.getInstance(request)
1029
                .resolve(session, pid);
1030
        OutputStream out = response.getOutputStream();
1031
        response.setStatus(200);
1032
        response.setContentType("text/xml");
1033
        TypeMarshaller.marshalTypeToOutputStream(locationList, out);
1034

    
1035
    }
1036

    
1037
    /**
1038
     * Set the owner of a resource
1039
     * 
1040
     * @param id
1041
     * @throws InvalidToken
1042
     * @throws ServiceFailure
1043
     * @throws NotFound
1044
     * @throws NotAuthorized
1045
     * @throws NotImplemented
1046
     * @throws InvalidRequest
1047
     * @throws IllegalAccessException
1048
     * @throws InstantiationException
1049
     * @throws VersionMismatch 
1050
     */
1051
    private void owner(String id) 
1052
        throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, 
1053
        NotImplemented, InvalidRequest, InstantiationException, 
1054
        IllegalAccessException, VersionMismatch {
1055

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

    
1059
        long serialVersion = 0L;
1060
        String serialVersionStr = null;
1061
        String userIdStr = null;
1062
        Subject userId = null;
1063
        
1064
        // Parse the params out of the multipart form data
1065
        // Read the incoming data from its Mime Multipart encoding
1066
        logMetacat.debug("Parsing rights holder parameters from the mime multipart entity");
1067
        try {
1068
            collectMultipartParams();
1069
            
1070
        } catch (FileUploadException e1) {
1071
            String msg = "FileUploadException: Couldn't parse the mime multipart information: " +
1072
            e1.getMessage();
1073
            logMetacat.debug(msg);
1074
            throw new ServiceFailure("4490", msg);
1075

    
1076
        } catch (IOException e1) {
1077
            String msg = "IOException: Couldn't parse the mime multipart information: " +
1078
            e1.getMessage();
1079
            logMetacat.debug(msg);
1080
            throw new ServiceFailure("4490", msg);
1081
        
1082
        } catch (Exception e1) {
1083
            String msg = "Exception: Couldn't parse the mime multipart information: " +
1084
            e1.getMessage();
1085
            logMetacat.debug(msg);
1086
            throw new ServiceFailure("4490", msg);
1087

    
1088
        }
1089
        
1090
        // get the serialVersion
1091
        try {
1092
            serialVersionStr = multipartparams.get("serialVersion").get(0);
1093
            serialVersion = new Long(serialVersionStr).longValue();
1094
            
1095
        } catch (NumberFormatException nfe) {
1096
            String msg = "The 'serialVersion' must be provided as a positive integer and was not.";
1097
            logMetacat.error(msg);
1098
            throw new InvalidRequest("4442", msg);
1099
                        
1100
        } catch (NullPointerException e) {
1101
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
1102
            logMetacat.error(msg);
1103
            throw new InvalidRequest("4442", msg);
1104
            
1105
        }
1106

    
1107
        // get the subject userId that will become the rights holder
1108
        try {
1109
            userIdStr = multipartparams.get("userId").get(0);
1110
            userId = new Subject();
1111
            userId.setValue(userIdStr);
1112
                                    
1113
        } catch (NullPointerException e) {
1114
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
1115
            logMetacat.error(msg);
1116
            throw new InvalidRequest("4442", msg);
1117
            
1118
        }
1119

    
1120
        // set the rights holder
1121
        Identifier retPid = CNodeService.getInstance(request).setRightsHolder(session, pid, userId, serialVersion);
1122
        
1123
        try {
1124
            OutputStream out = response.getOutputStream();
1125
            response.setStatus(200);
1126
            response.setContentType("text/xml");
1127
            TypeMarshaller.marshalTypeToOutputStream(retPid, out);
1128
            
1129
        } catch (IOException e) {
1130
            String msg = "Couldn't write the identifier to the response output stream: " +
1131
                e.getMessage();
1132
            logMetacat.debug(msg);
1133
            throw new ServiceFailure("4490", msg);
1134
        
1135
        } catch (JiBXException e) {
1136
            String msg = "Couldn't marshall the identifier to the response output stream: " +
1137
            e.getMessage();
1138
            logMetacat.debug(msg);
1139
            throw new ServiceFailure("4490", msg);
1140
            
1141
        }
1142
    }
1143

    
1144
    /**
1145
     * Processes the authorization check for given id
1146
     * 
1147
     * @param id
1148
     * @return
1149
     * @throws ServiceFailure
1150
     * @throws InvalidToken
1151
     * @throws NotFound
1152
     * @throws NotAuthorized
1153
     * @throws NotImplemented
1154
     * @throws InvalidRequest
1155
     */
1156
    private boolean isAuthorized(String id) throws ServiceFailure,
1157
            InvalidToken, NotFound, NotAuthorized, NotImplemented,
1158
            InvalidRequest {
1159
        Identifier pid = new Identifier();
1160
        pid.setValue(id);
1161
        String permission = params.get("action")[0];
1162
        boolean result = CNodeService.getInstance(request).isAuthorized(
1163
                session, pid, Permission.convert(permission));
1164
        response.setStatus(200);
1165
        response.setContentType("text/xml");
1166
        return result;
1167
    }
1168

    
1169
    /**
1170
     * Register System Metadata without data or metadata object
1171
     * 
1172
     * @param pid
1173
     *            identifier for System Metadata entry
1174
     * @throws JiBXException
1175
     * @throws FileUploadException
1176
     * @throws IOException
1177
     * @throws InvalidRequest
1178
     * @throws ServiceFailure
1179
     * @throws InvalidSystemMetadata
1180
     * @throws NotAuthorized
1181
     * @throws NotImplemented
1182
     * @throws IllegalAccessException
1183
     * @throws InstantiationException
1184
     */
1185
    protected void registerSystemMetadata()
1186
            throws ServiceFailure, InvalidRequest, IOException,
1187
            FileUploadException, JiBXException, NotImplemented, NotAuthorized,
1188
            InvalidSystemMetadata, InstantiationException,
1189
            IllegalAccessException {
1190
    	
1191
    	// Read the incoming data from its Mime Multipart encoding
1192
        Map<String, File> files = collectMultipartFiles();
1193
        
1194
    	// get the encoded pid string from the body and make the object
1195
        String pidString = multipartparams.get("pid").get(0);
1196
        Identifier pid = new Identifier();
1197
        pid.setValue(pidString);
1198
        
1199
        logMetacat.debug("registerSystemMetadata: " + pid);
1200

    
1201
        // get the system metadata from the request
1202
        File smFile = files.get("sysmeta");
1203
        FileInputStream sysmeta = new FileInputStream(smFile);
1204
        SystemMetadata systemMetadata = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1205

    
1206
        logMetacat.debug("registering system metadata with pid " + pid.getValue());
1207
        Identifier retGuid = CNodeService.getInstance(request).registerSystemMetadata(session, pid, systemMetadata);
1208

    
1209
        OutputStream out = response.getOutputStream();
1210
        response.setStatus(200);
1211
        response.setContentType("text/xml");
1212
        
1213
        TypeMarshaller.marshalTypeToOutputStream(retGuid, out);
1214

    
1215
    }
1216

    
1217
    /**
1218
     * set the access perms on a document
1219
     * 
1220
     * @throws JiBXException
1221
     * @throws InvalidRequest
1222
     * @throws NotImplemented
1223
     * @throws NotAuthorized
1224
     * @throws NotFound
1225
     * @throws ServiceFailure
1226
     * @throws InvalidToken
1227
     * @throws IllegalAccessException
1228
     * @throws InstantiationException
1229
     * @throws IOException
1230
     * @throws SAXException
1231
     * @throws ParserConfigurationException
1232
     * @throws VersionMismatch 
1233
     */
1234
    protected void setAccess(String pid) throws JiBXException, InvalidToken,
1235
            ServiceFailure, NotFound, NotAuthorized, NotImplemented,
1236
            InvalidRequest, IOException, InstantiationException,
1237
            IllegalAccessException, ParserConfigurationException, SAXException, VersionMismatch {
1238

    
1239
        long serialVersion = 0L;
1240
        String serialVersionStr = null;
1241
        
1242
        // parse the accessPolicy
1243
        Map<String, File> files = collectMultipartFiles();        
1244
        AccessPolicy accessPolicy = TypeMarshaller.unmarshalTypeFromFile(AccessPolicy.class, files.get("accessPolicy"));;
1245

    
1246
        // get the serialVersion
1247
        try {
1248
            serialVersionStr = multipartparams.get("serialVersion").get(0);
1249
            serialVersion = new Long(serialVersionStr).longValue();
1250

    
1251
        } catch (NumberFormatException nfe) {
1252
            String msg = "The 'serialVersion' must be provided as a positive integer and was not.";
1253
            logMetacat.error(msg);
1254
            throw new InvalidRequest("4402", msg);
1255
            
1256
        } catch (NullPointerException e) {
1257
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
1258
            logMetacat.error(msg);
1259
            throw new InvalidRequest("4402", msg);
1260

    
1261
        }
1262

    
1263
        Identifier id = new Identifier();
1264
        id.setValue(pid);
1265

    
1266
        CNodeService.getInstance(request).setAccessPolicy(session, id,
1267
                accessPolicy, serialVersion);
1268

    
1269
    }
1270

    
1271
    /**
1272
     * List the objects
1273
     * 
1274
     * @throws NotImplemented
1275
     * @throws InvalidRequest
1276
     * @throws NotAuthorized
1277
     * @throws ServiceFailure
1278
     * @throws InvalidToken
1279
     * @throws NotFound
1280
     * @throws IOException
1281
     * @throws JiBXException
1282
     * @throws Exception
1283
     */
1284
    private void listObjects() throws InvalidToken, ServiceFailure,
1285
            NotAuthorized, InvalidRequest, NotImplemented, NotFound,
1286
            IOException, JiBXException {
1287

    
1288
        Date startTime = null;
1289
        Date endTime = null;
1290
        ObjectFormatIdentifier formatId = null;
1291
        Identifier identifier = null;
1292
        //boolean replicaStatus = true;
1293
        NodeReference nodeId = null;
1294
        int start = 0;
1295
        int count = 1000;
1296
        Enumeration<String> paramlist = request.getParameterNames();
1297
        while (paramlist.hasMoreElements()) {
1298
            // parse the params and make the call
1299
            String name = paramlist.nextElement();
1300
            String[] values = request.getParameterValues(name);
1301
            String value = null;
1302
            if (values != null && values.length > 0) {
1303
            	value = values[0];
1304
            	value = EncodingUtilities.decodeString(value);
1305
            }
1306

    
1307
            if (name.equals("fromDate") && value != null) {
1308
                try {
1309
                    startTime = DateTimeMarshaller.deserializeDateToUTC(value);
1310
                } catch (Exception e) {
1311
                    // if we can't parse it, just don't use the startTime param
1312
                    logMetacat.warn("Could not parse fromDate: " + value);
1313
                    startTime = null;
1314
                }
1315
            } else if (name.equals("toDate") && value != null) {
1316
                try {
1317
                    endTime = DateTimeMarshaller.deserializeDateToUTC(value);
1318
                } catch (Exception e) {
1319
                    // if we can't parse it, just don't use the endTime param
1320
                    logMetacat.warn("Could not parse toDate: " + value);
1321
                    endTime = null;
1322
                }
1323
            } else if (name.equals("formatId") && value != null) {
1324
            	formatId = new ObjectFormatIdentifier();
1325
            	formatId.setValue(value);
1326
            } else if (name.equals("identifier") && value != null) {
1327
            	identifier = new Identifier();
1328
            	identifier.setValue(value);
1329
            /*} else if (name.equals("replicaStatus") && value != null) {
1330
                replicaStatus = Boolean.parseBoolean(value);*/
1331
            } else if (name.equals("nodeId") && value != null) {
1332
                nodeId = new NodeReference();
1333
                nodeId.setValue(value);
1334
                logMetacat.debug("the nodeId value is "+nodeId.getValue());
1335
            } else if (name.equals("start") && value != null) {
1336
                start = Integer.valueOf(value);
1337
            } else if (name.equals("count") && value != null) {
1338
                count = Integer.valueOf(value);
1339
            }
1340
        }
1341
        // make the call
1342
        logMetacat.debug("session: " + session + " fromDate: " + startTime
1343
                + " toDate: " + endTime + " formatId: " + formatId
1344
                + " start: " + start
1345
                + " count: " + count);        
1346

    
1347
        // get the list
1348
        ObjectList ol = CNodeService.getInstance(request).listObjects(session,
1349
                startTime, endTime, formatId, identifier, nodeId, start, count);
1350

    
1351
        // send it
1352
        OutputStream out = response.getOutputStream();
1353
        response.setStatus(200);
1354
        response.setContentType("text/xml");
1355

    
1356
        // style the object with a processing directive
1357
        String stylesheet = null;
1358
        try {
1359
            stylesheet = PropertyService.getProperty("dataone.types.xsl.v2");
1360
        } catch (PropertyNotFoundException e) {
1361
            logMetacat.warn("Could not locate DataONE types XSLT: "
1362
                    + e.getMessage());
1363
        }
1364

    
1365
        // Serialize and write it to the output stream
1366
        TypeMarshaller.marshalTypeToOutputStream(ol, out, stylesheet);
1367
    }
1368

    
1369
    /**
1370
     * Pass the request to get node replication authorization to CNodeService
1371
     * 
1372
     * @param pid
1373
     *            the identifier of the object to get authorization to replicate
1374
     * 
1375
     * @throws NotImplemented
1376
     * @throws NotAuthorized
1377
     * @throws InvalidToken
1378
     * @throws ServiceFailure
1379
     * @throws NotFound
1380
     * @throws InvalidRequest
1381
     */
1382
    public boolean isNodeAuthorized(String pid) throws NotImplemented,
1383
            NotAuthorized, InvalidToken, ServiceFailure, NotFound,
1384
            InvalidRequest {
1385

    
1386
        boolean result = false;
1387
        Subject targetNodeSubject = new Subject();
1388
        String nodeSubject = null;
1389
        String replPermission = null;
1390

    
1391
        // get the pid
1392
        Identifier identifier = new Identifier();
1393
        identifier.setValue(pid);
1394

    
1395
        // get the target node subject
1396
        try {
1397
            nodeSubject = params.get("targetNodeSubject")[0];
1398
            targetNodeSubject.setValue(nodeSubject);
1399

    
1400
        } catch (NullPointerException e) {
1401
            String msg = "The 'targetNodeSubject' must be provided as a parameter and was not.";
1402
            logMetacat.error(msg);
1403
            throw new InvalidRequest("4873", msg);
1404

    
1405
        }
1406

    
1407
        result = CNodeService.getInstance(request).isNodeAuthorized(session, targetNodeSubject, identifier);
1408

    
1409
        response.setStatus(200);
1410
        response.setContentType("text/xml");
1411
        return result;
1412

    
1413
    }
1414

    
1415
    /**
1416
     * Pass the request to set the replication policy to CNodeService
1417
     * 
1418
     * @param pid
1419
     *            the identifier of the object to set the replication policy on
1420
     * 
1421
     * @throws NotImplemented
1422
     * @throws NotFound
1423
     * @throws NotAuthorized
1424
     * @throws ServiceFailure
1425
     * @throws InvalidRequest
1426
     * @throws InvalidToken
1427
     * @throws IOException
1428
     * @throws InstantiationException
1429
     * @throws IllegalAccessException
1430
     * @throws JiBXException
1431
     * @throws VersionMismatch 
1432
     */
1433
    public boolean setReplicationPolicy(String pid) throws NotImplemented,
1434
            NotFound, NotAuthorized, ServiceFailure, InvalidRequest,
1435
            InvalidToken, IOException, InstantiationException,
1436
            IllegalAccessException, JiBXException, VersionMismatch {
1437

    
1438
        boolean result = false;
1439
        long serialVersion = 0L;
1440
        String serialVersionStr = null;
1441

    
1442
        Identifier identifier = new Identifier();
1443
        identifier.setValue(pid);
1444

    
1445
        // parse the policy
1446
        Map<String, File> files = collectMultipartFiles();        
1447
        ReplicationPolicy policy = TypeMarshaller.unmarshalTypeFromFile(ReplicationPolicy.class, files.get("policy"));
1448

    
1449
        // get the serialVersion
1450
        try {
1451
            serialVersionStr = multipartparams.get("serialVersion").get(0);
1452
            serialVersion = new Long(serialVersionStr).longValue();
1453

    
1454
        } catch (NullPointerException e) {
1455
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
1456
            logMetacat.error(msg);
1457
            throw new InvalidRequest("4883", msg);
1458

    
1459
        }
1460
        result = CNodeService.getInstance(request).setReplicationPolicy(
1461
                session, identifier, policy, serialVersion);
1462
        response.setStatus(200);
1463
        response.setContentType("text/xml");
1464
        return result;
1465

    
1466
    }
1467
    
1468
    /**
1469
     * Update the system metadata for a given pid, setting it to be obsoleted
1470
     * by the obsoletedByPid
1471
     *  
1472
     * @param pid
1473
     * @return
1474
     * @throws NotImplemented
1475
     * @throws NotFound
1476
     * @throws NotAuthorized
1477
     * @throws ServiceFailure
1478
     * @throws InvalidRequest
1479
     * @throws InvalidToken
1480
     * @throws InstantiationException
1481
     * @throws IllegalAccessException
1482
     * @throws VersionMismatch
1483
     */
1484
    public boolean setObsoletedBy(String pid) 
1485
        throws NotImplemented, NotFound, NotAuthorized, ServiceFailure, 
1486
        InvalidRequest, InvalidToken, InstantiationException, 
1487
        IllegalAccessException, VersionMismatch {
1488

    
1489
        boolean result = false;
1490
        long serialVersion = 0L;
1491
        String serialVersionStr = null;
1492

    
1493
        Identifier identifier = new Identifier();
1494
        identifier.setValue(pid);
1495
        String obsoletedByPidString = null;
1496
        Identifier obsoletedByPid = null;
1497

    
1498
        // Parse the params out of the multipart form data
1499
        // Read the incoming data from its Mime Multipart encoding
1500
        logMetacat.debug("Parsing rights holder parameters from the mime multipart entity");
1501
        try {
1502
            collectMultipartParams();
1503
            
1504
        } catch (FileUploadException e1) {
1505
            String msg = "FileUploadException: Couldn't parse the mime multipart information: " +
1506
            e1.getMessage();
1507
            logMetacat.debug(msg);
1508
            throw new ServiceFailure("4941", msg);
1509

    
1510
        } catch (IOException e1) {
1511
            String msg = "IOException: Couldn't parse the mime multipart information: " +
1512
            e1.getMessage();
1513
            logMetacat.debug(msg);
1514
            throw new ServiceFailure("4941", msg);
1515
        
1516
        } catch (Exception e1) {
1517
            String msg = "Exception: Couldn't parse the mime multipart information: " +
1518
            e1.getMessage();
1519
            logMetacat.debug(msg);
1520
            throw new ServiceFailure("4941", msg);
1521

    
1522
        }
1523

    
1524
        // get the obsoletedByPid
1525
        try {
1526
            obsoletedByPidString = multipartparams.get("obsoletedByPid").get(0);
1527
            obsoletedByPid = new Identifier();
1528
            obsoletedByPid.setValue(obsoletedByPidString);
1529
        } catch (NullPointerException e) {
1530
            String msg = "The 'obsoletedByPid' must be provided as a parameter and was not.";
1531
            logMetacat.error(msg);
1532
            throw new InvalidRequest("4883", msg);
1533
        }
1534

    
1535
        // get the serialVersion
1536
        try {
1537
            serialVersionStr = multipartparams.get("serialVersion").get(0);
1538
            serialVersion = new Long(serialVersionStr).longValue();
1539
            
1540
        } catch (NumberFormatException nfe) {
1541
            String msg = "The 'serialVersion' must be provided as a positive integer and was not.";
1542
            logMetacat.error(msg);
1543
            throw new InvalidRequest("4942", msg);
1544
                        
1545
        } catch (NullPointerException e) {
1546
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
1547
            logMetacat.error(msg);
1548
            throw new InvalidRequest("4942", msg);
1549
            
1550
        }
1551
        result = CNodeService.getInstance(request).setObsoletedBy(session,
1552
            identifier, obsoletedByPid, serialVersion);
1553
        response.setStatus(200);
1554
        response.setContentType("text/xml");
1555
        return result;
1556

    
1557
    }
1558
    
1559
    /**
1560
     * Delete the replica entry with the given nodeId for the given pid
1561
     * 
1562
     * @param pid
1563
     * @return
1564
     * @throws NotImplemented
1565
     * @throws NotFound
1566
     * @throws NotAuthorized
1567
     * @throws ServiceFailure
1568
     * @throws InvalidRequest
1569
     * @throws InvalidToken
1570
     * @throws InstantiationException
1571
     * @throws IllegalAccessException
1572
     * @throws VersionMismatch
1573
     */
1574
    public boolean deleteReplica(String pid) 
1575
        throws NotImplemented, NotFound, NotAuthorized, ServiceFailure, 
1576
        InvalidRequest, InvalidToken, InstantiationException, 
1577
        IllegalAccessException, VersionMismatch {
1578

    
1579
        boolean result = false;
1580
        long serialVersion = 0L;
1581
        String serialVersionStr = null;
1582

    
1583
        Identifier identifier = new Identifier();
1584
        identifier.setValue(pid);
1585

    
1586
        NodeReference nodeId = null;
1587
        
1588
        // Parse the params out of the multipart form data
1589
        // Read the incoming data from its Mime Multipart encoding
1590
        logMetacat.debug("Parsing delete replica parameters from the mime multipart entity");
1591
        try {
1592
            collectMultipartParams();
1593
            
1594
        } catch (FileUploadException e1) {
1595
            String msg = "FileUploadException: Couldn't parse the mime multipart information: " +
1596
            e1.getMessage();
1597
            logMetacat.debug(msg);
1598
            throw new ServiceFailure("4951", msg);
1599

    
1600
        } catch (IOException e1) {
1601
            String msg = "IOException: Couldn't parse the mime multipart information: " +
1602
            e1.getMessage();
1603
            logMetacat.debug(msg);
1604
            throw new ServiceFailure("4951", msg);
1605
        
1606
        } catch (Exception e1) {
1607
            String msg = "Exception: Couldn't parse the mime multipart information: " +
1608
            e1.getMessage();
1609
            logMetacat.debug(msg);
1610
            throw new ServiceFailure("4951", msg);
1611

    
1612
        }
1613
        
1614
        // get the nodeId param
1615
        try {
1616
            String nodeIdString = multipartparams.get("nodeId").get(0);
1617
            nodeId = new NodeReference();
1618
            nodeId.setValue(nodeIdString);
1619
            
1620
        } catch (NullPointerException e) {
1621
            String msg = "The 'nodeId' must be provided as a parameter and was not.";
1622
            logMetacat.error(msg);
1623
            throw new InvalidRequest("4952", msg);
1624
        }
1625

    
1626
        // get the serialVersion
1627
        try {
1628
            serialVersionStr = multipartparams.get("serialVersion").get(0);
1629
            serialVersion = new Long(serialVersionStr).longValue();
1630
            
1631
        } catch (NumberFormatException nfe) {
1632
            String msg = "The 'serialVersion' must be provided as a positive integer and was not.";
1633
            logMetacat.error(msg);
1634
            throw new InvalidRequest("4952", msg);
1635
                        
1636
        } catch (NullPointerException e) {
1637
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
1638
            logMetacat.error(msg);
1639
            throw new InvalidRequest("4952", msg);
1640
            
1641
        }
1642
        result = CNodeService.getInstance(request).deleteReplicationMetadata(
1643
                session, identifier, nodeId, serialVersion);
1644
        response.setStatus(200);
1645
        response.setContentType("text/xml");
1646
        return result;
1647

    
1648
    }
1649

    
1650
    /**
1651
     * Pass the request to set the replication status to CNodeService
1652
     * 
1653
     * @param pid
1654
     *            the identifier of the object to set the replication status on
1655
     * 
1656
     * @throws ServiceFailure
1657
     * @throws NotImplemented
1658
     * @throws InvalidToken
1659
     * @throws NotAuthorized
1660
     * @throws InvalidRequest
1661
     * @throws NotFound
1662
     * @throws JiBXException 
1663
     * @throws IllegalAccessException 
1664
     * @throws InstantiationException 
1665
     * @throws IOException 
1666
     */
1667
    public boolean setReplicationStatus(String pid) throws ServiceFailure,
1668
            NotImplemented, InvalidToken, NotAuthorized, InvalidRequest,
1669
            NotFound {
1670
        
1671
        boolean result = false;
1672
        Identifier identifier = new Identifier();
1673
        identifier.setValue(pid);
1674
        BaseException failure = null;
1675
        ReplicationStatus status = null;
1676
        String replicationStatus = null;
1677
        NodeReference targetNodeRef = null;
1678
        String targetNode = null;
1679

    
1680
        // Parse the params out of the multipart form data
1681
        // Read the incoming data from its Mime Multipart encoding
1682
        logMetacat.debug("Parsing ReplicaStatus from the mime multipart entity");
1683

    
1684
        try {
1685
        	// parse the failure, if we have it
1686
            Map<String, File> files = collectMultipartFiles();        
1687
            if (files.containsKey("failure")) {
1688
            	failure = ExceptionHandler.deserializeXml(new FileInputStream(files.get("failure")), 
1689
                        "Replication failed for an unknown reason.");
1690
            }
1691
            
1692
        } catch (Exception e2) {
1693
            throw new ServiceFailure("4700", "Couldn't resolve the multipart request: " +
1694
                e2.getMessage());
1695
            
1696
        }
1697
        
1698
        // get the replication status param
1699
        try {
1700
            replicationStatus = multipartparams.get("status").get(0);
1701
            status = ReplicationStatus.convert(replicationStatus);
1702

    
1703
        } catch (NullPointerException npe) {
1704

    
1705
            logMetacat.debug("The 'status' parameter was not found in the "
1706
                    + "multipartparams map.  Trying the params map.");
1707

    
1708
            try {
1709
                replicationStatus = params.get("status")[0];
1710
                status = ReplicationStatus.convert(replicationStatus
1711
                        .toLowerCase());
1712

    
1713
            } catch (Exception e) {
1714
                String msg = "The 'status' must be provided as a parameter and was not.";
1715
                logMetacat.error(msg);
1716
                throw new InvalidRequest("4730", msg);
1717

    
1718
            }
1719

    
1720
        }
1721

    
1722
        // get the target node reference param
1723
        try {
1724
            targetNode = multipartparams.get("nodeRef").get(0);
1725
            targetNodeRef = new NodeReference();
1726
            targetNodeRef.setValue(targetNode);
1727

    
1728
        } catch (NullPointerException e) {
1729
            logMetacat.debug("The 'nodeRef' parameter was not found in the "
1730
                    + "multipartparams map.  Trying the params map.");
1731

    
1732
            try {
1733
                targetNode = params.get("nodeRef")[0];
1734
                targetNodeRef = new NodeReference();
1735
                targetNodeRef.setValue(targetNode);
1736

    
1737
            } catch (Exception e1) {
1738
                String msg = "The 'nodeRef' must be provided as a parameter and was not.";
1739
                logMetacat.error(msg);
1740
                throw new InvalidRequest("4730", msg);
1741

    
1742
            }
1743

    
1744
        }
1745

    
1746
        result = CNodeService.getInstance(request).setReplicationStatus(
1747
                session, identifier, targetNodeRef, status, failure);
1748
        response.setStatus(200);
1749
        response.setContentType("text/xml");
1750
        return result;
1751

    
1752
    }
1753

    
1754
    /**
1755
     * Pass the request to update the replication metadata to CNodeService
1756
     * 
1757
     * @param pid
1758
     *            the identifier of the object to update the replication
1759
     *            metadata on
1760
     * 
1761
     * @throws ServiceFailure
1762
     * @throws NotImplemented
1763
     * @throws InvalidToken
1764
     * @throws NotAuthorized
1765
     * @throws InvalidRequest
1766
     * @throws NotFound
1767
     * @throws VersionMismatch 
1768
     * @throws JiBXException 
1769
     * @throws IOException 
1770
     * @throws IllegalAccessException 
1771
     * @throws InstantiationException 
1772
     */
1773
    public boolean updateReplicationMetadata(String pid) throws ServiceFailure,
1774
            NotImplemented, InvalidToken, NotAuthorized, InvalidRequest,
1775
            NotFound, VersionMismatch, InstantiationException, IllegalAccessException, IOException, JiBXException {
1776

    
1777
        boolean result = false;
1778
        long serialVersion = 0L;
1779
        String serialVersionStr = null;
1780
        Identifier identifier = new Identifier();
1781
        identifier.setValue(pid);
1782

    
1783
        // parse the replica
1784
        Map<String, File> files = collectMultipartFiles();        
1785
        Replica replica = TypeMarshaller.unmarshalTypeFromFile(Replica.class, files.get("replicaMetadata"));
1786

    
1787
        // get the serialVersion
1788
        try {
1789
            serialVersionStr = multipartparams.get("serialVersion").get(0);
1790
            serialVersion = new Long(serialVersionStr).longValue();
1791

    
1792
        } catch (NullPointerException e) {
1793
            String msg = "The 'serialVersion' must be provided as a parameter and was not.";
1794
            logMetacat.error(msg);
1795
            throw new InvalidRequest("4853", msg);
1796

    
1797
        }
1798

    
1799
        result = CNodeService.getInstance(request).updateReplicationMetadata(
1800
                session, identifier, replica, serialVersion);
1801
        response.setStatus(200);
1802
        response.setContentType("text/xml");
1803
        return result;
1804

    
1805
    }
1806
    
1807
    /**
1808
     * Update the system metadata for a specified pid
1809
     * @throws ServiceFailure
1810
     * @throws InvalidRequest
1811
     * @throws InstantiationException
1812
     * @throws IllegalAccessException
1813
     * @throws IOException
1814
     * @throws JiBXException
1815
     * @throws NotImplemented
1816
     * @throws NotAuthorized
1817
     * @throws InvalidSystemMetadata
1818
     * @throws InvalidToken
1819
     */
1820
    protected void updateSystemMetadata() throws ServiceFailure, InvalidRequest, InstantiationException, 
1821
                        IllegalAccessException, IOException, JiBXException, NotImplemented, NotAuthorized, InvalidSystemMetadata, InvalidToken {
1822
        // Read the incoming data from its Mime Multipart encoding
1823
        Map<String, File> files = collectMultipartFiles();
1824
        
1825
        // get the encoded pid string from the body and make the object
1826
        String pidString = multipartparams.get("pid").get(0);
1827
        Identifier pid = new Identifier();
1828
        pid.setValue(pidString);
1829
        
1830
        logMetacat.debug("updateSystemMetadata: " + pid);
1831

    
1832
        // get the system metadata from the request
1833
        File smFile = files.get("sysmeta");
1834
        FileInputStream sysmeta = new FileInputStream(smFile);
1835
        SystemMetadata systemMetadata = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysmeta);
1836

    
1837
        logMetacat.debug("updating system metadata with pid " + pid.getValue());
1838
        
1839
        CNodeService.getInstance(request).updateSystemMetadata(session, pid, systemMetadata);
1840
    }
1841
    
1842
    private void doViews(String format, String pid) {
1843
        
1844
        OutputStream out = null;
1845
        CNodeService cnode = CNodeService.getInstance(request);
1846

    
1847
        try {
1848
            // get a list of views
1849
            if (pid != null) {
1850
                Identifier identifier = new Identifier();
1851
                identifier.setValue(pid);
1852
                InputStream stream = cnode.view(session, format, identifier);
1853

    
1854
                // set the content-type if we have it from the implementation
1855
                if (stream instanceof ContentTypeInputStream) {
1856
                    response.setContentType(((ContentTypeInputStream) stream).getContentType());
1857
                }
1858
                response.setStatus(200);
1859
                out = response.getOutputStream();
1860
                // write the results to the output stream
1861
                IOUtils.copyLarge(stream, out);
1862
                return;
1863
            } else {
1864
                // TODO: list the registered views
1865
                //BaseException ni = new NotImplemented("9999", "MN.listViews() is not implemented at this node");
1866
                //throw ni;
1867
                OptionList list = cnode.listViews(session);
1868
                
1869
                response.setContentType("text/xml");
1870
                response.setStatus(200);
1871
                TypeMarshaller.marshalTypeToOutputStream(list, response.getOutputStream());
1872
            }
1873
            
1874
            
1875
        } catch (BaseException be) {
1876
            // report Exceptions as clearly as possible
1877
            try {
1878
                out = response.getOutputStream();
1879
            } catch (IOException e) {
1880
                logMetacat.error("Could not get output stream from response", e);
1881
            }
1882
            serializeException(be, out);
1883
        } catch (Exception e) {
1884
            // report Exceptions as clearly and generically as possible
1885
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
1886
            try {
1887
                out = response.getOutputStream();
1888
            } catch (IOException ioe) {
1889
                logMetacat.error("Could not get output stream from response", ioe);
1890
            }
1891
            ServiceFailure se = new ServiceFailure("0000", e.getMessage());
1892
            serializeException(se, out);
1893
        }
1894
    }
1895

    
1896
}
(1-1/4)