Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author: Serhan AKIN $'
7
 *     '$Date: 2009-06-13 15:28:13 +0300  $'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23
package edu.ucsb.nceas.metacat.restservice;
24

    
25
import java.io.*;
26
import java.util.*;
27

    
28
import javax.mail.BodyPart;
29
import javax.mail.MessagingException;
30
import javax.mail.internet.MimeMultipart;
31
import javax.servlet.ServletContext;
32
import javax.servlet.http.HttpServletRequest;
33
import javax.servlet.http.HttpServletResponse;
34
import java.text.DateFormat;
35
import java.text.ParseException;
36
import java.text.ParsePosition;
37
import java.text.SimpleDateFormat;
38

    
39

    
40
import org.apache.commons.httpclient.util.DateParser;
41
import org.apache.commons.io.IOUtils;
42
import org.apache.log4j.Logger;
43
import org.apache.maven.artifact.ant.shaded.IOUtil;
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.NotImplemented;
52
import org.dataone.service.exceptions.ServiceFailure;
53
import org.dataone.service.exceptions.UnsupportedType;
54
import org.dataone.service.exceptions.NotFound;
55
import org.dataone.service.types.*;
56
import org.jibx.runtime.BindingDirectory;
57
import org.jibx.runtime.IBindingFactory;
58
import org.jibx.runtime.IMarshallingContext;
59
import org.jibx.runtime.IUnmarshallingContext;
60
import org.jibx.runtime.JiBXException;
61

    
62
import edu.ucsb.nceas.metacat.DBUtil;
63
import edu.ucsb.nceas.metacat.IdentifierManager;
64
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
65
import edu.ucsb.nceas.metacat.MetacatHandler;
66
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
67
import edu.ucsb.nceas.metacat.dataone.CrudService;
68
import edu.ucsb.nceas.metacat.properties.PropertyService;
69
import edu.ucsb.nceas.metacat.service.SessionService;
70
import edu.ucsb.nceas.metacat.util.RequestUtil;
71
import edu.ucsb.nceas.metacat.util.SessionData;
72
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
73

    
74
import org.dataone.service.streaming.util.StreamUtil;
75

    
76
import com.gc.iotools.stream.is.InputStreamFromOutputStream;
77
/**
78
 * 
79
 * Implements Earthgrid REST API to Metacat <br/><br/> 
80
 * 
81
 * <ul>
82
 * <li>
83
 * <h3> EarthGrid Query Service</h3>
84
 * <ul><li>
85
 * <h3>Get & Authenticated Get:</h3> 
86
 * is equal to Metacat's read action and returns a data file having
87
 * the specified <doc-id> in the resource path. For authenticated Get service, a session id must be provided 
88
 * in the query string. <br/><br/>
89
 * 
90
 * <b>REST URL:</b> <code>GET, [context-root]/object/[doc-id]?sessionid=[sessionid] </code><br/>
91
 * <b>Returns:</b> data file <br/><br/>
92
 * </li>
93
 * 
94
 * <li>
95
 * <h3>Query & Authenticated Query:</h3> 
96
 * Metacat's equivalent is squery action but this function 
97
 * receives a Earthgrid query document and returns Earthgrid resultset document by transforming those documents
98
 * to Metacat's equivalents by the means of Metacat Implementation in Earthgrid library. For authenticated Query service 
99
 * a session id must be provided in the query string. See Earthgrid (a.k.a. Ecogrid) project for XSD files of 
100
 * query and resultset documents<br/><br/>
101
 * 
102
 * <b>REST URL:</b> <code>POST, [context-root]/object?sessionid=[sessionid]</code>    <br/>
103
 * <b>POST Data:</b> Earthgrid query document , Content-type: <code>text/xml</code><br/>
104
 * <b>Returns:</b> Earthgrid resultset document<br/><br/>
105
 * 
106
 * </li>
107
 * </ul>
108
 * 
109
 * </li>
110
 * 
111
 * <li>
112
 * <h3> EarthGrid Authentication Service</h3>
113
 * <ul><li>
114
 * <h3>Login: </h3> 
115
 * Receives username and password parameters in POST data and 
116
 * returns SessionId (in XML format) or failure message and uses MetacatHandler's handleLoginAction function<br/><br/>
117
 *  
118
 * <b>REST URL:</b> <code>POST, [context-root]/session?op=login</code> <br/>
119
 * <b>POST Data:</b> username=[username]&password=[password], Content-type: <code>application/x-www-form-urlencoded</code>
120
 * <b>Returns:</b> sessionId in XML format<br/><br/>
121
 * </li>
122
 * 
123
 * <li>
124
 * <h3>Logout: </h3> 
125
 * Receives session Id parameters in querystring and returns xml message, calls 
126
 * MetacatHandler's handleLogoutAction function<br/><br/>
127
 *  
128
 * <b>REST URL:</b> <code>GET, [context-root]/session?op=logout&sessionid=[sessionid]</code>   <br/>
129
 * <b>Returns:</b> message in XML format<br/><br/>
130
 * </li>
131
 * </ul>
132
 * 
133
 * <li>
134
 * <h3>EarthGrid Put Service</h3>
135
 * 
136
 * <ul>
137
 * <li><h3>Update/Insert: </h3>     
138
 * <br/>
139
 * <b>REST URL:</b> <code>PUT, [context-root]/object/[doc-id]?op={update|insert}&sessionid=[sessionid]</code>   <br/>
140
 * <b>POST Data:</b> document object, Content-type: <code>text/xml</code><br/>
141
 * <b>Returns:</b> message in XML format<br/><br/>
142
 * </li>
143
 * 
144
 * <li><h3>Delete: </h3>        
145
 * <br/>
146
 * <b>REST URL:</b> <code>DELETE, [context-root]/object/[doc-id]?sessionid=[sessionid]</code>   <br/>
147
 * <b>Returns:</b> message in XML format<br/><br/>
148
 * </li>
149

    
150
 * </ul>
151
 * </li>
152
 * 
153
 * <li>
154
 * <h3>EarthGrid Identifier Service</h3><br/>
155
 * 
156
 * <ul>
157
 * <li><h3>isRegistered: </h3>      <br/>
158
 * <b>REST URL:</b> <code>GET, [context-root]/identifier/[doc-id]?op=isregistered</code>   <br/>
159
 * <b>Returns:</b> message in XML format<br/><br/>
160
 * </li>
161

    
162
 * <li><h3>getAllDocIds:</h3>       <br/>       
163
 * <b>REST URL:</b> <code>GET, [context-root]/identifier?op=getalldocids</code>   <br/>
164
 * <b>Returns:</b> document id list in XML format<br/><br/>
165
 * </li>
166
 * 
167
 * <li><h3>addLSID Function:</h3> 
168
 * Metacat does not support this function       <br/>
169
 * <b>REST URL:</b> <code>PUT, [context-root]/identifier/[doc-id]</code>   <br/>
170
 * <b>Returns:</b> error message in XML format<br/><br/>
171
 * </li>
172
 * 
173
 * <li><h3>getNextRevision:</h3>        <br/>
174
 * <b>REST URL:</b> <code>GET, [context-root]/identifier/[doc-id]?op=getnextrevision</code>   <br/>
175
 * <b>Returns:</b> message in XML format<br/><br/>
176
 * </li>
177
 * 
178
 * <li><h3>getNextObject:</h3>      <br/>
179
 * <b>REST URL:</b> <code>GET, [context-root]/identifier?op=getnextobject&scope=[scope]</code>   <br/>
180
 * <b>Returns:</b> message in XML format<br/><br/>
181
 * </li>
182
 * 
183
 * </li>
184
 * </ul>
185
 * 
186
 */
