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: $'
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.dataone;
24

    
25
import java.io.ByteArrayOutputStream;
26
import java.io.File;
27
import java.io.FileNotFoundException;
28
import java.io.FileOutputStream;
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.io.OutputStream;
32
import java.io.PrintWriter;
33
import java.io.StringBufferInputStream;
34
import java.security.MessageDigest;
35
import java.sql.SQLException;
36
import java.util.*;
37
import java.text.DateFormat;
38
import java.text.SimpleDateFormat;
39

    
40
import javax.servlet.ServletContext;
41
import javax.servlet.http.HttpServletRequest;
42
import javax.servlet.http.HttpServletResponse;
43

    
44
import org.apache.commons.io.IOUtils;
45
import org.apache.log4j.Logger;
46
import org.dataone.service.exceptions.IdentifierNotUnique;
47
import org.dataone.service.exceptions.InsufficientResources;
48
import org.dataone.service.exceptions.InvalidRequest;
49
import org.dataone.service.exceptions.InvalidSystemMetadata;
50
import org.dataone.service.exceptions.InvalidToken;
51
import org.dataone.service.exceptions.NotAuthorized;
52
import org.dataone.service.exceptions.NotFound;
53
import org.dataone.service.exceptions.NotImplemented;
54
import org.dataone.service.exceptions.ServiceFailure;
55
import org.dataone.service.exceptions.UnsupportedType;
56
import org.dataone.service.mn.MemberNodeCrud;
57
import org.dataone.service.types.*;
58
import org.jibx.runtime.BindingDirectory;
59
import org.jibx.runtime.IBindingFactory;
60
import org.jibx.runtime.IMarshallingContext;
61
import org.jibx.runtime.IUnmarshallingContext;
62
import org.jibx.runtime.JiBXException;
63

    
64
import com.gc.iotools.stream.is.InputStreamFromOutputStream;
65

    
66
import edu.ucsb.nceas.metacat.AccessionNumberException;
67
import edu.ucsb.nceas.metacat.MetacatResultSet;
68
import edu.ucsb.nceas.metacat.MetacatResultSet.Document;
69
import edu.ucsb.nceas.metacat.DocumentImpl;
70
import edu.ucsb.nceas.metacat.EventLog;
71
import edu.ucsb.nceas.metacat.IdentifierManager;
72
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
73
import edu.ucsb.nceas.metacat.McdbException;
74
import edu.ucsb.nceas.metacat.MetacatHandler;
75
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
76
import edu.ucsb.nceas.metacat.client.rest.MetacatRestClient;
77
import edu.ucsb.nceas.metacat.properties.PropertyService;
78
import edu.ucsb.nceas.metacat.replication.ForceReplicationHandler;
79
import edu.ucsb.nceas.metacat.service.SessionService;
80
import edu.ucsb.nceas.metacat.util.DocumentUtil;
81
import edu.ucsb.nceas.metacat.util.SessionData;
82
import edu.ucsb.nceas.utilities.ParseLSIDException;
83
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
84

    
85
/**
86
 * 
87
 * Implements DataONE MemberNode CRUD API for Metacat. 
88
 * 
89
 * @author Matthew Jones
90
 */