187
public class ResourceHandler {
188

    
189
    /**HTTP Verb GET*/
190
    public static final byte GET = 1;
191
    /**HTTP Verb POST*/
192
    public static final byte POST = 2;
193
    /**HTTP Verb PUT*/
194
    public static final byte PUT = 3;
195
    /**HTTP Verb DELETE*/
196
    public static final byte DELETE = 4;
197
    /**HTTP Verb HEAD*/
198
    public static final byte HEAD = 5;
199

    
200
    /*
201
     * API Resources
202
     */
203
    private static final String RESOURCE_OBJECTS = "object";
204
    private static final String RESOURCE_META = "meta";
205
    private static final String RESOURCE_SESSION = "session";
206
    private static final String RESOURCE_IDENTIFIER = "identifier";
207
    private static final String RESOURCE_LOG = "log";
208
    private static final String RESOURCE_CHECKSUM = "checksum";
209
    private static final String RESOURCE_BASE_URL = "d1";
210

    
211
    /*
212
     * API Functions used as URL parameters
213
     */
214
    private static final String FUNCTION_KEYWORD = "op";
215
    private static final String FUNCTION_NAME_LOGIN = "login";
216
    private static final String FUNCTION_NAME_LOGOUT = "logout";
217
    private static final String FUNCTION_NAME_SET_ACCESS = "setaccess";
218
    private static final String FUNCTION_NAME_ISREGISTERED = "isregistered";
219
    private static final String FUNCTION_NAME_GETALLDOCS = "getalldocids";
220
    private static final String FUNCTION_NAME_GETNEXTREV = "getnextrevision";
221
    private static final String FUNCTION_NAME_GETNEXTOBJ = "getnextobject";
222
    private static final String FUNCTION_NAME_INSERT = "insert";
223
    private static final String FUNCTION_NAME_UPDATE = "update";
224
    private static final String FUNCTION_NAME_GENERATE_MISSING_SYSTEM_METADATA = "generatemissingsystemmetadata";
225

    
226
    private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
227
    
228
    private ServletContext servletContext;
229
    private Logger logMetacat;
230
    private MetacatHandler handler;
231
    private HttpServletRequest request;
232
    private HttpServletResponse response;
233
    private String username;
234
    private String password;
235
    private String sessionId;
236
    private String[] groupNames;
237

    
238
    private Hashtable<String, String[]> params;
239

    
240
    /**Initializes new instance by setting servlet context,request and response*/
241
    public ResourceHandler(ServletContext servletContext,
242
            HttpServletRequest request, HttpServletResponse response) {
243
        this.servletContext = servletContext;
244
        this.request = request;
245
        this.response = response;
246
    }
247

    
248
    /**
249
     * This function is called from REST APU servlet and handles each request to the servlet 
250
     * 
251
     * @param httpVerb (GET, POST, PUT or DELETE)
252
     */
253
    public void handle(byte httpVerb) {
254
        logMetacat = Logger.getLogger(ResourceHandler.class);
255
        try {
256
            String resource = request.getServletPath();
257
            if(resource.endsWith("d1/") || resource.endsWith("d1"))
258
            {
259
                resource = RESOURCE_BASE_URL;
260
            }
261
            else
262
            {
263
                //substring off the /d1/
264
                resource = resource.substring(resource.indexOf("d1/") + 3, resource.length());
265
                resource = resource.trim();
266
            }
267
            
268
            String verb = "";
269
            
270
            System.out.println("handling verb " + httpVerb + " request with resource '" + resource + "'");
271
            boolean status = false;
272
            loadSessionData();
273

    
274
            if (resource != null) {
275
                //resource = request.getServletPath().substring(1);
276

    
277
                params = new Hashtable<String, String[]>();
278
                initParams();
279

    
280
                Timer timer = new Timer();
281
                handler = new MetacatHandler(timer);
282

    
283
                if(resource.equals(RESOURCE_BASE_URL)) {
284
                    //node registry response
285
                    System.out.println("Using resource 'd1' (node registry response)");
286
                    createNodeResponse();
287
                    status = true;
288
                    
289
                } else if (resource.equals(RESOURCE_SESSION) && 
290
                        httpVerb == POST && 
291
                        params.get(FUNCTION_KEYWORD) != null) {
292
                    System.out.println("Using resource 'session'");
293
                    //System.out.println("function_keyword: " + params.get(FUNCTION_KEYWORD)[0]);
294
                    if (params.get(FUNCTION_KEYWORD)[0].equals(FUNCTION_NAME_LOGIN)) {
295
                        login();
296
                        status = true;
297
                    } else if (params.get(FUNCTION_KEYWORD)[0].equals(FUNCTION_NAME_LOGOUT)) {
298
                        logout();
299
                        status = true;
300
                    } else if (params.get(FUNCTION_KEYWORD)[0].equals(FUNCTION_NAME_SET_ACCESS)) {
301
                        setaccess();
302
                        status = true;
303
                        //System.out.println("done setting access");
304
                    }
305
                } else if (resource.equals(RESOURCE_META)) {
306
                    System.out.println("Using resource 'meta'");
307
                    if(params != null && params.get(FUNCTION_KEYWORD) != null &&
308
                            params.get(FUNCTION_KEYWORD)[0].equals(FUNCTION_NAME_GENERATE_MISSING_SYSTEM_METADATA))
309
                    { //generate system metadata for any object that is
310
                        //a) not system metadata itself
311
                        //b) does not already have a system metadata id in the systemmetadata table
312
                        //c) not a BIN object (data)
313
                        //TODO: check if we need this anymore.  Might be superceded
314
                        //by MetacatPopulator
315
                        generateMissingSystemMetadata();
316
                        status = true;
317
                    }
318
                    else
319
                    {
320
                        String objectId = request.getPathInfo();
321
                        if (objectId != null && objectId.length() > 1) 
322
                        {
323
                            objectId = request.getPathInfo().substring(1);
324
                        }
325
                        getSystemMetadataObject(objectId);
326
                        status = true;
327
                    }
328

    
329
                } else if (resource.equals(RESOURCE_OBJECTS)) {
330
                    System.out.println("Using resource 'object'");
331
                    logMetacat.debug("D1 Rest: Starting resource processing...");
332
                    loadSessionData();
333

    
334
                    String objectId = request.getPathInfo();
335
                    if (objectId != null && objectId.length() > 1) 
336
                    {
337
                        objectId = request.getPathInfo().substring(1);
338
                    }
339
                    else
340
                    {
341
                        objectId = null;
342
                    }
343

    
344
                    logMetacat.debug("verb:" + httpVerb);
345

    
346
                    if (httpVerb == GET) {
347
                        getObject(objectId);
348
                        status = true;
349
                    } else if (httpVerb == POST) {
350
                        putObject(objectId, FUNCTION_NAME_INSERT);
351
                        status = true;
352
                    } else if (httpVerb == PUT) {
353
                        putObject(objectId, FUNCTION_NAME_UPDATE);
354
                        status = true;
355
                    } else if (httpVerb == DELETE) {
356
                        deleteObject(objectId);
357
                        status = true;
358
                    } else if (httpVerb == HEAD) {
359
                        describeObject(objectId);
360
                        status = true;
361
                    }
362
                    
363

    
364
                } else if (resource.equals(RESOURCE_IDENTIFIER)) {
365
                    System.out.println("Using resource 'identifier'");
366
                    String identifierId = request.getPathInfo();
367
                    if (identifierId != null && identifierId.length() > 1)
368
                        identifierId = request.getPathInfo().substring(1); //trim the slash
369

    
370
                    if (httpVerb == GET) {
371
                        String op = params.get(FUNCTION_KEYWORD)[0];
372
                        if (op.equals(FUNCTION_NAME_ISREGISTERED)) {
373
                            isRegistered(identifierId);
374
                            status = true;
375
                        } else if (op.equals(FUNCTION_NAME_GETALLDOCS)) {
376
                            getAllDocIds();
377
                            status = true;
378
                        } else if (op.equals(FUNCTION_NAME_GETNEXTREV)) {
379
                            getNextRevision(identifierId);
380
                            status = true;
381
                        } else if (op.equals(FUNCTION_NAME_GETNEXTOBJ)) {
382
                            getNextObject();
383
                            status = true;
384
                        } 
385

    
386
                    } else if (httpVerb == PUT) {
387
                        //Earthgrid API > Identifier Service > addLSID Function 
388
                        response.setStatus(501);
389
                        printError(
390
                                "This method is not supported by metacat.  To "
391
                                + "add a new LSID, add a document to metacat.",
392
                                response);
393
                        status = true;
394
                    }
395

    
396
                } else if (resource.equals(RESOURCE_LOG)) {
397
                    System.out.println("Using resource 'log'");
398
                    //handle log events
399
                    if(httpVerb == GET)
400
                    {
401
                        getLog();
402
                        status = true;
403
                    }
404
                    else
405
                    {
406
                        //change to D1 spec for specifying which http methods are allowed for a resource
407
                        response.setStatus(501);
408
                        printError("POST, PUT, DELETE is not supported for logs.", response);
409
                        status = true;
410
                    }
411

    
412
                } else if(resource.equals(RESOURCE_CHECKSUM)) {
413
                    System.out.println("Using resource 'checksum'");
414
                    //handle checksum requests
415
                    if(httpVerb == GET)
416
                    {
417
                        String guid = null;
418
                        String checksumAlgorithm = "MD5";
419
                    
420
                        try
421
                        {
422
                           guid = params.get("id")[0];
423
                        }
424
                        catch(Exception e)
425
                        {
426
                            throw new InvalidRequest("1402", "Incorrect parameters passed to getChecksum");
427
                        }
428
                        
429
                        Identifier guidid = new Identifier();
430
                        guidid.setValue(guid);
431
                        AuthToken token = new AuthToken(sessionId);
432
                        try
433
                        {
434
                            checksumAlgorithm = params.get("checksumAlgorithm")[0];
435
                        }
436
                        catch(Exception e)
437
                        {
438
                            //do nothing.  default to MD5
439
                        }
440
                        System.out.println("getting checksum for object " + guid +
441
                                " with algorithm " + checksumAlgorithm);
442
                        try
443
                        {
444
                            Checksum c = CrudService.getInstance().getChecksum(token, guidid, checksumAlgorithm);
445
                            System.out.println("got checksum " + c.getValue());
446
                            response.setStatus(200);
447
                            System.out.println("serializing response");
448
                            serializeServiceType(Checksum.class, c, response.getOutputStream());
449
                            System.out.println("done serializing response.");
450
                        }
451
                        catch(NotAuthorized na)
452
                        {
453
                            na.setDetail_code("1400");
454
                            serializeException(na, response.getOutputStream());
455
                        }
456
                        catch(NotFound nf)
457
                        {
458
                            nf.setDetail_code("1420");
459
                            serializeException(nf, response.getOutputStream());
460
                        }
461
                        catch(InvalidRequest ir)
462
                        {
463
                            ir.setDetail_code("1402");
464
                            serializeException(ir, response.getOutputStream());
465
                        }
466
                        catch(ServiceFailure sf)
467
                        {
468
                            sf.setDetail_code("1410");
469
                            serializeException(sf, response.getOutputStream());
470
                        }
471
                        catch(InvalidToken it)
472
                        {
473
                            it.setDetail_code("1430");
474
                            serializeException(it, response.getOutputStream());
475
                        }
476
                        status = true;
477
                    }
478
                }
479
                    
480
                if (!status)
481
                {
482
                    response.setStatus(400);
483
                    printError("Incorrect parameters!", response);
484
                }
485
            } else {
486
                response.setStatus(400);
487
                printError("Incorrect resource!", response);
488
            }
489
        } catch (Exception e) {
490
            logMetacat.error(e.getMessage());
491
            e.printStackTrace();
492
        }
493
    }
494
    
495
    /**
496
     * create the root node registry response.  
497
     * @throws JiBXException
498
     * @throws IOException
499
     */
500
    private void createNodeResponse() 
501
        throws JiBXException, IOException
502
    {
503
        NodeList nl = new NodeList();
504
        Node n = new Node();
505
        NodeReference nr = new NodeReference();
506
        nr.setValue(request.getRequestURL().toString());
507
        n.setIdentifier(nr);
508
        n.setBaseURL(request.getRequestURL().toString());
509
        n.setDescription("Metacat DataONE Node");
510
        n.setName("Metacat");
511
        n.setType(NodeType.convert("mn"));
512
        
513
        //create the services
514
        Service mnCrud03 = new Service();
515
        mnCrud03.setName("Metacat MN_Crud Services Version 0.3");
516
        mnCrud03.setVersion("0.3");
517
        
518
        Service mnCrud04 = new Service();
519
        mnCrud04.setName("Metacat MN_Crud Services Version 0.4");
520
        mnCrud04.setVersion("0.4");
521
        
522
        Service mnCrud09 = new Service();
523
        mnCrud09.setName("Metacat MN_Crud Services Version 0.9");
524
        mnCrud09.setVersion("0.9");
525
        
526
        Service mnReplication03 = new Service();
527
        mnReplication03.setName("Metcat MN_Replication Version 0.3");
528
        mnReplication03.setVersion("0.3");
529
        
530
        Service mnHealth04 = new Service();
531
        mnHealth04.setName("Metacat MN_Health Version 0.4");
532
        mnHealth04.setVersion("0.4");
533
        
534
        Service mnHealth06 = new Service();
535
        mnHealth06.setName("Metacat MN_Health Version 0.6");
536
        mnHealth06.setVersion("0.6");
537
        
538
        Service mnAuthentication07 = new Service();
539
        mnAuthentication07.setName("Metacat MN_Authentication Version 0.7");
540
        mnAuthentication07.setVersion("0.7");
541
        
542
        Service mnAuthorization07 = new Service();
543
        mnAuthorization07.setName("Metacat MN_Authorization Version 0.7");
544
        mnAuthorization07.setVersion("0.7");
545
        
546
                                    //name, rest, implemented
547
        mnCrud03.addMethod(getServiceMethod("MN_crud.get()", "/object/<guid>", true));
548
        mnCrud03.addMethod(getServiceMethod("MN_crud.getSystemMetadata()", "/meta/<guid>", true));
549
        mnCrud04.addMethod(getServiceMethod("MN_crud.create()", "/object/<guid>", true));
550
        mnCrud04.addMethod(getServiceMethod("MN_crud.update()", "/object/<guid>", true));
551
        mnCrud09.addMethod(getServiceMethod("MN_crud.delete()", "/object/<guid>", true));
552
        mnCrud03.addMethod(getServiceMethod("MN_crud.describe()", "/object/<guid>", true));
553
        mnCrud03.addMethod(getServiceMethod("MN_crud.getChecksum()", "/checksum/<guid>", true));
554
        mnCrud03.addMethod(getServiceMethod("MN_crud.getLogRecords()", "/log", true));
555
        mnReplication03.addMethod(getServiceMethod("MN_replication.listObjects()", "/object", true));
556
        mnHealth04.addMethod(getServiceMethod("MN_health.ping()", "/health/ping", false));
557
        mnHealth04.addMethod(getServiceMethod("MN_health.getObjectStatistics()", "/monitor/object/<guid>", false));
558
        mnHealth06.addMethod(getServiceMethod("MN_health.getStatus()", "/health/status", false));
559
        mnAuthentication07.addMethod(getServiceMethod("MN_authentication.login()", "/account/login", false));
560
        mnAuthentication07.addMethod(getServiceMethod("MN_authentication.logout()", "/account/logout", false));
561
        mnAuthorization07.addMethod(getServiceMethod("MN_authorization.isAuthorized()", "/isAuthorized/<guid>", false));
562
        
563
        Services ss = new Services();
564
        ss.addService(mnCrud03);
565
        ss.addService(mnCrud04);
566
        ss.addService(mnCrud09);
567
        ss.addService(mnReplication03);
568
        ss.addService(mnHealth04);
569
        ss.addService(mnHealth06);
570
        ss.addService(mnAuthentication07);
571
        ss.addService(mnAuthorization07);
572
        n.setServices(ss);
573
        nl.addNode(n);
574
        serializeServiceType(NodeList.class, nl, response.getOutputStream());
575
    }
576
    
577
    /**
578
     * MN_crud.describe()
579
     * http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.describe
580
     * @param guid
581
     */
582
    private void describeObject(String guid)
583
    {
584
        Logger logMetacat = Logger.getLogger(ResourceHandler.class);
585
        OutputStream out = null;
586
        try
587
        {
588
            out = response.getOutputStream();
589
        }
590
        catch(Exception e)
591
        {
592
            logMetacat.error("Error getting output stream in ResourceHandler.describeObject: " + e.getMessage());
593
            return;
594
        }
595
        response.setStatus(200);
596
        response.setContentType("text/xml");
597
        AuthToken token = new AuthToken(sessionId);
598
        CrudService cs = CrudService.getInstance();
599
        Identifier id = new Identifier();
600
        id.setValue(guid);
601
        try
602
        {
603
            DescribeResponse dr = cs.describe(token, id);
604
            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SZ");
605
            response.addHeader("guid", guid);
606
            response.addHeader("checksum", dr.getDataONE_Checksum().getValue());
607
            response.addHeader("checksum_algorithm", dr.getDataONE_Checksum().getAlgorithm().name());
608
            response.addHeader("content_length", dr.getContent_Length() + "");
609
            response.addHeader("last_modified", dateFormat.format(dr.getLast_Modified()));
610
            response.addHeader("format", dr.getDataONE_ObjectFormat().toString());
611
        }
612
        catch(InvalidRequest ir)
613
        {
614
            serializeException(ir, out);
615
        }
616
        catch(NotImplemented ni)
617
        {
618
            serializeException(ni, out);
619
        }
620
        catch(NotAuthorized na)
621
        {
622
            serializeException(na, out);
623
        }
624
        catch(ServiceFailure sf)
625
        {
626
            serializeException(sf, out);
627
        }
628
        catch(NotFound nf)
629
        {
630
            serializeException(nf, out);
631
        }
632
        catch(InvalidToken it)
633
        {
634
            serializeException(it, out);
635
        }
636
    }
637
    
638
    /**
639
     * get the logs from the CrudService based on passed params.  Available 
640
     * params are token, fromDate, toDate, event.  See 
641
     * http://mule1.dataone.org/ArchitectureDocs/mn_api_crud.html#MN_crud.getLogRecords
642
     * for more info
643
     */
644
    private void getLog()
645
    {
646
        OutputStream out = null;
647
        try
648
        {
649
            out = response.getOutputStream();
650
            response.setStatus(200);
651
            response.setContentType("text/xml");
652
            AuthToken token = new AuthToken(sessionId);
653
            String fromDateS = params.get("fromDate")[0];
654
            System.out.println("param fromDateS: " + fromDateS);
655
            Date fromDate = null;
656
            String toDateS = params.get("toDate")[0];
657
            System.out.println("param toDateS: " + toDateS);
658
            Date toDate = null;
659
            String eventS = params.get("event")[0];
660
            Event event = null;
661
            if(fromDateS != null)
662
            {
663
                //fromDate = dateFormat.parse(fromDateS);
664
                fromDate = parseDateAndConvertToGMT(fromDateS);
665
            }
666
            if(toDateS != null)
667
            {
668
                //toDate = dateFormat.parse(toDateS);
669
                toDate = parseDateAndConvertToGMT(toDateS);
670
            }
671
            if(eventS != null)
672
            {
673
                event = Event.convert(eventS);
674
            }
675
            System.out.println("fromDate: " + fromDate + " toDate: " + toDate);
676
            
677
            System.out.println("calling crudservice.getLogRecords");
678
            Log log = CrudService.getInstance().getLogRecords(token, fromDate, toDate, event);
679
            serializeServiceType(Log.class, log, out);
680
        }
681
        catch(Exception e)
682
        {
683
            String msg = "Could not get logs from CrudService: " + e.getMessage();
684
            response.setStatus(500);
685
            ServiceFailure sf = new ServiceFailure("1490", msg);
686
            logMetacat.error(msg);
687
            e.printStackTrace();
688
            serializeException(sf, out);
689
        }
690
    }
691
    
692
    /**
693
     *  copies request parameters to a hashtable which is given as argument to native metacathandler functions  
694
     */
695
    private void initParams() {
696

    
697
        String name = null;
698
        String[] value = null;
699
        Enumeration paramlist = request.getParameterNames();
700
        while (paramlist.hasMoreElements()) {
701
            name = (String) paramlist.nextElement();
702
            value = request.getParameterValues(name);
703
            params.put(name, value);
704
        }
705
    }
706

    
707
    /**
708
     * 
709
     * Load user details of metacat session from the request 
710
     * 
711
     */
712
    private void loadSessionData()
713
      throws Exception
714
    {
715
        SessionData sessionData = RequestUtil.getSessionData(request);
716
        try
717
        {
718
            username = null;
719
            password = null;
720
            groupNames = null;
721
            sessionId = null;
722
            
723
            boolean validSession = false;
724
            SessionService ss = SessionService.getInstance();
725
            System.out.println("sessionData: " + sessionData);
726
            if(sessionData == null)
727
            {
728
                username = "public";
729
                sessionId = "0";
730
                System.out.println("sessiondata is null.  Creating a public session.");
731
                return;
732
            }
733
            
734
            System.out.println("username: " + sessionData.getUserName());
735
            System.out.println("sessionid: " + sessionData.getId());
736
            //validate the session
737
            if(ss.isSessionRegistered(sessionData.getId()) && 
738
               !(sessionData.getUserName().equals("public") || sessionData.getId().equals("0")))
739
            {
740
                validSession = true;
741
            }
742
            
743
            if(validSession)
744
            {
745
                //if the session is valid, set these variables
746
                username = sessionData.getUserName();
747
                password = sessionData.getPassword();
748
                groupNames = sessionData.getGroupNames();
749
                sessionId = sessionData.getId();
750
                System.out.println("setting sessionid to " + sessionId);
751
                System.out.println("username: " + username);
752
            }
753
            
754
            //if the session is not valid or the username is null, set
755
            //username to public
756
            if (username == null) 
757
            {
758
                System.out.println("setting username to public.");
759
                username = "public";
760
            }
761
        }
762
        catch(Exception e)
763
        {
764
            e.printStackTrace();
765
            throw new Exception("Could not load the session data: " + e.getMessage());
766
        }
767
    }
768
    
769
    /**
770
     * generate missing system metadata for any science metadata objects
771
     * that don't already have it. https://trac.dataone.org/ticket/591
772
     * 
773
     * called with POST meta/?op=generatemissingsystemmetadata
774
     */
775
    private void generateMissingSystemMetadata()
776
    {
777
        AuthToken token = new AuthToken(sessionId);
778
        CrudService.getInstance().generateMissingSystemMetadata(token);
779
    }
780

    
781
    /**
782
     *  Earthgrid API > Identifier Service > isRegistered Function : 
783
     *  calls MetacatHandler > handleIdIsRegisteredAction
784
     * @param guid
785
     * @throws IOException
786
     */
787
    private void isRegistered(String guid) throws IOException
788
    {
789
        
790
        // Look up the localId for this guid
791
        IdentifierManager im = IdentifierManager.getInstance();
792
        String localId = "";
793
        try {
794
            localId = im.getLocalId(guid);
795
        } catch (McdbDocNotFoundException e) {
796
            // TODO: Need to return the proper DataONE exception
797
        }
798
        
799
        params.put("docid", new String[] { localId });
800
        PrintWriter out = response.getWriter();
801
        response.setStatus(200);
802
        response.setContentType("text/xml");
803
        handler.handleIdIsRegisteredAction(out, params, response);
804
        out.close();
805
    }
806

    
807
    /**
808
     * Earthgrid API > Identifier Service > getAllDocIds Function : 
809
     * calls MetacatHandler > handleGetAllDocidsAction
810
     * @throws IOException
811
     */
812
    private void getAllDocIds() throws IOException {
813
        PrintWriter out = response.getWriter();
814
        response.setStatus(200);
815
        response.setContentType("text/xml");
816
        handler.handleGetAllDocidsAction(out, params, response);
817
        out.close();
818
    }
819

    
820
    /**
821
     * Earthgrid API > Identifier Service > getNextRevision Function : 
822
     * calls MetacatHandler > handleGetRevisionAndDocTypeAction
823
     * @param guid
824
     * @throws IOException
825
     */
826
    private void getNextRevision(String guid) throws IOException 
827
    {
828
        params.put("docid", new String[] { guid });
829
        PrintWriter out = response.getWriter();
830
        response.setStatus(200);
831
        response.setContentType("text/xml");
832
        //handler.handleGetRevisionAndDocTypeAction(out, params);
833

    
834
        try {
835
            // Make sure there is a docid
836
            if (guid == null || guid.equals("")) {
837
                throw new Exception("User didn't specify docid!");
838
            }
839

    
840
            // Look up the localId for this guid
841
            IdentifierManager im = IdentifierManager.getInstance();
842
            String localId = "";
843
            try {
844
                localId = im.getLocalId(guid);
845
            } catch (McdbDocNotFoundException e) {
846
                // TODO: Need to return the proper DataONE exception
847
            }
848
           
849
            // Create a DBUtil object
850
            DBUtil dbutil = new DBUtil();
851
            // Get a rev and doctype
852
            String revAndDocType = dbutil
853
                    .getCurrentRevisionAndDocTypeForGivenDocument(localId);
854
            int revision = Integer.parseInt(revAndDocType.split(";")[0]) + 1;
855

    
856
            out.println("<?xml version=\"1.0\"?>");
857
            out.print("<next-revision>");
858
            out.print(revision);
859
            out.print("</next-revision>");
860

    
861
        } catch (Exception e) {
862
            // Handle exception
863
            response.setStatus(500);
864
            out.println("<?xml version=\"1.0\"?>");
865
            out.println("<error>");
866
            out.println(e.getMessage());
867
            out.println("</error>");
868
        }
869

    
870
        out.close();
871
    }
872

    
873
    /**
874
     * Earthgrid API > Identifier Service > getNextObject Function : 
875
     * calls MetacatHandler > handleGetMaxDocidAction
876
     * @throws IOException
877
     */
878
    private void getNextObject() throws IOException {
879
        PrintWriter out = response.getWriter();
880
        response.setStatus(200);
881
        response.setContentType("text/xml");
882
        handler.handleGetMaxDocidAction(out, params, response);
883
        out.close();
884
    }
885

    
886
    /**
887
     * Implements REST version of DataONE CRUD API --> get
888
     * @param guid ID of data object to be read
889
     */
890
    private void getObject(String guid) {
891
        CrudService cs = CrudService.getInstance();
892
        cs.setParamsFromRequest(request);
893
        AuthToken token = new AuthToken(sessionId);
894
        OutputStream out = null;
895
        try {
896
            out = response.getOutputStream();
897
            response.setStatus(200);
898
            response.setContentType("text/xml");
899
            if(guid != null)
900
            { //get a specific document                
901
                Identifier id = new Identifier();
902
                id.setValue(guid);
903
                try
904
                {
905
                    if(token == null)
906
                    {
907
                        token = new AuthToken("Public");
908
                    }
909
                    InputStream data = cs.get(token, id);
910
                    IOUtils.copyLarge(data, response.getOutputStream());
911
                }
912
                catch(InvalidToken it)
913
                {
914
                    response.setStatus(500);
915
                    serializeException(it, out); 
916
                }
917
                catch(ServiceFailure sf)
918
                {
919
                    response.setStatus(500);
920
                    serializeException(sf, out); 
921
                }
922
                catch(NotAuthorized na)
923
                {
924
                    response.setStatus(500);
925
                    serializeException(na, out); 
926
                }
927
                catch(NotFound nf)
928
                {
929
                    response.setStatus(500);
930
                    serializeException(nf, out); 
931
                }
932
                catch(NotImplemented ni)
933
                {
934
                    response.setStatus(500);
935
                    serializeException(ni, out); 
936
                }
937
                catch(Exception e)
938
                {
939
                    response.setStatus(500);
940
                    System.out.println("Error with Crud.get().  " +
941
                            "If this is an 'Exception producing data' error, " +
942
                            "go to CrudService.get() for better debugging.  " +
943
                            "Here's the error: " + e.getMessage());
944
                    e.printStackTrace();
945
                    ServiceFailure sf = new ServiceFailure("1030", 
946
                            "IO Error in ResourceHandler.getObject: " + e.getMessage());
947
                    serializeException(sf, out); 
948
                }
949
            }
950
            else
951
            { //call listObjects with specified params
952
                Date startTime = null;
953
                Date endTime = null;
954
                ObjectFormat objectFormat = null;
955
                boolean replicaStatus = false;
956
                int start = 0;
957
                //TODO: make the max count into a const
958
                int count = 1000;
959
                Enumeration paramlist = request.getParameterNames();
960
                while (paramlist.hasMoreElements()) 
961
                { //parse the params and make the crud call
962
                    String name = (String) paramlist.nextElement();
963
                    String[] value = (String[])request.getParameterValues(name);
964
                    /*for(int i=0; i<value.length; i++)
965
                    {
966
                        System.out.println("name: " + name + " value: " + value[i]);
967
                    }*/
968
                    if(name.equals("startTime") && value != null)
969
                    {
970
                        try
971
                        {
972
                          //startTime = dateFormat.parse(value[0]);
973
                            startTime = parseDateAndConvertToGMT(value[0]);
974
                        }
975
                        catch(Exception e)
976
                        {  //if we can't parse it, just don't use the startTime param
977
                            System.out.println("Could not parse startTime: " + value[0]);
978
                            startTime = null;
979
                        }
980
                    }
981
                    else if(name.equals("endTime") && value != null)
982
                    {
983
                        try
984
                        {
985
                          //endTime = dateFormat.parse(value[0]);
986
                            endTime = parseDateAndConvertToGMT(value[0]);
987
                        }
988
                        catch(Exception e)
989
                        {  //if we can't parse it, just don't use the endTime param
990
                            System.out.println("Could not parse endTime: " + value[0]);
991
                            endTime = null;
992
                        }
993
                    }
994
                    else if(name.equals("objectFormat") && value != null)
995
                    {
996
                        objectFormat = ObjectFormat.convert(value[0]);
997
                    }
998
                    else if(name.equals("replicaStatus") && value != null)
999
                    {
1000
                        if(value != null && 
1001
                           value.length > 0 && 
1002
                           (value[0].equals("true") || value[0].equals("TRUE") || value[0].equals("YES")))
1003
                        {
1004
                            replicaStatus = true;
1005
                        }
1006
                    }
1007
                    else if(name.equals("start") && value != null)
1008
                    {
1009
                        start = new Integer(value[0]).intValue();
1010
                    }
1011
                    else if(name.equals("count") && value != null)
1012
                    {
1013
                        count = new Integer(value[0]).intValue();
1014
                    }
1015
                }
1016
                //make the crud call
1017
                System.out.println("token: " + token + " startTime: " + startTime +
1018
                        " endtime: " + endTime + " objectFormat: " + 
1019
                        objectFormat + " replicaStatus: " + replicaStatus + 
1020
                        " start: " + start + " count: " + count);
1021
               
1022
                ObjectList ol = cs.listObjects(token, startTime, endTime, 
1023
                        objectFormat, replicaStatus, start, count);
1024
                
1025
                StringReader sr = new StringReader(ol.toString());                
1026
                out = response.getOutputStream();  
1027
                response.setStatus(200);
1028
                response.setContentType("text/xml");
1029
                // Serialize and write it to the output stream
1030
                
1031
                try {
1032
                    serializeServiceType(ObjectList.class, ol, out);
1033
                } catch (JiBXException e) {
1034
                    throw new ServiceFailure("1190", "Failed to serialize ObjectList: " + e.getMessage());
1035
                }
1036
            }
1037
        } catch (BaseException e) {
1038
                response.setStatus(500);
1039
                serializeException(e, out);
1040
        } catch (IOException e) {
1041
            e.printStackTrace();
1042
            response.setStatus(500);
1043
            ServiceFailure sf = new ServiceFailure("1030", 
1044
                    "IO Error in ResourceHandler.getObject: " + e.getMessage());
1045
            serializeException(sf, out); 
1046
        } catch(NumberFormatException ne) {
1047
            response.setStatus(500);
1048
            InvalidRequest ir = new InvalidRequest("1030", "Invalid format for parameter: " + ne.getMessage());
1049
            serializeException(ir, out);
1050
        } catch (Exception e) {
1051
            e.printStackTrace();
1052
            response.setStatus(500);
1053
            ServiceFailure sf = new ServiceFailure("1030", 
1054
                    "Exception " + e.getClass().getName() + " raised while handling listObjects request: " + 
1055
                    e.getMessage());
1056
            serializeException(sf, out);
1057
        }
1058
    }
1059
    
1060
    /**
1061
     * parse a date and return it in GMT if it ends with a 'Z'
1062
     * @param date
1063
     * @return
1064
     * @throws ParseException
1065
     */
1066
    private Date parseDateAndConvertToGMT(String date) throws ParseException
1067
    {
1068
        try
1069
        {   //the format we want
1070
            return dateFormat.parse(date);
1071
        }
1072
        catch(java.text.ParseException pe)
1073
        {   //try another legacy format
1074
            DateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
1075
            dateFormat2.setTimeZone(TimeZone.getTimeZone("GMT-0"));
1076
            return dateFormat2.parse(date);
1077
        }    
1078
    }
1079

    
1080
    /**
1081
     * Implements REST version of DataONE CRUD API --> getSystemMetadata
1082
     * @param guid ID of data object to be read
1083
     */
1084
    private void getSystemMetadataObject(String guid) {
1085
        CrudService cs = CrudService.getInstance();
1086
        cs.setParamsFromRequest(request);
1087
        AuthToken token = new AuthToken(sessionId);
1088
        OutputStream out = null;
1089
        try {
1090
            response.setContentType("text/xml");
1091
            response.setStatus(200);
1092
            out = response.getOutputStream();
1093
            Identifier id = new Identifier();
1094
            id.setValue(guid);
1095
            SystemMetadata sysmeta = cs.getSystemMetadata(token, id);
1096
            
1097
            // Serialize and write it to the output stream
1098
            try {
1099
                //TODO: look at the efficiency of this method.  The system metadata
1100
                //is read from metacat (in CrudService) as xml, then serialized
1101
                //to a SystemMetadat object, then returned here, then serizlized
1102
                //back to XML to be sent to the response.
1103
                serializeServiceType(SystemMetadata.class, sysmeta, out);
1104
            } catch (JiBXException e) {
1105
                throw new ServiceFailure("1190", "Failed to serialize SystemMetadata: " + e.getMessage());
1106
            }
1107
        } catch (BaseException e) {
1108
            response.setStatus(500);
1109
                serializeException(e, out);
1110
        } catch (IOException e) {
1111
            response.setStatus(500);
1112
            ServiceFailure sf = new ServiceFailure("1030", 
1113
                    "Error in ResourceHandler.getSystemMetadataObject: " + e.getMessage());
1114
            serializeException(sf, out);
1115
        } finally {
1116
            IOUtils.closeQuietly(out);
1117
        }
1118
    }
1119
    
1120
    /**
1121
     * serialize an object of type to out
1122
     * @param type the class of the object to serialize (i.e. SystemMetadata.class)
1123
     * @param object the object to serialize
1124
     * @param out the stream to serialize it to
1125
     * @throws JiBXException
1126
     */
1127
    private void serializeServiceType(Class type, Object object, OutputStream out)
1128
      throws JiBXException
1129
    {
1130
        IBindingFactory bfact = BindingDirectory.getFactory(type);
1131
        IMarshallingContext mctx = bfact.createMarshallingContext();
1132
        mctx.marshalDocument(object, "UTF-8", null, out);
1133
    }
1134
    
1135
    /**
1136
     * deserialize an object of type from is
1137
     * @param type the class of the object to serialize (i.e. SystemMetadata.class)
1138
     * @param is the stream to deserialize from
1139
     * @throws JiBXException
1140
     */
1141
    private Object deserializeServiceType(Class type, InputStream is)
1142
      throws JiBXException
1143
    {
1144
        IBindingFactory bfact = BindingDirectory.getFactory(type);
1145
        IUnmarshallingContext uctx = bfact.createUnmarshallingContext();
1146
        Object o = (Object) uctx.unmarshalDocument(is, null);
1147
        return o;
1148
    }
1149
    
1150
    /**
1151
     * Earthgrid API > Query Service > Query Function : translates ecogrid query document to metacat query 
1152
     * then calls DBQuery > createResultDocument function and then again translate resultset to ecogrid resultset
1153
     * 
1154
     * NOTE:
1155
     *      This is the only method that uses EcoGrid classes for its implementation.  
1156
     *      It does so because it takes an EcoGrid Query as input, and outputs an
1157
     *      EcoGrid ResultSet document.  These documents are parsed by the auto-generated
1158
     *      EcoGrid classes from axis, and so we link to them here rather than re-inventing them.
1159
     *      This creates a circular dependency, because the Metacat classes are needed
1160
     *      to build the EcoGrid implementation, and the EcoGrid jars are needed to build this query()
1161
     *      method.  This circularity could be resolved by moving the EcoGrid classes
1162
     *      to Metacat directly.  As we transition away from EcoGrid SOAP methods in
1163
     *      favor of these REST interfaces, this circular dependency can be eliminated.
1164
     *        
1165
     * @throws Exception
1166
     */
1167
    private void query() throws Exception {
1168
        /*  This block commented out because of the EcoGrid circular dependency.
1169
         *  For now, query will not be supported until the circularity can be
1170
         *  resolved, probably by moving the ecogrid query syntax transformers
1171
         *  directly into the Metacat codebase.  MBJ 2010-02-03
1172
         
1173
        try {
1174
            EcogridQueryParser parser = new EcogridQueryParser(request
1175
                    .getReader());
1176
            parser.parseXML();
1177
            QueryType queryType = parser.getEcogridQuery();
1178
            EcogridJavaToMetacatJavaQueryTransformer queryTransformer = 
1179
                new EcogridJavaToMetacatJavaQueryTransformer();
1180
            QuerySpecification metacatQuery = queryTransformer
1181
                    .transform(queryType);
1182

    
1183
            DBQuery metacat = new DBQuery();
1184

    
1185
            boolean useXMLIndex = (new Boolean(PropertyService
1186
                    .getProperty("database.usexmlindex"))).booleanValue();
1187
            String xmlquery = "query"; // we don't care the query in resultset,
1188
            // the query can be anything
1189
            PrintWriter out = null; // we don't want metacat result, so set out null
1190

    
1191
            // parameter: queryspecification, user, group, usingIndexOrNot
1192
            StringBuffer result = metacat.createResultDocument(xmlquery,
1193
                    metacatQuery, out, username, groupNames, useXMLIndex);
1194

    
1195
            // create result set transfer       
1196
            String saxparser = PropertyService.getProperty("xml.saxparser");
1197
            MetacatResultsetParser metacatResultsetParser = new MetacatResultsetParser(
1198
                    new StringReader(result.toString()), saxparser, queryType
1199
                            .getNamespace().get_value());
1200
            ResultsetType records = metacatResultsetParser.getEcogridResult();
1201

    
1202
            System.out
1203
                    .println(EcogridResultsetTransformer.toXMLString(records));
1204
            response.setContentType("text/xml");
1205
            out = response.getWriter();
1206
            out.print(EcogridResultsetTransformer.toXMLString(records));
1207

    
1208
        } catch (Exception e) {
1209
            e.printStackTrace();
1210
        }*/
1211
        response.setContentType("text/xml");
1212
        response.setStatus(501);
1213
        PrintWriter out = response.getWriter();
1214
        out.print("<error>Query operation not yet supported by Metacat.</error>");
1215
        out.close();
1216
    }
1217
    
1218
    private String streamToString(InputStream is)
1219
    throws IOException
1220
    {
1221
        return IOUtil.toString(is);
1222
    }
1223

    
1224
    private InputStream stringToStream(String s)
1225
    throws IOException
1226
    {
1227
        ByteArrayInputStream bais = new ByteArrayInputStream(s.getBytes());
1228
        return bais;
1229
    }
1230
    
1231
    /**
1232
     * process a mime multipart message
1233
     * @param is
1234
     * @return
1235
     */
1236
    /*private Hashtable processMMP(InputStream is)
1237
      throws IOException
1238
    {
1239
        //TODO: verify that this is how the RFC for MMP should work
1240
        //SORTAHACK: Since mmp seems to have a bug where large object parts get truncated,
1241
        //parse the stream here.  This has the disavantage of putting the
1242
        //stream into memory.
1243
        InputStream object = null;
1244
        InputStream sysmeta = null;
1245
        String s = IOUtils.toString(is);
1246
        System.out.println("mime: " + s);
1247
        //figure out what the boundary marker is
1248
        String searchString = "boundary=";
1249
        int searchStringIndex = s.indexOf(searchString);
1250
        String boundary = s.substring(searchStringIndex + searchString.length() + 1, 
1251
                s.indexOf("\"", searchStringIndex + searchString.length() + 1));
1252
        boundary = "--" + boundary;
1253
        //System.out.println("boundary is " + boundary);
1254
        
1255
        //find the system metadata
1256
        searchString = "Content-Disposition: attachment; filename=systemmetadata";
1257
        searchStringIndex = s.indexOf(searchString);
1258
        String sm = s.substring(searchStringIndex +
1259
                searchString.length() + 2, 
1260
                s.indexOf(boundary, searchStringIndex));
1261
        sysmeta = new ByteArrayInputStream(sm.getBytes());
1262
        
1263
        //find the object
1264
        searchString = "Content-Disposition: attachment; filename=object";
1265
        searchStringIndex = s.indexOf(searchString);
1266
        String o = s.substring(searchStringIndex +
1267
                searchString.length() + 2, 
1268
                s.indexOf(boundary, searchStringIndex));
1269
        object = new ByteArrayInputStream(o.getBytes());
1270
        
1271
        Hashtable h = new Hashtable();
1272
        h.put("object", object);
1273
        h.put("systemmetadata", sysmeta);
1274
                
1275
        //System.out.println("o: \"" + o + "\"");
1276
        //System.out.println("sm: \"" + sm + "\"");
1277
        return h;
1278
    }
1279
    */
1280
    
1281
    protected static String[] findBoundaryString(InputStream is)
1282
        throws IOException
1283
    {
1284
        String[] endResult = new String[2];
1285
        String boundary = "";
1286
        String searchString = "boundary=";
1287
        boolean doneWithCurrentArray = false;
1288
        byte[] b = new byte[1024];
1289
        int numbytes = is.read(b, 0, 1024);
1290
        while(numbytes != -1)
1291
        {
1292
            String s = new String(b, 0, numbytes);
1293
            
1294
            int[] result = StreamUtil.lookForMatch(searchString, s);
1295
            int searchStringIndex = s.indexOf(searchString);
1296
            if(s.indexOf("\"", searchStringIndex + searchString.length() + 1) == -1)
1297
            { //the end of the boundary is in the next byte array
1298
                boundary = s.substring(searchStringIndex + searchString.length() + 1, s.length());
1299
            }
1300
            else if(!boundary.startsWith("--"))
1301
            { //we can read the whole boundary from this byte array
1302
                boundary = s.substring(searchStringIndex + searchString.length() + 1, 
1303
                    s.indexOf("\"", searchStringIndex + searchString.length() + 1));
1304
                boundary = "--" + boundary;
1305
                endResult[0] = boundary;
1306
                endResult[1] = s.substring(s.indexOf("\"", searchStringIndex + searchString.length() + 1) + 1,
1307
                        s.length());
1308
                break;
1309
            }
1310
            else
1311
            { //we're now reading the 2nd byte array to get the rest of the boundary
1312
                searchString = "\"";
1313
                searchStringIndex = s.indexOf(searchString);
1314
                boundary += s.substring(0, searchStringIndex);
1315
                boundary = "--" + boundary;
1316
                endResult[0] = boundary;
1317
                endResult[1] = s.substring(s.indexOf("\"", searchStringIndex + searchString.length() + 1) + 1,
1318
                        s.length());
1319
                break;
1320
            }
1321
        }
1322
        System.out.println("boundary is: " + boundary);
1323
        return endResult;
1324
    }
1325
    
1326
    protected String writeMMPPartToFile(String beginSearch, 
1327
            InputStream is, String boundary, String searchString, File f)
1328
        throws IOException
1329
    {
1330
        Logger logMetacat = Logger.getLogger(ResourceHandler.class);
1331
        logMetacat.info("writing MMP parts");
1332
        //String s = beginSearch;
1333
        String s = null;
1334
        FileOutputStream fos = new FileOutputStream(f);
1335
        int numread = 0;
1336
        byte[] b = new byte[1024];
1337
        String writeString = "";
1338
        
1339
        if(s == null)
1340
        {   //starting with the first part of the stream 
1341
            numread = is.read(b, 0, 1024);
1342
            s = new String(b, 0, numread);
1343
        }
1344
        
1345
        if(beginSearch != null)
1346
        {
1347
            s = beginSearch + s;
1348
        }
1349
        
1350
        boolean useCurrentS = true;
1351
        boolean searchForBoundary = false;
1352
        String seekString = searchString;
1353
        
1354
        while(numread != -1)
1355
        {
1356
            logMetacat.info("////////////////////////iterating");
1357
            logMetacat.info("searchForBoundary: " + searchForBoundary);
1358
            logMetacat.info("useCurrentS: " + useCurrentS);
1359
            logMetacat.info("seekString: " + seekString);
1360
            logMetacat.info("in string: " + s);
1361
            if(searchForBoundary)
1362
            {
1363
                seekString = boundary;
1364
            }
1365
            else
1366
            {
1367
                seekString = searchString;
1368
            }
1369
            
1370
            int[] result = StreamUtil.lookForMatch(seekString, s);
1371
            if(!useCurrentS)
1372
            {
1373
                numread = is.read(b, 0, 1024);
1374
                if(numread != -1)
1375
                {
1376
                    s = new String(b, 0, numread);
1377
                }
1378
                else
1379
                {
1380
                    break;
1381
                }
1382
            }
1383
            
1384
            logMetacat.info("2seekString: " + seekString);
1385
            logMetacat.info("2in string: " + s);
1386
            
1387
            if(result[0] >= 0 && result[1] == seekString.length())
1388
            {
1389
                //searchString is full in s
1390
                logMetacat.info("seekstring is fully in s");
1391
                if(!searchForBoundary)
1392
                {   //we're looking for searchString and we found it
1393
                    //chop off the searchString itself and start writing
1394
                    //until we find boundary
1395
                    s = s.substring(result[0] + result[1], s.length());
1396
                    if(s.length() > 0)
1397
                    {
1398
                        useCurrentS = true;
1399
                    }
1400
                    else
1401
                    {
1402
                        useCurrentS = false;
1403
                    }
1404
                    searchForBoundary = true;
1405
                }
1406
                else
1407
                {   //we're writing, but we found the boundary in this chunk
1408
                    
1409
                    writeString = s.substring(0, result[0]);
1410
                    //System.out.println("writing1: " + writeString);
1411
                    fos.write(writeString.getBytes());
1412
                    //we're done.  break and return;
1413
                    return s.substring(result[0] + result[1], s.length());
1414
                }
1415
            }
1416
            else if(result[0] > 0 && result[1] != seekString.length())
1417
            {
1418
                logMetacat.info("seekstring is partially in s");
1419
                //seekString is partially in s
1420
                //more specifically, the beginning of seekString is at the end
1421
                //of s
1422
                
1423
                //get the next chunk right now, see if the beginning matches
1424
                numread = is.read(b, 0, 1024);
1425
                String s2 = new String(b, 0, numread);
1426
                s += s2;
1427
                useCurrentS = true;
1428
            }
1429
            else
1430
            {
1431
                logMetacat.info("seekstring is not in s");
1432
                //searchString is not in s 
1433
                if(searchForBoundary)
1434
                {
1435
                    //System.out.println("writing2: " + s);
1436
                    fos.write(s.getBytes());
1437
                }
1438
                numread = is.read(b, 0, 1024);
1439
                if(numread != -1)
1440
                {
1441
                    s = new String(b, 0, numread);
1442
                }
1443
                else
1444
                {
1445
                    break;
1446
                }
1447
                useCurrentS = true;
1448
            }
1449
        }
1450
        return "";
1451
    }
1452
    
1453
    protected Hashtable<String, File> writeMMPPartsToFiles(InputStream is)
1454
        throws IOException
1455
    {
1456
        Logger logMetacat = Logger.getLogger(ResourceHandler.class);
1457
        logMetacat.info("Processing Mime Multipart");
1458
        String[] boundaryResults = findBoundaryString(is);
1459
        String boundary = boundaryResults[0];
1460
        String s = boundaryResults[1];
1461
        String[] searchStrings = {
1462
                "Content-Disposition: attachment; filename=systemmetadata\n\n",
1463
                "Content-Disposition: attachment; filename=object\n\n"};
1464
        
1465
        File[] fileArr = getMMPTempFiles();
1466
        Hashtable<String, File> h = new Hashtable<String, File>();
1467
        //System.out.println("==========================Looking for SM");
1468
        //System.out.println("writing sm to " + fileArr[0].getAbsolutePath());
1469
        logMetacat.info("writing mime system metadata to " + fileArr[0].getAbsolutePath());
1470
        s = writeMMPPartToFile(s.trim(), is, boundary, searchStrings[0], fileArr[0]);
1471
        logMetacat.info("writeMMPPartToFile returned '" + s.trim() + "' after processing the system metadata");
1472
        //System.out.println("==========================Looking for Object");
1473
        //System.out.println("writing obj to " + fileArr[1].getAbsolutePath());
1474
        logMetacat.info("writing mime object to " + fileArr[1].getAbsolutePath());
1475
        writeMMPPartToFile(s.trim(), is, boundary, searchStrings[1], fileArr[1]);
1476
        h.put("sysmeta", fileArr[0]);
1477
        h.put("object", fileArr[1]);
1478
        return h;
1479
    }
1480
    
1481
    private static File[] getMMPTempFiles()
1482
        throws IOException
1483
    {
1484
        Logger logMetacat = Logger.getLogger(ResourceHandler.class);
1485
        File tmpDir;
1486
        try
1487
        {
1488
            tmpDir = new File(PropertyService.getProperty("application.tempDir"));
1489
        }
1490
        catch(PropertyNotFoundException pnfe)
1491
        {
1492
            logMetacat.error("ResourceHandler.writeMMPPartstoFiles: " +
1493
                    "application.tmpDir not found.  Using /tmp instead.");
1494
            tmpDir = new File("/tmp");
1495
        }
1496
        long datetimetag = new Date().getTime();
1497
        File smFile = new File(tmpDir, "sm." + datetimetag + ".tmp");
1498
        File objFile = new File(tmpDir, "obj." + datetimetag + ".tmp");
1499
        File[] fileArr = {smFile, objFile};
1500
        return fileArr;
1501
    }
1502
    
1503
    /**
1504
     * return a vector where the first element is a string that represents the system
1505
     * metadata and the 2nd element is an InputStream that is the object
1506
     */
1507
    protected Hashtable<String, File> processMMP(final InputStream is)
1508
      throws IOException
1509
    {
1510
        return writeMMPPartsToFiles(is);
1511
    }
1512
    
1513
    /**
1514
     * Earthgrid API > Put Service >Put Function : calls MetacatHandler > handleInsertOrUpdateAction 
1515
     * 
1516
     * @param guid ID of data object to be inserted or updated
1517
     * @throws IOException
1518
     */
1519
    private void putObject(String guid, String action) {
1520
        logMetacat.debug("Entering putObject: " + guid + "/" + action);
1521
        OutputStream out = null;
1522
        Hashtable<String, File> parts = null;
1523
        try {
1524
            out = response.getOutputStream();
1525
            response.setStatus(200);
1526
            response.setContentType("text/xml");
1527
        } catch (IOException e1) {
1528
            logMetacat.error("Could not get the output stream for writing in putObject");
1529
        }
1530
        try {
1531
            
1532
            // Read the incoming data from its Mime Multipart encoding
1533
            logMetacat.debug("Disassembling MIME multipart form");
1534
            InputStream object = null;
1535
            InputStream sysmeta = null;
1536
            
1537
            try
1538
            {
1539
                //String req = IOUtils.toString(request.getInputStream());
1540
                //System.out.println("request: " + req);
1541
                //InputStream reqStr = IOUtils.toInputStream(req);
1542
                InputStream reqStr = request.getInputStream();
1543
                parts = processMMP(reqStr);
1544
                object = new FileInputStream(parts.get("object"));
1545
                sysmeta = new FileInputStream(parts.get("sysmeta"));
1546
                
1547
                /*String obj = IOUtils.toString(object);
1548
                String sm = IOUtils.toString(sysmeta);
1549
                System.out.println("object: " + obj);
1550
                System.out.println("sm: " + sm);
1551
                object = IOUtils.toInputStream(obj);
1552
                sysmeta = IOUtils.toInputStream(sm);*/
1553
                
1554
            }
1555
            catch(IOException ioe)
1556
            {
1557
                throw new ServiceFailure("1202", 
1558
                        "IOException when processing Mime Multipart: " + ioe.getMessage());
1559
            }
1560
            
1561
            if ( action.equals(FUNCTION_NAME_INSERT)) { //handle inserts
1562

    
1563
                // Check if the objectId exists
1564
                IdentifierManager im = IdentifierManager.getInstance();
1565
                if (im.identifierExists(guid)) {
1566
                    throw new IdentifierNotUnique("1000", "Identifier is already in use: " + guid);
1567
                }
1568

    
1569
                logMetacat.debug("Commence creation...");
1570
                IBindingFactory bfact =
1571
                    BindingDirectory.getFactory(SystemMetadata.class);
1572
                IUnmarshallingContext uctx = bfact.createUnmarshallingContext();
1573
                SystemMetadata m = (SystemMetadata) uctx.unmarshalDocument(sysmeta, null);
1574

    
1575
                CrudService cs = CrudService.getInstance();
1576
                AuthToken token = new AuthToken(sessionId); 
1577
                cs.setParamsFromRequest(request);
1578
                Identifier id = new Identifier();
1579
                id.setValue(guid);
1580
                Identifier rId = cs.create(token, id, object, m);
1581
                serializeServiceType(Identifier.class, rId, out);
1582
                
1583
            } else if (action.equals(FUNCTION_NAME_UPDATE)) { //handle updates
1584
                IdentifierManager im = IdentifierManager.getInstance();
1585
                CrudService cs = CrudService.getInstance();
1586
                Identifier obsoletedGuid = new Identifier();
1587
                Identifier id = new Identifier();
1588
                id.setValue(guid);
1589
                AuthToken token = new AuthToken(sessionId);
1590
                
1591
                //do some checks
1592
                if(params.get("obsoletedGuid") == null)
1593
                {
1594
                    throw new InvalidRequest("1202", "obsoletedGuid must be contained in the request parameters.");
1595
                }
1596
                //get the obsoletedGuid
1597
                String[] obsGuidS = params.get("obsoletedGuid");
1598
                obsoletedGuid.setValue(obsGuidS[0]);
1599
                
1600
                if (!im.identifierExists(obsoletedGuid.getValue())) 
1601
                {
1602
                    throw new InvalidRequest("1202", "The guid you are trying to update does not exist.");
1603
                }
1604
                
1605
                
1606
                logMetacat.debug("Commence update...");
1607
                
1608
                //get the systemmetadata
1609
                IBindingFactory bfact =
1610
                        BindingDirectory.getFactory(SystemMetadata.class);
1611
                    IUnmarshallingContext uctx = bfact.createUnmarshallingContext();
1612
                    SystemMetadata m = (SystemMetadata) uctx.unmarshalDocument(sysmeta, null);
1613
                
1614
                //do the update
1615
                try
1616
                {
1617
                    cs.setParamsFromRequest(request);
1618
                    Identifier rId = cs.update(token, id, object, obsoletedGuid, m);
1619
                    serializeServiceType(Identifier.class, rId, out);
1620
                }
1621
                catch(NotFound e)
1622
                {
1623
                    throw new InvalidRequest("1202", "The guid you are trying to update does not exist.");
1624
                }
1625
                
1626
            } else {
1627
                throw new InvalidRequest("1000", "Operation must be create or update.");
1628
            }
1629
            
1630
            //clean up the MMP files
1631
            parts.get("sysmeta").delete();
1632
            parts.get("object").delete();
1633
        } catch (NotAuthorized e) {
1634
            response.setStatus(500);
1635
            serializeException(e, out);
1636
        } catch (InvalidToken e) {
1637
            response.setStatus(500);
1638
            serializeException(e, out);
1639
        } catch (ServiceFailure e) {
1640
            response.setStatus(500);
1641
            serializeException(e, out);
1642
        } catch (IdentifierNotUnique e) {
1643
            response.setStatus(500);
1644
            serializeException(e, out);
1645
        } catch (UnsupportedType e) {
1646
            response.setStatus(500);
1647
            serializeException(e, out);
1648
        } catch (InsufficientResources e) {
1649
            response.setStatus(500);
1650
            serializeException(e, out);
1651
        } catch (InvalidSystemMetadata e) {
1652
            response.setStatus(500);
1653
            serializeException(e, out);
1654
        } catch (NotImplemented e) {
1655
            response.setStatus(500);
1656
            serializeException(e, out);
1657
        } catch (InvalidRequest e) {
1658
            response.setStatus(500);
1659
            serializeException(e, out);
1660
        } /*catch (MessagingException e) {
1661
            ServiceFailure sf = new ServiceFailure("1000", e.getMessage());
1662
            serializeException(sf, out);
1663
        } catch (IOException e) {
1664
            response.setStatus(500);
1665
            ServiceFailure sf = new ServiceFailure("1000", e.getMessage());
1666
            serializeException(sf, out);
1667
        }*/ catch (JiBXException e) {
1668
            response.setStatus(500);
1669
            e.printStackTrace(System.out);
1670
            InvalidSystemMetadata ism = new InvalidSystemMetadata("1080", e.getMessage());
1671
            serializeException(ism, out);
1672
        }
1673
        finally
1674
        {
1675
            if(parts != null)
1676
            {
1677
                parts.get("sysmeta").delete();
1678
                parts.get("object").delete();
1679
            }
1680
        }
1681
    }
1682

    
1683
    /**
1684
     * Handle delete 
1685
     * @param guid ID of data object to be deleted
1686
     * @throws IOException
1687
     */
1688
    private void deleteObject(String guid) throws IOException 
1689
    {
1690
        // Look up the localId for this global identifier
1691
        System.out.println("!!!!!!!!!!!!!!!!!deleting object " + guid);
1692
        IdentifierManager im = IdentifierManager.getInstance();
1693
        String localId = "";
1694
        OutputStream out = response.getOutputStream();
1695
        response.setStatus(200);
1696
        try {
1697
            localId = im.getLocalId(guid);
1698
        } catch (McdbDocNotFoundException e) {
1699
            NotFound nf = new NotFound("1340", "Document with guid " + guid + " not found.");
1700
            response.setStatus(404);
1701
            serializeException(nf, out);
1702
        }
1703
       
1704
        AuthToken token = new AuthToken(sessionId);
1705
        CrudService cs = CrudService.getInstance();
1706
        Identifier id = new Identifier();
1707
        id.setValue(guid);
1708
        try
1709
        {
1710
            System.out.println("Calling delete");
1711
            cs.delete(token, id);
1712
            serializeServiceType(Identifier.class, id, out);
1713
        } 
1714
        catch (NotAuthorized e) {
1715
            response.setStatus(500);
1716
            serializeException(e, out);
1717
        } catch (InvalidToken e) {
1718
            response.setStatus(500);
1719
            serializeException(e, out);
1720
        } catch (ServiceFailure e) {
1721
            response.setStatus(500);
1722
            serializeException(e, out);
1723
        } catch (NotImplemented e) {
1724
            response.setStatus(500);
1725
            serializeException(e, out);
1726
        } catch (InvalidRequest e) {
1727
            response.setStatus(500);
1728
            serializeException(e, out);
1729
        } catch(NotFound e) {
1730
            response.setStatus(500);
1731
            serializeException(e, out);
1732
        } catch(JiBXException e) {
1733
            response.setStatus(500);
1734
            serializeException(new ServiceFailure("1350", "JiBXException: " + e.getMessage()), out);
1735
        }
1736
        out.close();
1737
    }
1738
    
1739
    /**
1740
     * set the access perms on a document
1741
     * @throws IOException
1742
     */
1743
    private void setaccess() throws Exception
1744
    {
1745
        try
1746
        {
1747
            String guid = params.get("guid")[0];
1748
            Identifier id = new Identifier();
1749
            id.setValue(guid);
1750
            AuthToken token = new AuthToken(sessionId);
1751
            String principal = params.get("principal")[0];
1752
            String permission = params.get("permission")[0];
1753
            String permissionType = params.get("permissionType")[0];
1754
            String permissionOrder = params.get("permissionOrder")[0];
1755
            String setSystemMetadata = params.get("setsystemmetadata")[0];
1756
            boolean ssm = false;
1757
            if(setSystemMetadata.equals("true") || setSystemMetadata.equals("TRUE") ||
1758
                    setSystemMetadata.equals("yes"))
1759
            {
1760
                ssm = true;
1761
            }
1762
            CrudService cs = CrudService.getInstance();
1763
            //TODO: remove the setsystemmetadata param and set this so the systemmetadata always gets set
1764
            cs.setAccess(token, id, principal, permission, permissionType, permissionOrder, ssm);
1765
        }
1766
        catch(Exception e)
1767
        {
1768
            response.setStatus(500);
1769
            printError("Error setting access in ResourceHandler: " + e.getMessage(), response);
1770
            throw e;
1771
        }
1772
    }
1773

    
1774
    /**
1775
     * Earthgrid API > Authentication Service > Login Function : calls MetacatHandler > handleLoginAction
1776
     * 
1777
     * @throws IOException
1778
     */
1779
    private void login() throws IOException {
1780
        PrintWriter out = response.getWriter();
1781
        response.setStatus(200);
1782
        response.setContentType("text/xml");
1783
        handler.handleLoginAction(out, params, request, response);
1784
        out.close();
1785
    }
1786

    
1787
    /**
1788
     * Earthgrid API > Authentication Service > Logout Function : calls MetacatHandler > handleLogoutAction
1789
     * 
1790
     * @throws IOException
1791
     */
1792
    private void logout() throws IOException {
1793
        PrintWriter out = response.getWriter();
1794
        response.setStatus(200);
1795
        response.setContentType("text/xml");
1796
        handler.handleLogoutAction(out, params, request, response);
1797
        out.close();
1798
    }
1799

    
1800
    /**
1801
     * Prints xml response
1802
     * @param message Message to be displayed
1803
     * @param response Servlet response that xml message will be printed 
1804
     * */
1805
    private void printError(String message, HttpServletResponse response) {
1806
        try {
1807
            logMetacat.error("ResourceHandler: Printing error to servlet response: " + message);
1808
            PrintWriter out = response.getWriter();
1809
            response.setContentType("text/xml");
1810
            out.println("<?xml version=\"1.0\"?>");
1811
            out.println("<error>");
1812
            out.println(message);
1813
            out.println("</error>");
1814
            out.close();
1815
        } catch (IOException e) {
1816
            e.printStackTrace();
1817
        }
1818
    }
1819
    
1820
    /**
1821
     * serialize a D1 exception using jibx
1822
     * @param e
1823
     * @param out
1824
     */
1825
    private void serializeException(BaseException e, OutputStream out) {
1826
        // TODO: Use content negotiation to determine which return format to use
1827
        response.setContentType("text/xml");
1828
        response.setStatus(e.getCode());
1829
        
1830
        logMetacat.error("ResourceHandler: Serializing exception with code " + e.getCode() + ": " + e.getMessage());
1831
        e.printStackTrace();
1832
        
1833
        try {
1834
            IOUtils.write(e.serialize(BaseException.FMT_XML), out);
1835
        } catch (IOException e1) {
1836
            logMetacat.error("Error writing exception to stream. " 
1837
                    + e1.getMessage());
1838
        }
1839
    }
1840
    
1841
    /**
1842
     * create a new ServiceMethod declaration
1843
     * @param name
1844
     * @param rest
1845
     * @param implemented
1846
     * @return
1847
     */
1848
    private ServiceMethod getServiceMethod(String name, String rest, boolean implemented)
1849
    {
1850
        ServiceMethod sm = new ServiceMethod();
1851
        sm.setImplemented(implemented);
1852
        sm.setName(name);
1853
        sm.setRest(rest);
1854
        return sm;
1855
    }
1856

    
1857
}
(2-2/3)