91
public class CrudService implements MemberNodeCrud
92
{
93
    private static CrudService crudService = null;
94

    
95
    private MetacatHandler handler;
96
    private Hashtable<String, String[]> params;
97
    private Logger logMetacat = null;
98
    private Logger logCrud = null;
99
    
100
    private String metacatUrl;
101
    
102
    private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZ");
103

    
104
    /**
105
     * singleton accessor
106
     */
107
    public static CrudService getInstance()
108
    {
109
      if(crudService == null)
110
      {
111
        crudService = new CrudService();
112
      }
113
      
114
      return crudService;
115
    }
116
    
117
    /**
118
     * Initializes new instance by setting servlet context,request and response.
119
     */
120
    public CrudService() {
121
    //change crud service into a singleton.  dont pass servlet data structures here
122
        logMetacat = Logger.getLogger(CrudService.class);
123
        logCrud = Logger.getLogger("DataOneLogger");
124
        try
125
        {
126
            String server = PropertyService.getProperty("server.name");
127
            String port = PropertyService.getProperty("server.httpPort");
128
            String context = PropertyService.getProperty("application.context");
129
            metacatUrl = "http://" + server + ":" + port + "/" + context;
130
            logMetacat.debug("Initializing CrudService with url " + metacatUrl);
131
        }
132
        catch(Exception e)
133
        {
134
            logMetacat.error("Could not find servlet url in CrudService: " + e.getMessage());
135
            e.printStackTrace();
136
            throw new RuntimeException("Error getting servlet url in CrudService: " + e.getMessage());
137
        }
138
        
139
        /*this.servletContext = servletContext;
140
        this.request = request;
141
        this.response = response;*/
142
        
143
        params = new Hashtable<String, String[]>();
144

    
145
        handler = new MetacatHandler(new Timer());
146

    
147
    }
148
    
149
    /**
150
     * return the context url CrudService is using.
151
     */
152
    public String getContextUrl()
153
    {
154
        return metacatUrl;
155
    }
156
    
157
    /**
158
     * Set the context url that this service uses.  It is normally not necessary
159
     * to call this method unless you are trying to connect to a server other
160
     * than the one in which this service is installed.  Otherwise, this value is
161
     * taken from the metacat.properties file (server.name, server.port, application.context).
162
     */
163
    public void setContextUrl(String url)
164
    {
165
        metacatUrl = url;
166
    }
167
    
168
    /**
169
     * set the params for this service from an HttpServletRequest param list
170
     */
171
    public void setParamsFromRequest(HttpServletRequest request)
172
    {
173
        Enumeration paramlist = request.getParameterNames();
174
        while (paramlist.hasMoreElements()) {
175
            String name = (String) paramlist.nextElement();
176
            String[] value = (String[])request.getParameterValues(name);
177
            params.put(name, value);
178
        }
179
    }
180
    
181
    /**
182
     * Authenticate against metacat and get a token.
183
     * @param username
184
     * @param password
185
     * @return
186
     * @throws ServiceFailure
187
     */
188
    public AuthToken authenticate(String username, String password)
189
      throws ServiceFailure
190
    {
191
        try
192
        {
193
            MetacatRestClient restClient = new MetacatRestClient(getContextUrl());   
194
            String response = restClient.login(username, password);
195
            String sessionid = restClient.getSessionId();
196
            SessionService sessionService = SessionService.getInstance();
197
            sessionService.registerSession(new SessionData(sessionid, username, new String[0], password, "CrudServiceLogin"));
198
            AuthToken token = new AuthToken(sessionid);
199
            EventLog.getInstance().log(metacatUrl,
200
                    username, null, "authenticate");
201
            logCrud.info("authenticate");
202
            return token;
203
        }
204
        catch(Exception e)
205
        {
206
            throw new ServiceFailure("1000", "Error authenticating with metacat: " + e.getMessage());
207
        }
208
    }
209
    
210
    /**
211
     * set the parameter values needed for this request
212
     */
213
    public void setParameter(String name, String[] value)
214
    {
215
        params.put(name, value);
216
    }
217
    
218
    /**
219
     * Generate SystemMetadata for any object in the object store that does
220
     * not already have it.  SystemMetadata documents themselves, are, of course,
221
     * exempt.  This is a utility method for migration of existing object 
222
     * stores to DataONE where SystemMetadata is required for all objects.  See 
223
     * https://trac.dataone.org/ticket/591
224
     * 
225
     * @param token an authtoken with appropriate permissions to read all 
226
     * documents in the object store.  To work correctly, this should probably
227
     * be an adminstrative credential.
228
     */
229
    public void generateMissingSystemMetadata(AuthToken token)
230
    {
231
        IdentifierManager im = IdentifierManager.getInstance();
232
        //get the list of ids with no SM
233
        List<String> l = im.getLocalIdsWithNoSystemMetadata();
234
        for(int i=0; i<l.size(); i++)
235
        { //for each id, add a system metadata doc
236
            String localId = l.get(i);
237
            //System.out.println("Creating SystemMetadata for localId " + localId);
238
            //get the document
239
            try
240
            {
241
                //generate required system metadata fields from the document
242
                SystemMetadata sm = createSystemMetadata(localId, token);
243
                //insert the systemmetadata object
244
                SessionData sessionData = getSessionData(token);
245
                insertSystemMetadata(sm, sessionData);
246
                String username = "public";
247
                if(sessionData != null)
248
                {
249
                    username = sessionData.getUserName();
250
                }
251
                EventLog.getInstance().log(metacatUrl,
252
                        username, localId, "generateMissingSystemMetadata");
253
            }
254
            catch(Exception e)
255
            {
256
                //e.printStackTrace();
257
                System.out.println("Exception generating missing system metadata: " + e.getMessage());
258
                logMetacat.error("Could not generate missing system metadata: " + e.getMessage());
259
            }
260
        }
261
        logCrud.info("generateMissingSystemMetadata");
262
    }
263
    
264
    /**
265
     * create an object via the crud interface
266
     */
267
    public Identifier create(AuthToken token, Identifier guid, 
268
            InputStream object, SystemMetadata sysmeta) throws InvalidToken, 
269
            ServiceFailure, NotAuthorized, IdentifierNotUnique, UnsupportedType, 
270
            InsufficientResources, InvalidSystemMetadata, NotImplemented {
271
        System.out.println("#####################Create!");
272

    
273
        logMetacat.debug("Starting CrudService.create()...");
274
        
275
        // authenticate & get user info
276
        SessionData sessionData = getSessionData(token);
277
        String username = "public";
278
        String[] groups = null;
279
        if(sessionData != null)
280
        {
281
            username = sessionData.getUserName();
282
            groups = sessionData.getGroupNames();
283
        }
284
        String localId = null;
285

    
286
        if (username == null || username.equals("public"))
287
        {
288
            throw new NotAuthorized("1000", "User " + username + " is not authorized to create content." +
289
                    "  If you are not logged in, please do so and retry the request.");
290
        }
291
        
292
        // verify that guid == SystemMetadata.getIdentifier()
293
        logMetacat.debug("Comparing guid|sysmeta_guid: " + guid.getValue() + "|" + sysmeta.getIdentifier().getValue());
294
        if (!guid.getValue().equals(sysmeta.getIdentifier().getValue())) {
295
            throw new InvalidSystemMetadata("1180", 
296
                "GUID in method call does not match GUID in system metadata.");
297
        }
298

    
299
        logMetacat.debug("Checking if identifier exists...");
300
        // Check that the identifier does not already exist
301
        IdentifierManager im = IdentifierManager.getInstance();
302
        if (im.identifierExists(guid.getValue())) {
303
            throw new IdentifierNotUnique("1120", 
304
                "GUID is already in use by an existing object.");
305
        }
306

    
307
        // Check if we are handling metadata or data
308
        boolean isScienceMetadata = isScienceMetadata(sysmeta);
309
        
310
        if (isScienceMetadata) {
311
            // CASE METADATA:
312
            try {
313
                System.out.println("CrudService: inserting document with guid " + guid.getValue());
314
                this.insertDocument(object, guid, sessionData);
315
                localId = im.getLocalId(guid.getValue());
316
            } catch (IOException e) {
317
                String msg = "Could not create string from XML stream: " +
318
                    " " + e.getMessage();
319
                logMetacat.debug(msg);
320
                throw new ServiceFailure("1190", msg);
321
            } catch(Exception e) {
322
                String msg = "Unexpected error in CrudService.create: " + e.getMessage();
323
                logMetacat.debug(msg);
324
                throw new ServiceFailure("1190", msg);
325
            }
326
            
327

    
328
        } else {
329
            // DEFAULT CASE: DATA (needs to be checked and completed)
330
            insertDataObject(object, guid, sessionData);
331
            
332
        }
333

    
334
        // For Metadata and Data, insert the system metadata into the object store too
335
        String sysMetaLocalId = insertSystemMetadata(sysmeta, sessionData);
336
        //get the document info.  add any access params for the sysmeta too
337
        System.out.println("looking for access records to add for system " +
338
            "metadata who's parent doc's  local id is " + localId);
339
        try
340
        {
341
            Hashtable<String, Object> h = im.getDocumentInfo(localId.substring(0, localId.lastIndexOf(".")));
342
            Vector v = (Vector)h.get("access");
343
            for(int i=0; i<v.size(); i++)
344
            {
345
                Hashtable ah = (Hashtable)v.elementAt(i);
346
                String principal = (String)ah.get("principal_name");
347
                String permission = (String)ah.get("permission");
348
                String permissionType = (String)ah.get("permission_type");
349
                String permissionOrder = (String)ah.get("permission_order");
350
                int perm = new Integer(permission).intValue();
351
                System.out.println("found access record for principal " + principal);
352
                System.out.println("permission: " + perm + " perm_type: " + permissionType + 
353
                    " perm_order: " + permissionOrder);
354
                this.setAccess(token, guid, principal, perm, permissionType, permissionOrder, true);
355
            }
356
        }
357
        catch(Exception e)
358
        {
359
            logMetacat.error("Error setting permissions on System Metadata object " + 
360
                    " with id " + sysMetaLocalId + ": " + e.getMessage());
361
        }
362
        
363
        
364
        logMetacat.debug("Returning from CrudService.create()");
365
        EventLog.getInstance().log(metacatUrl,
366
                username, localId, "create");
367
        logCrud.info("create localId:" + localId + " guid:" + guid.getValue());
368
        return guid;
369
    }
370
    
371
    /**
372
     * update an existing object with a new object.  Change the system metadata
373
     * to reflect the changes and update it as well.
374
     */
375
    public Identifier update(AuthToken token, Identifier guid, 
376
            InputStream object, Identifier obsoletedGuid, SystemMetadata sysmeta) 
377
            throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
378
            UnsupportedType, InsufficientResources, NotFound, InvalidSystemMetadata, 
379
            NotImplemented {
380
        try
381
        {
382
            SessionData sessionData = getSessionData(token);
383
            
384
            //find the old systemmetadata (sm.old) document id (the one linked to obsoletedGuid)
385
            SystemMetadata sm = getSystemMetadata(token, obsoletedGuid);
386
            //change sm.old's obsoletedBy field 
387
            List l = sm.getObsoletedByList();
388
            l.add(guid);
389
            sm.setObsoletedByList(l);
390
            //update sm.old
391
            updateSystemMetadata(sm, sessionData);
392
            
393
            //change the obsoletes field of the new systemMetadata (sm.new) to point to the id of the old one
394
            sysmeta.addObsolete(obsoletedGuid);
395
            //insert sm.new
396
            insertSystemMetadata(sysmeta, sessionData);
397
            
398
            boolean isScienceMetadata = isScienceMetadata(sysmeta);
399
            if(isScienceMetadata)
400
            {
401
                //update the doc
402
                updateDocument(object, obsoletedGuid, guid, sessionData, false);
403
            }
404
            else
405
            {
406
                //update a data file, not xml
407
                insertDataObject(object, guid, sessionData);
408
            }
409
            
410
            IdentifierManager im = IdentifierManager.getInstance();
411
            String username = "public";
412
            if(sessionData != null)
413
            {
414
                username = sessionData.getUserName();
415
            }
416
            EventLog.getInstance().log(metacatUrl,
417
                    username, im.getLocalId(guid.getValue()), "update");
418
            logCrud.info("update localId:" + im.getLocalId(guid.getValue()) + " guid:" + guid.getValue());
419
            return guid;
420
        }
421
        catch(Exception e)
422
        {
423
            throw new ServiceFailure("1030", "Error updating document in CrudService: " + e.getMessage());
424
        }
425
    }
426
    
427
    /**
428
     * set access control on the doc
429
     * @param token
430
     * @param id
431
     * @param principal
432
     * @param permission
433
     */
434
    public void setAccess(AuthToken token, Identifier id, String principal, int permission,
435
      String permissionType, String permissionOrder, boolean setSystemMetadata)
436
      throws ServiceFailure
437
    {
438
        String perm = "";
439
        if(permission >= 4)
440
        {
441
            perm = "read";
442
        }
443
        if(permission >= 6)
444
        {
445
            perm = "write";
446
        }
447
        System.out.println("perm in setAccess: " + perm);
448
        System.out.println("permission in setAccess: " + permission);
449
        setAccess(token, id, principal, perm, permissionType, permissionOrder,
450
                setSystemMetadata);
451
       
452
    }
453
    
454
    /**
455
     * set the permission on the document
456
     * @param token
457
     * @param principal
458
     * @param permission
459
     * @param permissionType
460
     * @param permissionOrder
461
     * @return
462
     */
463
    public void setAccess(AuthToken token, Identifier id, String principal, String permission,
464
            String permissionType, String permissionOrder, boolean setSystemMetadata)
465
      throws ServiceFailure
466
    {
467
        try
468
        {
469
            final SessionData sessionData = getSessionData(token);
470
            if(sessionData == null)
471
            {
472
                throw new ServiceFailure("1000", "User must be logged in to set access.");
473
            }
474
            IdentifierManager im = IdentifierManager.getInstance();
475
            String docid = im.getLocalId(id.getValue());
476
        
477
            String permNum = "0";
478
            if(permission.equals("read"))
479
            {
480
                permNum = "4";
481
            }
482
            else if(permission.equals("write"))
483
            {
484
                permNum = "6";
485
            }
486
            System.out.println("user " + sessionData.getUserName() + 
487
                    " is setting access level " + permNum + " for permission " + 
488
                    permissionType + " on doc with localid " + docid);
489
            handler.setAccess(metacatUrl, sessionData.getUserName(), docid, 
490
                    principal, permNum, permissionType, permissionOrder);
491
            if(setSystemMetadata)
492
            {
493
                //set the same perms on the system metadata doc
494
                String smlocalid = im.getSystemMetadataLocalId(id.getValue());
495
                System.out.println("setting access on SM doc with localid " + smlocalid);
496
                //cs.setAccess(token, smid, principal, permission, permissionType, permissionOrder);
497
                handler.setAccess(metacatUrl, sessionData.getUserName(), smlocalid,
498
                        principal, permNum, permissionType, permissionOrder);
499
            }
500
            String username = "public";
501
            if(sessionData != null)
502
            {
503
                username = sessionData.getUserName();
504
            }
505
            EventLog.getInstance().log(metacatUrl,
506
                    username, im.getLocalId(id.getValue()), "setAccess");
507
            logCrud.info("setAccess");
508
        }
509
        catch(Exception e)
510
        {
511
            e.printStackTrace();
512
            throw new ServiceFailure("1000", "Could not set access on the document with id " + id.getValue());
513
        }
514
    }
515
    
516
    /**
517
     *  Retrieve the list of objects present on the MN that match the calling 
518
     *  parameters. This method is required to support the process of Member 
519
     *  Node synchronization. At a minimum, this method should be able to 
520
     *  return a list of objects that match:
521
     *  startTime <= SystemMetadata.dateSysMetadataModified
522
     *  but is expected to also support date range (by also specifying endTime), 
523
     *  and should also support slicing of the matching set of records by 
524
     *  indicating the starting index of the response (where 0 is the index 
525
     *  of the first item) and the count of elements to be returned.
526
     *  
527
     *  If startTime or endTime is null, the query is not restricted by that parameter.
528
     *  
529
     * @see http://mule1.dataone.org/ArchitectureDocs/mn_api_replication.html#MN_replication.listObjects
530
     * @param token
531
     * @param startTime
532
     * @param endTime
533
     * @param objectFormat
534
     * @param replicaStatus
535
     * @param start
536
     * @param count
537
     * @return ObjectList
538
     * @throws NotAuthorized
539
     * @throws InvalidRequest
540
     * @throws NotImplemented
541
     * @throws ServiceFailure
542
     * @throws InvalidToken
543
     */
544
    public ObjectList listObjects(AuthToken token, Date startTime, Date endTime, 
545
        ObjectFormat objectFormat, boolean replicaStatus, int start, int count)
546
      throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken
547
    {
548
      ObjectList ol = new ObjectList();
549
      final SessionData sessionData = getSessionData(token);
550
      int totalAfterQuery = 0;
551
      try
552
      {
553
          System.out.println("=========== Listing Objects =============");
554
          System.out.println("Current server time is: " + new Date());
555
          if(startTime != null)
556
          {
557
              System.out.println("query start time is " + startTime);
558
          }
559
          if(endTime != null)
560
          {
561
              System.out.println("query end time is " + endTime);
562
          }
563
          params.clear();
564
          params.put("returndoctype", new String[] {"http://dataone.org/service/types/SystemMetadata/0.1"});
565
          params.put("qformat", new String[] {"xml"});
566
          params.put("returnfield", new String[] {"size", "originMemberNode", 
567
                  "identifier", "objectFormat", "dateSysMetadataModified", "checksum", "checksum/@algorithm"});
568
          params.put("anyfield", new String[] {"%"});
569
          
570
          //System.out.println("query is: metacatUrl: " + metacatUrl + " user: " + sessionData.getUserName() +
571
          //    " sessionid: " + sessionData.getId() + " params: " + params.toString());
572
          String username = "public";
573
          String[] groups = null;
574
          String sessionid = "";
575
          if(sessionData != null)
576
          {
577
              username = sessionData.getUserName();
578
              groups = sessionData.getGroupNames();
579
              sessionid = sessionData.getId();
580
          }
581
          
582
          MetacatResultSet rs = handler.query(metacatUrl, params, username, 
583
                  groups, sessionid);
584
          List docs = rs.getDocuments();
585
          if(count == 1000)
586
          {
587
              count = docs.size();
588
          }
589
          
590
          //System.out.println("query returned " + docs.size() + " documents.");
591
          Vector<Document> docCopy = new Vector<Document>();
592
          
593
          //preparse the list to remove any that don't match the query params
594
          for(int i=0; i<docs.size(); i++)
595
          {
596
              Document d = (Document)docs.get(i);
597
              ObjectFormat returnedObjectFormat = ObjectFormat.convert(d.getField("objectFormat"));
598
              
599
              if(returnedObjectFormat != null && 
600
                 objectFormat != null && 
601
                 !objectFormat.toString().trim().equals(returnedObjectFormat.toString().trim()))
602
              { //make sure the objectFormat is the one specified
603
                  continue;
604
              }
605
              
606
              String dateSMM = d.getField("dateSysMetadataModified");
607
              if((startTime != null || endTime != null) && dateSMM == null)
608
              {  //if startTime or endTime are not null, we need a date to compare to
609
                  continue;
610
              }
611
              
612
              //date parse
613
              Date dateSysMetadataModified = null;
614
              if(dateSMM != null)
615
              {
616
                  //dateSysMetadataModified = parseDate(dateSMM);
617
                  if(dateSMM.indexOf(".") != -1)
618
                  {  //strip the milliseconds
619
                      dateSMM = dateSMM.substring(0, dateSMM.indexOf(".")) + 'Z';
620
                  }
621
                  //System.out.println("dateSMM: " + dateSMM);
622
                  //dateFormat.setTimeZone(TimeZone.getTimeZone("GMT-0"));
623
                  try
624
                  {   //the format we want
625
                      dateSysMetadataModified = dateFormat.parse(dateSMM);
626
                  }
627
                  catch(java.text.ParseException pe)
628
                  {   //try another legacy format
629
                      DateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
630
                      dateFormat2.setTimeZone(TimeZone.getTimeZone("GMT-0"));
631
                      dateSysMetadataModified = dateFormat2.parse(dateSMM);
632
                  }                  
633
              }
634
              
635
              /*System.out.println("docid: " + d.docid);
636
              System.out.println("dateSysMetadataModified: " + dateSysMetadataModified);
637
              System.out.println("startTime: " + startTime);
638
              System.out.println("endtime: " + endTime);*/
639
              
640
              int startDateComparison = 0;
641
              int endDateComparison = 0;
642
              if(startTime != null)
643
              {
644
                  Calendar zTime = Calendar.getInstance(TimeZone.getTimeZone("GMT-0"));
645
                  zTime.setTime(startTime);
646
                  startTime = zTime.getTime();
647
                  
648
                  if(dateSysMetadataModified == null)
649
                  {
650
                      startDateComparison = -1;
651
                  }
652
                  else
653
                  {
654
                      startDateComparison = dateSysMetadataModified.compareTo(startTime);
655
                  }
656
                  //System.out.println("startDateCom: " + startDateComparison);
657
              }
658
              else
659
              {
660
                  startDateComparison = 1;
661
              }
662
              
663
              if(endTime != null)
664
              {
665
                  Calendar zTime = Calendar.getInstance(TimeZone.getTimeZone("GMT-0"));
666
                  zTime.setTime(endTime);
667
                  endTime = zTime.getTime();
668
                  
669
                  if(dateSysMetadataModified == null)
670
                  {
671
                      endDateComparison = 1;
672
                  }
673
                  else
674
                  {
675
                      endDateComparison = dateSysMetadataModified.compareTo(endTime);
676
                  }
677
                  //System.out.println("endDateCom: " + endDateComparison);
678
              }
679
              else
680
              {
681
                  endDateComparison = -1;
682
              }
683
              
684
              
685
              if(startDateComparison < 0 || endDateComparison > 0)
686
              { 
687
                  continue;                  
688
              }
689
              
690
              docCopy.add((Document)docs.get(i));
691
          } //end pre-parse
692
          
693
          docs = docCopy;
694
          totalAfterQuery = docs.size();
695
          //System.out.println("total after subquery: " + totalAfterQuery);
696
          
697
          //make sure we don't run over the end
698
          int end = start + count;
699
          if(end > docs.size())
700
          {
701
              end = docs.size();
702
          }
703
          
704
          for(int i=start; i<end; i++)
705
          {
706
              //get the document from the result
707
              Document d = (Document)docs.get(i);
708
              //System.out.println("processing doc " + d.docid);
709
              
710
              String dateSMM = d.getField("dateSysMetadataModified");
711
              Date dateSysMetadataModified = null;
712
              if(dateSMM != null)
713
              {
714
                  try
715
                  {
716
                      dateSysMetadataModified = parseDate(dateSMM);
717
                  }
718
                  catch(Exception e)
719
                  { //if we fail to parse the date, just ignore the value
720
                      dateSysMetadataModified = null;
721
                  }
722
              }
723
              ObjectFormat returnedObjectFormat = ObjectFormat.convert(d.getField("objectFormat"));
724
                
725
              
726
              ObjectInfo info = new ObjectInfo();
727
              //add the fields to the info object
728
              Checksum cs = new Checksum();
729
              cs.setValue(d.getField("checksum"));
730
              String csalg = d.getField("algorithm");
731
              if(csalg == null)
732
              {
733
                  csalg = "MD5";
734
              }
735
              ChecksumAlgorithm ca = ChecksumAlgorithm.convert(csalg);
736
              cs.setAlgorithm(ca);
737
              info.setChecksum(cs);
738
              info.setDateSysMetadataModified(dateSysMetadataModified);
739
              Identifier id = new Identifier();
740
              id.setValue(d.getField("identifier").trim());
741
              info.setIdentifier(id);
742
              info.setObjectFormat(returnedObjectFormat);
743
              String size = d.getField("size");
744
              if(size != null)
745
              {
746
                  info.setSize(new Long(size.trim()).longValue());
747
              }
748
              //add the ObjectInfo to the ObjectList
749
              if(info.getIdentifier().getValue() != null)
750
              { //id can be null from tests.  should not happen in production.
751
                  ol.addObjectInfo(info);
752
              }
753
             
754
          }
755
      }
756
      catch(Exception e)
757
      {
758
          e.printStackTrace();
759
          throw new ServiceFailure("1580", "Error retrieving ObjectList: " + e.getMessage());
760
      }
761
      String username = "public";
762
      if(sessionData != null)
763
      {
764
          username = sessionData.getUserName();
765
      }
766
      EventLog.getInstance().log(metacatUrl,
767
              username, null, "read");
768
      logCrud.info("listObjects");
769
      ol.setCount(count);
770
      ol.setStart(start);
771
      ol.setTotal(totalAfterQuery);
772
      return ol;
773
    }
774
    
775
    /**
776
     * Call listObjects with the default values for replicaStatus (true), start (0),
777
     * and count (1000).
778
     * @param token
779
     * @param startTime
780
     * @param endTime
781
     * @param objectFormat
782
     * @return
783
     * @throws NotAuthorized
784
     * @throws InvalidRequest
785
     * @throws NotImplemented
786
     * @throws ServiceFailure
787
     * @throws InvalidToken
788
     */
789
    public ObjectList listObjects(AuthToken token, Date startTime, Date endTime, 
790
        ObjectFormat objectFormat)
791
      throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken
792
    {
793
       return listObjects(token, startTime, endTime, objectFormat, true, 0, 1000);
794
    }
795

    
796
    /**
797
     * Delete a document.  NOT IMPLEMENTED
798
     */
799
    public Identifier delete(AuthToken token, Identifier guid)
800
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
801
            NotImplemented {
802
        logCrud.info("delete");
803
        throw new NotImplemented("1000", "This method not yet implemented.");
804
    }
805

    
806
    /**
807
     * describe a document.  NOT IMPLEMENTED
808
     */
809
    public DescribeResponse describe(AuthToken token, Identifier guid)
810
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
811
            NotImplemented {
812
        logCrud.info("describe");
813
        throw new NotImplemented("1000", "This method not yet implemented.");
814
    }
815
    
816
    /**
817
     * get a document with a specified guid.
818
     */
819
    public InputStream get(AuthToken token, Identifier guid)
820
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
821
            NotImplemented {
822
        
823
        // Retrieve the session information from the AuthToken
824
        // If the session is expired, then the user is 'public'
825
        if(token == null)
826
        {
827
            token = new AuthToken("Public");
828
        }
829
        final SessionData sessionData = getSessionData(token);
830
        
831
        // Look up the localId for this global identifier
832
        IdentifierManager im = IdentifierManager.getInstance();
833
        try {
834
            final String localId = im.getLocalId(guid.getValue());
835

    
836
            final InputStreamFromOutputStream<String> objectStream = 
837
                new InputStreamFromOutputStream<String>() {
838
                
839
                @Override
840
                public String produce(final OutputStream dataSink) throws Exception {
841

    
842
                    try {
843
                        String username = "public";
844
                        String[] groups = new String[0];
845
                        if(sessionData != null)
846
                        {
847
                            username = sessionData.getUserName();
848
                            groups = sessionData.getGroupNames();
849
                        }
850
                        /*System.out.println("metacatUrl: " + metacatUrl + 
851
                            " dataSink: " + dataSink + " localId: " + localId + 
852
                            " username: " + username + " params: " + params.toString());
853
                        */    
854
                        handler.readFromMetacat(metacatUrl, null, 
855
                                dataSink, localId, "xml",
856
                                username, 
857
                                groups, true, params);
858
                    } catch (PropertyNotFoundException e) {
859
                        e.printStackTrace();
860
                        throw new ServiceFailure("1030", "Error getting property from metacat: " + e.getMessage());
861
                    } catch (ClassNotFoundException e) {
862
                        e.printStackTrace();
863
                        throw new ServiceFailure("1030", "Class not found error when reading from metacat: " + e.getMessage());
864
                    } catch (IOException e) {
865
                        e.printStackTrace();
866
                        throw new ServiceFailure("1030", "IOException while reading from metacat: " + e.getMessage());
867
                    } catch (SQLException e) {
868
                        e.printStackTrace();
869
                        throw new ServiceFailure("1030", "SQLException while reading from metacat: " + e.getMessage());
870
                    } catch (McdbException e) {
871
                        e.printStackTrace();
872
                        throw new ServiceFailure("1030", "Metacat DB exception while reading from metacat: " + e.getMessage());
873
                    } catch (ParseLSIDException e) {
874
                        e.printStackTrace();
875
                        throw new NotFound("1020", "LSID parsing exception while reading from metacat: " + e.getMessage());
876
                    } catch (InsufficientKarmaException e) {
877
                        e.printStackTrace();
878
                        throw new NotAuthorized("1000", "User not authorized for get(): " + e.getMessage());
879
                    }
880

    
881
                    return "Completed";
882
                }
883
            };
884
            String username = "public";
885
            if(sessionData != null)
886
            {
887
                username = sessionData.getUserName();
888
            }
889
            
890
            EventLog.getInstance().log(metacatUrl,
891
                    username, im.getLocalId(guid.getValue()), "read");
892
            logCrud.info("get localId:" + localId + " guid:" + guid.getValue());
893
            return objectStream;
894

    
895
        } catch (McdbDocNotFoundException e) {
896
            throw new NotFound("1020", e.getMessage());
897
        }
898
    }
899

    
900
    /**
901
     * get the checksum for a document.  NOT IMPLEMENTED
902
     */
903
    public Checksum getChecksum(AuthToken token, Identifier guid)
904
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
905
            InvalidRequest, NotImplemented {
906
        logCrud.info("getChecksum");
907
        throw new NotImplemented("1000", "This method not yet implemented.");
908
    }
909

    
910
    /**
911
     * get the checksum for a document.  NOT IMPLEMENTED
912
     */
913
    public Checksum getChecksum(AuthToken token, Identifier guid, 
914
            String checksumAlgorithm) throws InvalidToken, ServiceFailure, 
915
            NotAuthorized, NotFound, InvalidRequest, NotImplemented {
916
        logCrud.info("getChecksum");
917
        throw new NotImplemented("1000", "This method not yet implemented.");
918
    }
919

    
920
    /**
921
     * get log records.  
922
     */
923
    public Log getLogRecords(AuthToken token, Date fromDate, Date toDate, Event event)
924
            throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest, 
925
            NotImplemented 
926
    {
927
        System.out.println("=================== Getting log records ===================");
928
        System.out.println("Current server time is: " + new Date());
929
        if(fromDate != null)
930
        {
931
          System.out.println("query start time is " + fromDate);
932
        }
933
        if(toDate != null)
934
        {
935
          System.out.println("query end time is " + toDate);
936
        }
937
        Log log = new Log();
938
        Vector<LogEntry> logs = new Vector<LogEntry>();
939
        IdentifierManager im = IdentifierManager.getInstance();
940
        EventLog el = EventLog.getInstance();
941
        if(fromDate == null)
942
        {
943
            System.out.println("setting fromdate from null");
944
            fromDate = new Date(1);
945
        }
946
        if(toDate == null)
947
        {
948
            System.out.println("setting todate from null");
949
            toDate = new Date();
950
        }
951
        
952
        System.out.println("fromDate: " + fromDate);
953
        System.out.println("toDate: " + toDate);
954
        
955
        String report = el.getReport(null, null, null, null, 
956
                new java.sql.Timestamp(fromDate.getTime()), 
957
                new java.sql.Timestamp(toDate.getTime()));
958
        
959
        //System.out.println("report: " + report);
960
        
961
        String logEntry = "<logEntry>";
962
        String endLogEntry = "</logEntry>";
963
        int startIndex = 0;
964
        int foundIndex = report.indexOf(logEntry, startIndex);
965
        while(foundIndex != -1)
966
        {
967
            //parse out each entry
968
            int endEntryIndex = report.indexOf(endLogEntry, foundIndex);
969
            String entry = report.substring(foundIndex, endEntryIndex);
970
            //System.out.println("entry: " + entry);
971
            startIndex = endEntryIndex + endLogEntry.length();
972
            foundIndex = report.indexOf(logEntry, startIndex);
973
            
974
            String entryId = getLogEntryField("entryid", entry);
975
            String ipAddress = getLogEntryField("ipAddress", entry);
976
            String principal = getLogEntryField("principal", entry);
977
            String docid = getLogEntryField("docid", entry);
978
            String eventS = getLogEntryField("event", entry);
979
            String dateLogged = getLogEntryField("dateLogged", entry);
980
            
981
            LogEntry le = new LogEntry();
982
            
983
            Event e = Event.convert(eventS);
984
            if(e == null)
985
            { //skip any events that are not Dataone Crud events
986
                continue;
987
            }
988
            le.setEvent(e);
989
            Identifier entryid = new Identifier();
990
            entryid.setValue(entryId);
991
            le.setEntryId(entryid);
992
            Identifier identifier = new Identifier();
993
            try
994
            {
995
                //System.out.println("converting docid '" + docid + "' to a guid.");
996
                if(docid == null || docid.trim().equals("") || docid.trim().equals("null"))
997
                {
998
                    continue;
999
                }
1000
                docid = docid.substring(0, docid.lastIndexOf("."));
1001
                identifier.setValue(im.getGUID(docid, im.getLatestRevForLocalId(docid)));
1002
            }
1003
            catch(Exception ex)
1004
            { //try to get the guid, if that doesn't work, just use the local id
1005
                //throw new ServiceFailure("1030", "Error getting guid for localId " + 
1006
                //        docid + ": " + ex.getMessage());\
1007
                
1008
                //skip it if the guid can't be found
1009
                continue;
1010
            }
1011
            
1012
            le.setIdentifier(identifier);
1013
            le.setIpAddress(ipAddress);
1014
            Calendar c = Calendar.getInstance();
1015
            String year = dateLogged.substring(0, 4);
1016
            String month = dateLogged.substring(5, 7);
1017
            String date = dateLogged.substring(8, 10);
1018
            //System.out.println("year: " + year + " month: " + month + " day: " + date);
1019
            c.set(new Integer(year).intValue(), new Integer(month).intValue(), new Integer(date).intValue());
1020
            Date logDate = c.getTime();
1021
            le.setDateLogged(logDate);
1022
            NodeReference memberNode = new NodeReference();
1023
            memberNode.setValue(ipAddress);
1024
            le.setMemberNode(memberNode);
1025
            Principal princ = new Principal();
1026
            princ.setValue(principal);
1027
            le.setPrincipal(princ);
1028
            le.setUserAgent("metacat/RESTService");
1029
            
1030
            if(event == null)
1031
            {
1032
                logs.add(le);
1033
            }
1034
            
1035
            if(event != null &&
1036
               e.toString().toLowerCase().trim().equals(event.toString().toLowerCase().trim()))
1037
            {
1038
              logs.add(le);
1039
            }
1040
        }
1041
        
1042
        log.setLogEntryList(logs);
1043
        logCrud.info("getLogRecords");
1044
        return log;
1045
    }
1046
    
1047
    /**
1048
     * parse a logEntry and get the relavent field from it
1049
     * @param fieldname
1050
     * @param entry
1051
     * @return
1052
     */
1053
    private String getLogEntryField(String fieldname, String entry)
1054
    {
1055
        String begin = "<" + fieldname + ">";
1056
        String end = "</" + fieldname + ">";
1057
        //System.out.println("looking for " + begin + " and " + end + " in entry " + entry);
1058
        String s = entry.substring(entry.indexOf(begin) + begin.length(), entry.indexOf(end));
1059
        //System.out.println("entry " + fieldname + " : " + s);
1060
        return s;
1061
    }
1062

    
1063
    /**
1064
     * get the system metadata for a document with a specified guid.
1065
     */
1066
    public SystemMetadata getSystemMetadata(AuthToken token, Identifier guid)
1067
            throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
1068
            InvalidRequest, NotImplemented {
1069
        
1070
        logMetacat.debug("CrudService.getSystemMetadata - for guid: " + guid.getValue());
1071
        
1072
        // Retrieve the session information from the AuthToken
1073
        // If the session is expired, then the user is 'public'
1074
        final SessionData sessionData = getSessionData(token);
1075
                
1076
        try {
1077
            IdentifierManager im = IdentifierManager.getInstance();
1078
            final String localId = im.getSystemMetadataLocalId(guid.getValue());
1079
            
1080
            // Read system metadata from metacat's db
1081
            final InputStreamFromOutputStream<String> objectStream = 
1082
                new InputStreamFromOutputStream<String>() {
1083
                
1084
                @Override
1085
                public String produce(final OutputStream dataSink) throws Exception {
1086
                    try {
1087
                        String username = "public";
1088
                        String[] groupnames = null;
1089
                        if(sessionData != null)
1090
                        {
1091
                            username = sessionData.getUserName();
1092
                            groupnames = sessionData.getGroupNames();
1093
                        }
1094
                        
1095
                        handler.readFromMetacat(metacatUrl, null, 
1096
                                dataSink, localId, "xml",
1097
                                username, 
1098
                                groupnames, true, params);
1099
                    } catch (PropertyNotFoundException e) {
1100
                        e.printStackTrace();
1101
                        throw new ServiceFailure("1030", "Property not found while reading system metadata from metacat: " + e.getMessage());
1102
                    } catch (ClassNotFoundException e) {
1103
                        e.printStackTrace();
1104
                        throw new ServiceFailure("1030", "Class not found while reading system metadata from metacat: " + e.getMessage());
1105
                    } catch (IOException e) {
1106
                        e.printStackTrace();
1107
                        throw new ServiceFailure("1030", "IOException while reading system metadata from metacat: " + e.getMessage());
1108
                    } catch (SQLException e) {
1109
                        e.printStackTrace();
1110
                        throw new ServiceFailure("1030", "SQLException while reading system metadata from metacat: " + e.getMessage());
1111
                    } catch (McdbException e) {
1112
                        e.printStackTrace();
1113
                        throw new ServiceFailure("1030", "Metacat DB Exception while reading system metadata from metacat: " + e.getMessage());
1114
                    } catch (ParseLSIDException e) {
1115
                        e.printStackTrace();
1116
                        throw new NotFound("1020", "Error parsing LSID while reading system metadata from metacat: " + e.getMessage());
1117
                    } catch (InsufficientKarmaException e) {
1118
                        e.printStackTrace();
1119
                        throw new NotAuthorized("1000", "User not authorized for get() on system metadata: " + e.getMessage());
1120
                    }
1121

    
1122
                    return "Completed";
1123
                }
1124
            };
1125
            
1126
            // Deserialize the xml to create a SystemMetadata object
1127
            SystemMetadata sysmeta = deserializeSystemMetadata(objectStream);
1128
            String username = "public";
1129
            if(sessionData != null)
1130
            {
1131
                username = sessionData.getUserName();
1132
            }
1133
            EventLog.getInstance().log(metacatUrl,
1134
                    username, im.getLocalId(guid.getValue()), "read");
1135
            logCrud.info("getSystemMetadata localId: " + localId + " guid:" + guid.getValue());
1136
            return sysmeta;
1137
            
1138
        } catch (McdbDocNotFoundException e) {
1139
            //e.printStackTrace();
1140
            throw new NotFound("1000", e.getMessage());
1141
        }                
1142
    }
1143
    
1144
    /**
1145
     * parse the date in the systemMetadata
1146
     * @param s
1147
     * @return
1148
     * @throws Exception
1149
     */
1150
    public Date parseDate(String s)
1151
      throws Exception
1152
    {
1153
        Date d = null;
1154
        int tIndex = s.indexOf("T");
1155
        int zIndex = s.indexOf("Z");
1156
        if(tIndex != -1 && zIndex != -1)
1157
        { //parse a date that looks like 2010-05-18T21:12:54.362Z
1158
            //System.out.println("original date: " + s);
1159
            
1160
            String date = s.substring(0, tIndex);
1161
            String year = date.substring(0, date.indexOf("-"));
1162
            String month = date.substring(date.indexOf("-") + 1, date.lastIndexOf("-"));
1163
            String day = date.substring(date.lastIndexOf("-") + 1, date.length());
1164
            /*System.out.println("date: " + "year: " + new Integer(year).intValue() + 
1165
                    " month: " + new Integer(month).intValue() + " day: " + 
1166
                    new Integer(day).intValue());
1167
            */
1168
            String time = s.substring(tIndex + 1, zIndex);
1169
            String hour = time.substring(0, time.indexOf(":"));
1170
            String minute = time.substring(time.indexOf(":") + 1, time.lastIndexOf(":"));
1171
            String seconds = "00";
1172
            String milliseconds = "00";
1173
            if(time.indexOf(".") != -1)
1174
            {
1175
                seconds = time.substring(time.lastIndexOf(":") + 1, time.indexOf("."));
1176
                milliseconds = time.substring(time.indexOf(".") + 1, time.length());
1177
            }
1178
            else
1179
            {
1180
                seconds = time.substring(time.lastIndexOf(":") + 1, time.length());
1181
            }
1182
            /*System.out.println("time: " + "hour: " + new Integer(hour).intValue() + 
1183
                    " minute: " + new Integer(minute).intValue() + " seconds: " + 
1184
                    new Integer(seconds).intValue() + " milli: " + 
1185
                    new Integer(milliseconds).intValue());*/
1186
            
1187
            //d = DateFormat.getDateTimeInstance().parse(date + " " + time);
1188
            Calendar c = Calendar.getInstance(/*TimeZone.getTimeZone("GMT-0")*/TimeZone.getDefault());
1189
            c.set(new Integer(year).intValue(), new Integer(month).intValue() - 1, 
1190
                  new Integer(day).intValue(), new Integer(hour).intValue(), 
1191
                  new Integer(minute).intValue(), new Integer(seconds).intValue());
1192
            c.set(Calendar.MILLISECOND, new Integer(milliseconds).intValue());
1193
            d = new Date(c.getTimeInMillis());
1194
            //System.out.println("d: " + d);
1195
            return d;
1196
        }
1197
        else
1198
        {  //if it's not in the expected format, try the formatter
1199
            return DateFormat.getDateTimeInstance().parse(s);
1200
        }
1201
    }
1202

    
1203
    /*
1204
     * Look up the information on the session using the token provided in
1205
     * the AuthToken.  The Session should have all relevant user information.
1206
     * If the session has expired or is invalid, the 'public' session will
1207
     * be returned, giving the user anonymous access.
1208
     */
1209
    public static SessionData getSessionData(AuthToken token) {
1210
        SessionData sessionData = null;
1211
        String sessionId = "PUBLIC";
1212
        if (token != null) {
1213
            sessionId = token.getToken();
1214
        }
1215
        
1216
        // if the session id is registered in SessionService, get the
1217
        // SessionData for it. Otherwise, use the public session.
1218
        //System.out.println("sessionid: " + sessionId);
1219
        if (sessionId != null &&
1220
            !sessionId.toLowerCase().equals("public") &&
1221
            SessionService.getInstance().isSessionRegistered(sessionId)) 
1222
        {
1223
            sessionData = SessionService.getInstance().getRegisteredSession(sessionId);
1224
        } else {
1225
            sessionData = SessionService.getInstance().getPublicSession();
1226
        }
1227
        
1228
        return sessionData;
1229
    }
1230

    
1231
    /** 
1232
     * Determine if a given object should be treated as an XML science metadata
1233
     * object. 
1234
     * 
1235
     * TODO: This test should be externalized in a configuration dictionary rather than being hardcoded.
1236
     * 
1237
     * @param sysmeta the SystemMetadata describig the object
1238
     * @return true if the object should be treated as science metadata
1239
     */
1240
    private boolean isScienceMetadata(SystemMetadata sysmeta) {
1241
        boolean scimeta = false;
1242
        switch (sysmeta.getObjectFormat()) {
1243
            case EML_2_1_0: scimeta = true; break;
1244
            case EML_2_0_1: scimeta = true; break;
1245
            case EML_2_0_0: scimeta = true; break;
1246
            case FGDC_STD_001_1_1999: scimeta = true; break;
1247
            case FGDC_STD_001_1998: scimeta = true; break;
1248
            case NCML_2_2: scimeta = true; break;
1249
        }
1250
        
1251
        return scimeta;
1252
    }
1253

    
1254
    /**
1255
     * insert a data doc
1256
     * @param object
1257
     * @param guid
1258
     * @param sessionData
1259
     * @throws ServiceFailure
1260
     */
1261
    private void insertDataObject(InputStream object, Identifier guid, 
1262
            SessionData sessionData) throws ServiceFailure {
1263
        
1264
        String username = "public";
1265
        String[] groups = null;
1266
        if(sessionData != null)
1267
        {
1268
          username = sessionData.getUserName();
1269
          groups = sessionData.getGroupNames();
1270
        }
1271

    
1272
        // generate guid/localId pair for object
1273
        logMetacat.debug("Generating a guid/localId mapping");
1274
        IdentifierManager im = IdentifierManager.getInstance();
1275
        String localId = im.generateLocalId(guid.getValue(), 1);
1276

    
1277
        try {
1278
            logMetacat.debug("Case DATA: starting to write to disk.");
1279
            if (DocumentImpl.getDataFileLockGrant(localId)) {
1280
    
1281
                // Save the data file to disk using "localId" as the name
1282
                try {
1283
                    String datafilepath = PropertyService.getProperty("application.datafilepath");
1284
    
1285
                    File dataDirectory = new File(datafilepath);
1286
                    dataDirectory.mkdirs();
1287
    
1288
                    File newFile = writeStreamToFile(dataDirectory, localId, object);
1289
    
1290
                    // TODO: Check that the file size matches SystemMetadata
1291
                    //                        long size = newFile.length();
1292
                    //                        if (size == 0) {
1293
                    //                            throw new IOException("Uploaded file is 0 bytes!");
1294
                    //                        }
1295
    
1296
                    // Register the file in the database (which generates an exception
1297
                    // if the localId is not acceptable or other untoward things happen
1298
                    try {
1299
                        logMetacat.debug("Registering document...");
1300
                        DocumentImpl.registerDocument(localId, "BIN", localId,
1301
                                username, groups);
1302
                        logMetacat.debug("Registration step completed.");
1303
                    } catch (SQLException e) {
1304
                        //newFile.delete();
1305
                        logMetacat.debug("SQLE: " + e.getMessage());
1306
                        e.printStackTrace(System.out);
1307
                        throw new ServiceFailure("1190", "Registration failed: " + e.getMessage());
1308
                    } catch (AccessionNumberException e) {
1309
                        //newFile.delete();
1310
                        logMetacat.debug("ANE: " + e.getMessage());
1311
                        e.printStackTrace(System.out);
1312
                        throw new ServiceFailure("1190", "Registration failed: " + e.getMessage());
1313
                    } catch (Exception e) {
1314
                        //newFile.delete();
1315
                        logMetacat.debug("Exception: " + e.getMessage());
1316
                        e.printStackTrace(System.out);
1317
                        throw new ServiceFailure("1190", "Registration failed: " + e.getMessage());
1318
                    }
1319
    
1320
                    logMetacat.debug("Logging the creation event.");
1321
                    EventLog.getInstance().log(metacatUrl,
1322
                            username, localId, "create");
1323
    
1324
                    // Schedule replication for this data file
1325
                    logMetacat.debug("Scheduling replication.");
1326
                    ForceReplicationHandler frh = new ForceReplicationHandler(
1327
                            localId, "create", false, null);
1328
    
1329
                } catch (PropertyNotFoundException e) {
1330
                    throw new ServiceFailure("1190", "Could not lock file for writing:" + e.getMessage());
1331
                }
1332
    
1333
            }
1334
        } catch (Exception e) {
1335
            // Could not get a lock on the document, so we can not update the file now
1336
            throw new ServiceFailure("1190", "Failed to lock file: " + e.getMessage());
1337
        }
1338
    }
1339

    
1340
    /**
1341
     * write a file to a stream
1342
     * @param dir
1343
     * @param fileName
1344
     * @param data
1345
     * @return
1346
     * @throws ServiceFailure
1347
     */
1348
    private File writeStreamToFile(File dir, String fileName, InputStream data) 
1349
        throws ServiceFailure {
1350
        
1351
        File newFile = new File(dir, fileName);
1352
        logMetacat.debug("Filename for write is: " + newFile.getAbsolutePath());
1353

    
1354
        try {
1355
            if (newFile.createNewFile()) {
1356
                // write data stream to desired file
1357
                OutputStream os = new FileOutputStream(newFile);
1358
                long length = IOUtils.copyLarge(data, os);
1359
                os.flush();
1360
                os.close();
1361
            } else {
1362
                logMetacat.debug("File creation failed, or file already exists.");
1363
                throw new ServiceFailure("1190", "File already exists: " + fileName);
1364
            }
1365
        } catch (FileNotFoundException e) {
1366
            logMetacat.debug("FNF: " + e.getMessage());
1367
            throw new ServiceFailure("1190", "File not found: " + fileName + " " 
1368
                    + e.getMessage());
1369
        } catch (IOException e) {
1370
            logMetacat.debug("IOE: " + e.getMessage());
1371
            throw new ServiceFailure("1190", "File was not written: " + fileName 
1372
                    + " " + e.getMessage());
1373
        }
1374

    
1375
        return newFile;
1376
    }
1377
    
1378
    private static Date getDateInTimeZone(Date currentDate, String timeZoneId)
1379
    {
1380
        TimeZone tz = TimeZone.getTimeZone(timeZoneId);
1381
        Calendar mbCal = new GregorianCalendar(TimeZone.getTimeZone(timeZoneId));
1382
        mbCal.setTimeInMillis(currentDate.getTime());
1383

    
1384
        Calendar cal = Calendar.getInstance();
1385
        cal.set(Calendar.YEAR, mbCal.get(Calendar.YEAR));
1386
        cal.set(Calendar.MONTH, mbCal.get(Calendar.MONTH));
1387
        cal.set(Calendar.DAY_OF_MONTH, mbCal.get(Calendar.DAY_OF_MONTH));
1388
        cal.set(Calendar.HOUR_OF_DAY, mbCal.get(Calendar.HOUR_OF_DAY));
1389
        cal.set(Calendar.MINUTE, mbCal.get(Calendar.MINUTE));
1390
        cal.set(Calendar.SECOND, mbCal.get(Calendar.SECOND));
1391
        cal.set(Calendar.MILLISECOND, mbCal.get(Calendar.MILLISECOND));
1392

    
1393
        return cal.getTime();
1394
    }
1395

    
1396
    /**
1397
     * insert a systemMetadata doc, return the localId of the sysmeta
1398
     */
1399
    private String insertSystemMetadata(SystemMetadata sysmeta, SessionData sessionData) 
1400
        throws ServiceFailure 
1401
    {
1402
        logMetacat.debug("Starting to insert SystemMetadata...");
1403
    
1404
        // generate guid/localId pair for sysmeta
1405
        Identifier sysMetaGuid = new Identifier();
1406
        sysMetaGuid.setValue(DocumentUtil.generateDocumentId(1));
1407
        sysmeta.setDateSysMetadataModified(new Date());
1408
        System.out.println("****inserting new system metadata with modified date " + 
1409
                sysmeta.getDateSysMetadataModified());
1410

    
1411
        String xml = new String(serializeSystemMetadata(sysmeta).toByteArray());
1412
        System.out.println("sysmeta: " + xml);
1413
        String localId = insertDocument(xml, sysMetaGuid, sessionData, true);
1414
        System.out.println("sysmeta inserted with localId " + localId);
1415
        //insert the system metadata doc id into the systemmetadata table to 
1416
        //link it to the data or metadata document
1417
        IdentifierManager.getInstance().createSystemMetadataMapping(
1418
                sysmeta.getIdentifier().getValue(), sysMetaGuid.getValue());
1419
        return localId;
1420
    }
1421
    
1422
    /**
1423
     * update a systemMetadata doc
1424
     */
1425
    private void updateSystemMetadata(SystemMetadata sm, SessionData sessionData)
1426
      throws ServiceFailure
1427
    {
1428
        try
1429
        {
1430
            String smId = IdentifierManager.getInstance().getSystemMetadataLocalId(sm.getIdentifier().getValue());
1431
            sm.setDateSysMetadataModified(new Date());
1432
            String xml = new String(serializeSystemMetadata(sm).toByteArray());
1433
            Identifier id = new Identifier();
1434
            id.setValue(smId);
1435
            String localId = updateDocument(xml, id, null, sessionData, true);
1436
            IdentifierManager.getInstance().updateSystemMetadataMapping(sm.getIdentifier().getValue(), localId);
1437
        }
1438
        catch(Exception e)
1439
        {
1440
            throw new ServiceFailure("1030", "Error updating system metadata: " + e.getMessage());
1441
        }
1442
    }
1443
    
1444
    private String insertDocument(String xml, Identifier guid, SessionData sessionData)
1445
        throws ServiceFailure
1446
    {
1447
        return insertDocument(xml, guid, sessionData, false);
1448
    }
1449
    
1450
    /**
1451
     * insert a document
1452
     * NOTE: this method shouldn't be used from the update or create() methods.  
1453
     * we shouldn't be putting the science metadata or data objects into memory.
1454
     */
1455
    private String insertDocument(String xml, Identifier guid, SessionData sessionData,
1456
            boolean isSystemMetadata)
1457
        throws ServiceFailure
1458
    {
1459
        return insertOrUpdateDocument(xml, guid, sessionData, "insert", isSystemMetadata);
1460
    }
1461
    
1462
    /**
1463
     * insert a document from a stream
1464
     */
1465
    private String insertDocument(InputStream is, Identifier guid, SessionData sessionData)
1466
      throws IOException, ServiceFailure
1467
    {
1468
        //HACK: change this eventually.  we should not be converting the stream to a string
1469
        String xml = IOUtils.toString(is);
1470
        return insertDocument(xml, guid, sessionData);
1471
    }
1472
    
1473
    /**
1474
     * update a document
1475
     * NOTE: this method shouldn't be used from the update or create() methods.  
1476
     * we shouldn't be putting the science metadata or data objects into memory.
1477
     */
1478
    private String updateDocument(String xml, Identifier obsoleteGuid, 
1479
            Identifier guid, SessionData sessionData, boolean isSystemMetadata)
1480
        throws ServiceFailure
1481
    {
1482
        return insertOrUpdateDocument(xml, obsoleteGuid, sessionData, "update", isSystemMetadata);
1483
    }
1484
    
1485
    /**
1486
     * update a document from a stream
1487
     */
1488
    private String updateDocument(InputStream is, Identifier obsoleteGuid, 
1489
            Identifier guid, SessionData sessionData, boolean isSystemMetadata)
1490
      throws IOException, ServiceFailure
1491
    {
1492
        //HACK: change this eventually.  we should not be converting the stream to a string
1493
        String xml = IOUtils.toString(is);
1494
        String localId = updateDocument(xml, obsoleteGuid, guid, sessionData, isSystemMetadata);
1495
        IdentifierManager im = IdentifierManager.getInstance();
1496
        if(guid != null)
1497
        {
1498
          im.createMapping(guid.getValue(), localId);
1499
        }
1500
        return localId;
1501
    }
1502
    
1503
    /**
1504
     * insert a document, return the id of the document that was inserted
1505
     */
1506
    protected String insertOrUpdateDocument(String xml, Identifier guid, 
1507
            SessionData sessionData, String insertOrUpdate, boolean isSystemMetadata) 
1508
        throws ServiceFailure {
1509
        logMetacat.debug("Starting to insert xml document...");
1510
        IdentifierManager im = IdentifierManager.getInstance();
1511

    
1512
        // generate guid/localId pair for sysmeta
1513
        String localId = null;
1514
        if(insertOrUpdate.equals("insert"))
1515
        {
1516
            localId = im.generateLocalId(guid.getValue(), 1, isSystemMetadata);
1517
        }
1518
        else
1519
        {
1520
            //localid should already exist in the identifier table, so just find it
1521
            try
1522
            {
1523
                localId = im.getLocalId(guid.getValue());
1524
                //increment the revision
1525
                String docid = localId.substring(0, localId.lastIndexOf("."));
1526
                String revS = localId.substring(localId.lastIndexOf(".") + 1, localId.length());
1527
                int rev = new Integer(revS).intValue();
1528
                rev++;
1529
                docid = docid + "." + rev;
1530
                localId = docid;
1531
            }
1532
            catch(McdbDocNotFoundException e)
1533
            {
1534
                throw new ServiceFailure("1030", "CrudService.insertOrUpdateDocument(): " +
1535
                    "guid " + guid.getValue() + " should have been in the identifier table, but it wasn't: " + e.getMessage());
1536
            }
1537
        }
1538
        logMetacat.debug("Metadata guid|localId: " + guid.getValue() + "|" +
1539
                localId);
1540

    
1541
        String[] action = new String[1];
1542
        action[0] = insertOrUpdate;
1543
        params.put("action", action);
1544
        String[] docid = new String[1];
1545
        docid[0] = localId;
1546
        params.put("docid", docid);
1547
        String[] doctext = new String[1];
1548
        doctext[0] = xml;
1549
        logMetacat.debug(doctext[0]);
1550
        params.put("doctext", doctext);
1551
        
1552
        // TODO: refactor handleInsertOrUpdateAction() to not output XML directly
1553
        // onto output stream, or alternatively, capture that and parse it to 
1554
        // generate the right exceptions
1555
        //ByteArrayOutputStream output = new ByteArrayOutputStream();
1556
        //PrintWriter pw = new PrintWriter(output);
1557
        String username = "public";
1558
        String[] groupnames = null;
1559
        if(sessionData != null)
1560
        {
1561
            username = sessionData.getUserName();
1562
            groupnames = sessionData.getGroupNames();
1563
        }
1564
        String result = handler.handleInsertOrUpdateAction(metacatUrl, null, 
1565
                            null, params, username, groupnames);
1566
        //String outputS = new String(output.toByteArray());
1567
        logMetacat.debug("CrudService.insertDocument - Metacat returned: " + result);
1568
        logMetacat.debug("Finsished inserting xml document with id " + localId);
1569
        return localId;
1570
    }
1571
    
1572
    /**
1573
     * serialize a system metadata doc
1574
     * @param sysmeta
1575
     * @return
1576
     * @throws ServiceFailure
1577
     */
1578
    public static ByteArrayOutputStream serializeSystemMetadata(SystemMetadata sysmeta) 
1579
        throws ServiceFailure {
1580
        IBindingFactory bfact;
1581
        ByteArrayOutputStream sysmetaOut = null;
1582
        try {
1583
            bfact = BindingDirectory.getFactory(SystemMetadata.class);
1584
            IMarshallingContext mctx = bfact.createMarshallingContext();
1585
            sysmetaOut = new ByteArrayOutputStream();
1586
            mctx.marshalDocument(sysmeta, "UTF-8", null, sysmetaOut);
1587
        } catch (JiBXException e) {
1588
            e.printStackTrace();
1589
            throw new ServiceFailure("1190", "Failed to serialize and insert SystemMetadata: " + e.getMessage());
1590
        }
1591
        
1592
        return sysmetaOut;
1593
    }
1594
    
1595
    /**
1596
     * deserialize a system metadata doc
1597
     * @param xml
1598
     * @return
1599
     * @throws ServiceFailure
1600
     */
1601
    public static SystemMetadata deserializeSystemMetadata(InputStream xml) 
1602
        throws ServiceFailure {
1603
        try {
1604
            IBindingFactory bfact = BindingDirectory.getFactory(SystemMetadata.class);
1605
            IUnmarshallingContext uctx = bfact.createUnmarshallingContext();
1606
            SystemMetadata sysmeta = (SystemMetadata) uctx.unmarshalDocument(xml, null);
1607
            return sysmeta;
1608
        } catch (JiBXException e) {
1609
            e.printStackTrace();
1610
            throw new ServiceFailure("1190", "Failed to deserialize and insert SystemMetadata: " + e.getMessage());
1611
        }    
1612
    }
1613
    
1614
    /**
1615
     * produce an md5 checksum for item
1616
     */
1617
    private String checksum(InputStream is)
1618
      throws Exception
1619
    {        
1620
        byte[] buffer = new byte[1024];
1621
        MessageDigest complete = MessageDigest.getInstance("MD5");
1622
        int numRead;
1623
        
1624
        do 
1625
        {
1626
          numRead = is.read(buffer);
1627
          if (numRead > 0) 
1628
          {
1629
            complete.update(buffer, 0, numRead);
1630
          }
1631
        } while (numRead != -1);
1632
        
1633
        
1634
        return getHex(complete.digest());
1635
    }
1636
    
1637
    /**
1638
     * convert a byte array to a hex string
1639
     */
1640
    private static String getHex( byte [] raw ) 
1641
    {
1642
        final String HEXES = "0123456789ABCDEF";
1643
        if ( raw == null ) {
1644
          return null;
1645
        }
1646
        final StringBuilder hex = new StringBuilder( 2 * raw.length );
1647
        for ( final byte b : raw ) {
1648
          hex.append(HEXES.charAt((b & 0xF0) >> 4))
1649
             .append(HEXES.charAt((b & 0x0F)));
1650
        }
1651
        return hex.toString();
1652
    }
1653
    
1654
    /**
1655
     * parse the metacat date which looks like 2010-06-08 (YYYY-MM-DD) into
1656
     * a proper date object
1657
     * @param date
1658
     * @return
1659
     */
1660
    private Date parseMetacatDate(String date)
1661
    {
1662
        String year = date.substring(0, 4);
1663
        String month = date.substring(5, 7);
1664
        String day = date.substring(8, 10);
1665
        Calendar c = Calendar.getInstance(TimeZone.getDefault());
1666
        c.set(new Integer(year).intValue(), 
1667
              new Integer(month).intValue(), 
1668
              new Integer(day).intValue());
1669
        System.out.println("time in parseMetacatDate: " + c.getTime());
1670
        return c.getTime();
1671
    }
1672
    
1673
    /**
1674
     * find the size (in bytes) of a stream
1675
     * @param is
1676
     * @return
1677
     * @throws IOException
1678
     */
1679
    private long sizeOfStream(InputStream is)
1680
        throws IOException
1681
    {
1682
        long size = 0;
1683
        byte[] b = new byte[1024];
1684
        int numread = is.read(b, 0, 1024);
1685
        while(numread != -1)
1686
        {
1687
            size += numread;
1688
            numread = is.read(b, 0, 1024);
1689
        }
1690
        return size;
1691
    }
1692
    
1693
    /**
1694
     * create system metadata with a specified id, doc and format
1695
     */
1696
    private SystemMetadata createSystemMetadata(String localId, AuthToken token)
1697
      throws Exception
1698
    {
1699
        IdentifierManager im = IdentifierManager.getInstance();
1700
        Hashtable<String, Object> docInfo = im.getDocumentInfo(localId);
1701
        
1702
        //get the document text
1703
        int rev = im.getLatestRevForLocalId(localId);
1704
        Identifier identifier = new Identifier();
1705
        identifier.setValue(im.getGUID(localId, rev));
1706
        InputStream is = this.get(token, identifier);
1707
        
1708
        SystemMetadata sm = new SystemMetadata();
1709
        //set the id
1710
        sm.setIdentifier(identifier);
1711
        
1712
        //set the object format
1713
        String doctype = (String)docInfo.get("doctype");
1714
        ObjectFormat format = ObjectFormat.convert((String)docInfo.get("doctype"));
1715
        if(format == null)
1716
        {
1717
            if(doctype.trim().equals("BIN"))
1718
            {
1719
                format = ObjectFormat.APPLICATIONOCTETSTREAM;
1720
            }
1721
            else
1722
            {
1723
                format = ObjectFormat.convert("text/plain");
1724
            }
1725
        }
1726
        sm.setObjectFormat(format);
1727
        
1728
        //create the checksum
1729
        String checksumS = checksum(is);
1730
        ChecksumAlgorithm ca = ChecksumAlgorithm.convert("MD5");
1731
        Checksum checksum = new Checksum();
1732
        checksum.setValue(checksumS);
1733
        checksum.setAlgorithm(ca);
1734
        sm.setChecksum(checksum);
1735
        
1736
        //set the size
1737
        is = this.get(token, identifier);
1738
        sm.setSize(sizeOfStream(is));
1739
        
1740
        //submitter
1741
        Principal p = new Principal();
1742
        p.setValue((String)docInfo.get("user_owner"));
1743
        sm.setSubmitter(p);
1744
        sm.setRightsHolder(p);
1745
        try
1746
        {
1747
            Date dateCreated = parseMetacatDate((String)docInfo.get("date_created"));
1748
            sm.setDateUploaded(dateCreated);
1749
            Date dateUpdated = parseMetacatDate((String)docInfo.get("date_updated"));
1750
            sm.setDateSysMetadataModified(dateUpdated);
1751
        }
1752
        catch(Exception e)
1753
        {
1754
            System.out.println("POSSIBLE ERROR: couldn't parse a date: " + e.getMessage());
1755
            Date dateCreated = new Date();
1756
            sm.setDateUploaded(dateCreated);
1757
            Date dateUpdated = new Date();
1758
            sm.setDateSysMetadataModified(dateUpdated);
1759
        }
1760
        NodeReference nr = new NodeReference();
1761
        nr.setValue("metacat");
1762
        sm.setOriginMemberNode(nr);
1763
        sm.setAuthoritativeMemberNode(nr);
1764
        return sm;
1765
    }
1766
}
(1-1/2)