Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that implements replication for metacat
4
 *  Copyright: 2000 Regents of the University of California and the
5
 *             National Center for Ecological Analysis and Synthesis
6
 *    Authors: Chad Berkley
7
 *    Release: @release@
8
 *
9
 *   '$Author: tao $'
10
 *     '$Date: 2002-05-08 16:07:54 -0700 (Wed, 08 May 2002) $'
11
 * '$Revision: 1065 $'
12
 *
13
 * This program is free software; you can redistribute it and/or modify
14
 * it under the terms of the GNU General Public License as published by
15
 * the Free Software Foundation; either version 2 of the License, or
16
 * (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 * GNU General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU General Public License
24
 * along with this program; if not, write to the Free Software
25
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
 */
27

    
28
package edu.ucsb.nceas.metacat;
29

    
30
import java.util.*;
31
import java.io.*;
32
import java.sql.*;
33
import java.net.*;
34
import java.lang.*;
35
import java.text.*;
36
import javax.servlet.*;
37
import javax.servlet.http.*;
38

    
39
import org.xml.sax.*;
40

    
41
public class MetacatReplication extends HttpServlet implements Runnable
42
{  
43
  private String deltaT;
44
  Timer replicationDaemon;
45
  private static MetaCatUtil util = new MetaCatUtil();
46
  private Vector fileLocks = new Vector();
47
  private Thread lockThread = null;
48
  
49
  /**
50
   * Initialize the servlet by creating appropriate database connections
51
   */
52
  public void init(ServletConfig config) throws ServletException 
53
  {
54
    //initialize db connections to handle any update requests
55
    MetaCatUtil util = new MetaCatUtil();
56
    deltaT = util.getOption("deltaT");
57
    //the default deltaT can be set from metacat.properties
58
    //create a thread to do the delta-T check but don't execute it yet
59
    replicationDaemon = new Timer(true);
60
  }
61
  
62
  public void destroy() 
63
  {
64
    replicationDaemon.cancel();
65
   // System.out.println("Replication daemon cancelled.");
66
  }
67
  
68
  public void doGet (HttpServletRequest request, HttpServletResponse response)
69
                     throws ServletException, IOException 
70
  {
71
    // Process the data and send back the response
72
    handleGetOrPost(request, response);
73
  }
74

    
75
  public void doPost(HttpServletRequest request, HttpServletResponse response)
76
                     throws ServletException, IOException 
77
  {
78
    // Process the data and send back the response
79
    handleGetOrPost(request, response);
80
  }
81
  
82
  private void handleGetOrPost(HttpServletRequest request, 
83
                               HttpServletResponse response) 
84
                               throws ServletException, IOException 
85
  {
86
    //PrintWriter out = response.getWriter();
87
    //ServletOutputStream outPut = response.getOutputStream();
88
    Hashtable params = new Hashtable();
89
    Enumeration paramlist = request.getParameterNames();
90
    
91
    //First check if administrate conifgure the replcation feature on
92
    //This configuration is set in build.xml file
93
    //If it is not on, reject any request.
94
    if (!(util.getOption("replication")).equals("on"))
95
    {
96
     (response.getWriter()).println("MetaCat is not set to handle replication");
97
      (response.getWriter()).close();
98
      return;
99
    }
100
    
101
// NOT NEEDED - doesn't provide enough security because of possible IP spoofing
102
// REPLACED with running replication comminications over HTTPS
103
//    String requestingServerIP = request.getRemoteAddr();
104
//    InetAddress iaddr = InetAddress.getByName(requestingServerIP);
105
//    String requestingServer = iaddr.getHostName();
106
    
107
    while (paramlist.hasMoreElements()) {
108
      String name = (String)paramlist.nextElement();
109
      String[] value = request.getParameterValues(name);
110
      params.put(name, value);  
111
    }
112
    
113
    String action = ((String[])params.get("action"))[0];
114
    String server = null;
115
    
116
    try {
117
      // check if the server is included in the list of replicated servers
118
      if ( !action.equals("servercontrol") && 
119
           !action.equals("stop") &&
120
           !action.equals("start") &&
121
           !action.equals("getall") ) {
122

    
123
        server = ((String[])params.get("server"))[0];
124
        if ( getServerCode(server) == 0 ) {
125
          System.out.println("Action \"" + action + 
126
                             "\" rejected for server: " + server);
127
          return;
128
        } else {
129
          System.out.println("Action \"" + action + 
130
                             "\" accepted for server: " + server);
131
        }
132
      }
133
    } catch (Exception e) {
134
      System.out.println("Error in MetacatReplication.handleGetOrPost: " +
135
                         e.getMessage() );
136
      return;
137
    }
138
    if ( action.equals("readdata") ) 
139
    {
140
      OutputStream out=response.getOutputStream();
141
      //to get the data file.
142
      handleGetDataFileRequest(out, params, response);
143
      out.close();
144
    }
145
    else if ( action.equals("forcereplicatedatafile") ) 
146
    {
147
     
148
      //OutputStream out=response.getOutputStream();
149
      //read a specific docid from remote host, and store it into local host
150
      handleForceReplicateDataFileRequest(params);
151
      //out.close();
152
    }
153
    else
154
    {
155
    PrintWriter out = response.getWriter();
156
    if ( action.equals("stop") ) {
157
      //stop the replication server
158
      replicationDaemon.cancel();
159
      replicationDaemon = new Timer(true);
160
      out.println("Replication Handler Stopped");
161
      MetacatReplication.replLog("deltaT handler stopped");
162
    
163

    
164
    } else if ( action.equals("start") ) {
165
      
166
      //start the replication server
167
      int rate;
168
      if ( params.containsKey("rate") ) {
169
        rate = new Integer(
170
               new String(((String[])params.get("rate"))[0])).intValue();
171
        if(rate < 30) {
172
            out.println("Replication deltaT rate cannot be less than 30!");
173
            //deltaT<30 is a timing mess!
174
            rate = 1000;
175
        }
176
      } else {
177
        rate = 1000;
178
      }
179
      out.println("New rate is: " + rate + " seconds.");
180
      replicationDaemon.cancel();
181
      replicationDaemon = new Timer(true);
182
      replicationDaemon.scheduleAtFixedRate(new ReplicationHandler(out), 0, 
183
                                            rate * 1000);
184
      out.println("Replication Handler Started");
185
      MetacatReplication.replLog("deltaT handler started with rate=" + 
186
                                    rate + " seconds");
187
     
188

    
189
    } else if ( action.equals("getall") ) {
190
      //updates this server exactly once
191
      replicationDaemon.schedule(new ReplicationHandler(out), 0);
192
      response.setContentType("text/html");
193
      out.println("<html><body>\"Get All\" Done</body></html>");
194

    
195
    } else if ( action.equals("forcereplicate") ) {
196
      //read a specific docid from remote host, and store it into local host
197
      handleForceReplicateRequest(out, params, response);
198
   
199
    } else if ( action.equals("update") ) {
200
      //request an update list from the server
201
      handleUpdateRequest(out, params, response);
202

    
203
    } else if ( action.equals("read") ) {
204
      //request a specific document from the server
205
      //note that this could be replaced by a call to metacatServlet
206
      //handleGetDocumentAction().
207
      handleGetDocumentRequest(out, params, response);
208
    } else if ( action.equals("getlock") ) {
209
      handleGetLockRequest(out, params, response);
210

    
211
    } else if ( action.equals("getdocumentinfo") ) {
212
      handleGetDocumentInfoRequest(out, params, response);
213

    
214
    } else if ( action.equals("gettime") ) {
215
      handleGetTimeRequest(out, params, response);
216

    
217
    } else if ( action.equals("getcatalog") ) {
218
      handleGetCatalogRequest(out, params, response, true);
219

    
220
    } else if ( action.equals("servercontrol") ) {
221
      handleServerControlRequest(out, params, response);
222
    }
223
    
224
    out.close();
225
    }//else
226
  }
227
  
228
  /** 
229
   * This method can add, delete and list the servers currently included in
230
   * xml_replication.
231
   * action           subaction            other needed params
232
   * ---------------------------------------------------------
233
   * servercontrol    add                  server
234
   * servercontrol    delete               server
235
   * servercontrol    list                 
236
   */
237
  private void handleServerControlRequest(PrintWriter out, Hashtable params,
238
                                          HttpServletResponse response)
239
  {
240
    String subaction = ((String[])params.get("subaction"))[0];
241
    Connection conn = null;
242

    
243
    try {
244
      conn = util.openDBConnection();
245
      PreparedStatement pstmt = null;
246

    
247
      // add server to server list
248
      if ( subaction.equals("add") ) {
249
        String replicate = ((String[])params.get("replicate"))[0];
250
        String server = ((String[])params.get("server"))[0];
251
        pstmt = conn.prepareStatement("INSERT INTO xml_replication " +
252
                                      "(server, last_checked, replicate) " +
253
                                      "VALUES ('" + server + "', to_date(" +
254
                                      "'01/01/00', 'MM/DD/YY'), '" +
255
                                      replicate + "')");
256
        pstmt.execute();
257
        pstmt.close();
258
        conn.commit();
259
        out.println("Server " + server + " added"); 
260
        response.setContentType("text/html");
261
        out.println("<html><body><table border=\"1\">");
262
        out.println("<tr><td><b>server</b></td><td><b>last_checked</b></td><td>");
263
        out.println("<b>replicate</b></td></tr>");
264
        pstmt = conn.prepareStatement("SELECT * FROM xml_replication");
265
        pstmt.execute();
266
        ResultSet rs = pstmt.getResultSet();
267
        boolean tablehasrows = rs.next();
268
        while(tablehasrows) {
269
          out.println("<tr><td>" + rs.getString(2) + "</td><td>");
270
          out.println(rs.getString(3) + "</td><td>");
271
          out.println(rs.getString(4) + "</td></tr>");
272
          tablehasrows = rs.next();
273
        }
274
        out.println("</table></body></html>");
275
        
276
        // download certificate with the public key on this server
277
        // and import it as a trusted certificate
278
        String certURL = ((String[])params.get("certificate"))[0];
279
        downloadCertificate(certURL);
280
        
281
      // delete server from server list
282
      } else if ( subaction.equals("delete") ) {
283
        String server = ((String[])params.get("server"))[0];
284
        pstmt = conn.prepareStatement("DELETE FROM xml_replication " +
285
                                      "WHERE server LIKE '" + server + "'");
286
        pstmt.execute();
287
        pstmt.close();
288
        conn.commit();
289
        out.println("Server " + server + " deleted");
290
        response.setContentType("text/html");
291
        out.println("<html><body><table border=\"1\">");
292
        out.println("<tr><td><b>server</b></td><td><b>last_checked</b></td><td>");
293
        out.println("<b>replicate</b></td></tr>");
294
        pstmt = conn.prepareStatement("SELECT * FROM xml_replication");
295
        pstmt.execute();
296
        ResultSet rs = pstmt.getResultSet();
297
        boolean tablehasrows = rs.next();
298
        while(tablehasrows)
299
        {
300
          out.println("<tr><td>" + rs.getString(2) + "</td><td>");
301
          out.println(rs.getString(3) + "</td><td>");
302
          out.println(rs.getString(4) + "</td></tr>");
303
          tablehasrows = rs.next();
304
        }
305
        out.println("</table></body></html>");
306

    
307
      // list servers in server list
308
      } else if ( subaction.equals("list") ) {
309
        response.setContentType("text/html");
310
        out.println("<html><body><table border=\"1\">");
311
        out.println("<tr><td><b>server</b></td><td><b>last_checked</b></td><td>");
312
        out.println("<b>replicate</b></td></tr>");
313
        pstmt = conn.prepareStatement("SELECT * FROM xml_replication");
314
        pstmt.execute();
315
        ResultSet rs = pstmt.getResultSet();
316
        boolean tablehasrows = rs.next();
317
        while(tablehasrows) {
318
          out.println("<tr><td>" + rs.getString(2) + "</td><td>");
319
          out.println(rs.getString(3) + "</td><td>");
320
          out.println(rs.getString(4) + "</td></tr>");
321
          tablehasrows = rs.next();
322
        }
323
        out.println("</table></body></html>");
324
      }
325
      pstmt.close();
326
      conn.close();
327

    
328
    } catch(Exception e) {
329
      System.out.println("Error in " + 
330
                         "MetacatReplication.handleServerControlRequest " + 
331
                         e.getMessage());
332
      e.printStackTrace(System.out);
333
    }
334
  }
335
  
336
  // download certificate with the public key from certURL and 
337
  // upload it onto this server; it then must be imported as a 
338
  // trusted certificate 
339
  private void downloadCertificate (String certURL)
340
                throws FileNotFoundException, IOException, MalformedURLException
341
  {
342
    MetaCatUtil util = new MetaCatUtil();
343
    String certPath = util.getOption("certPath"); //the path to be uploaded to
344
    
345
    // get filename from the URL of the certificate
346
    String filename = certURL;
347
    int slash = Math.max(filename.lastIndexOf('/'), filename.lastIndexOf('\\'));
348
    if ( slash > -1 ) {
349
      filename = filename.substring(slash + 1);
350
    }
351
    
352
    // open file output strem to write the input into it
353
    File f = new File(certPath, filename);
354
    synchronized (f) { 
355
      try {
356
        if ( f.exists() ) {
357
          throw new IOException("File already exist: " + f.getCanonicalFile());
358
          //if ( f.exists() && !f.canWrite() ) {
359
          //  throw new IOException("Not writable: " + f.getCanonicalFile());
360
        }
361
      } catch (SecurityException se) {
362
        // if a security manager exists,
363
        // its checkRead method is called for f.exist()
364
        // or checkWrite method is called for f.canWrite()
365
        throw se;
366
      }
367
      
368
      // create a buffered byte output stream
369
      // that uses a default-sized output buffer
370
      FileOutputStream fos = new FileOutputStream(f);
371
      BufferedOutputStream out = new BufferedOutputStream(fos);
372

    
373
      // this should be http url
374
      URL url = new URL(certURL);
375
      BufferedInputStream bis = null;
376
      try {
377
        bis = new BufferedInputStream(url.openStream());
378
        byte[] buf = new byte[4 * 1024]; // 4K buffer
379
        int b = bis.read(buf);
380
        while (b != -1) {
381
          out.write(buf, 0, b);
382
          b = bis.read(buf);
383
        }
384
      } finally {
385
        if (bis != null) bis.close();
386
      }
387
      // the input and the output streams must be closed
388
      bis.close();
389
	    out.flush();
390
	    out.close();
391
	    fos.close();
392
    } // end of synchronized(f)
393
  }
394
  
395
  /**
396
   * when a forcereplication request comes in, local host sends a read request
397
   * to the requesting server (remote server) for the specified docid.
398
   * Then store it in local database.
399
   */
400
  private void handleForceReplicateRequest(PrintWriter out, Hashtable params,
401
                                           HttpServletResponse response)
402
  {
403
    String server = ((String[])params.get("server"))[0]; // the server that
404
    String docid = ((String[])params.get("docid"))[0]; // sent the document
405
    String dbaction = "UPDATE"; // the default action is UPDATE
406
    boolean override = false;
407
    int serverCode = 1;
408
   
409
    try {
410
      //if the url contains a dbaction then the default action is overridden
411
      if(params.containsKey("dbaction")) {
412
        dbaction = ((String[])params.get("dbaction"))[0];
413
        //serverCode = MetacatReplication.getServerCode(server);
414
        //override = true; //we are now overriding the default action
415
      }
416
      MetacatReplication.replLog("force replication request from " + server); 
417
      util.debugMessage("Force replication docid: "+docid, 20);
418
      util.debugMessage("Force replication action: "+dbaction, 20);
419
      // sending back read request to server
420
     
421
      URL u = new URL("https://" + server + "?server="
422
                +util.getLocalReplicationServerName()
423
                +"&action=read&docid=" + docid);
424
      String xmldoc = MetacatReplication.getURLContent(u);
425
    
426
      // get the document info from server
427
      URL docinfourl = new URL("https://" + server + 
428
                               "?server="+util.getLocalReplicationServerName()
429
                               +"&action=getdocumentinfo&docid=" + docid);
430
    
431
      String docInfoStr = MetacatReplication.getURLContent(docinfourl);
432
  
433
      //dih is the parser for the docinfo xml format
434
      DocInfoHandler dih = new DocInfoHandler();
435
      XMLReader docinfoParser = ReplicationHandler.initParser(dih);
436
      docinfoParser.parse(new InputSource(new StringReader(docInfoStr)));
437
      Hashtable docinfoHash = dih.getDocInfo();
438
      String user = (String)docinfoHash.get("user_owner");
439
      
440
      String homeServer=(String)docinfoHash.get("home_server");
441
      util.debugMessage("homeServer: "+homeServer,20);
442
      // write the document here
443
      Connection conn = util.openDBConnection();
444
      DocumentImpl.writeReplication(conn, new StringReader(xmldoc), null, null,
445
              dbaction, docid, user, null, homeServer, true);
446
              
447
       
448
      conn.close();
449

    
450
      MetacatReplication.replLog("document " + docid + " added to DB with " +
451
                                 "action " + dbaction);
452
    } catch(Exception e) {
453
      
454
      System.out.println("ERROR in MetacatReplication.handleForceReplicate" +
455
                         "Request(): " + e.getMessage());
456
       e.printStackTrace();
457
    }
458
  }
459
  
460
  /**
461
   * when a forcereplication data file request comes in, local host sends a 
462
   * readdata request to the requesting server (remote server) for the specified 
463
   * docid. Then store it in local database and file system
464
   */
465
  private void handleForceReplicateDataFileRequest(Hashtable params)
466
  {
467
    //check if the metacat was configured to handle replication 
468
    if (!(util.getOption("replicationdata")).equals("on"))
469
    {
470
      return;
471
    }
472
    //make sure there is some parameters
473
    if(params.isEmpty())
474
    {
475
      return;
476
    }
477
    String server = ((String[])params.get("server"))[0]; // the server that
478
    //docid should include rev number
479
    String docid = ((String[])params.get("docid"))[0]; // sent the document
480
    if (docid==null)
481
    {
482
      util.debugMessage("Didn't specify docid for replication", 20);
483
      return;
484
    }
485
    //docid was switch to two parts
486
    boolean override = false;
487
    int serverCode = 1;
488
    String dbaction=null;
489
    Connection conn=null;
490
    
491
    try 
492
    {
493
      //docid was switch to two parts
494
      String uniqueCode=MetaCatUtil.getDocIdFromString(docid);
495
      int rev=MetaCatUtil.getVersionFromString(docid);
496
      if(params.containsKey("dbaction")) 
497
      {
498
        dbaction = ((String[])params.get("dbaction"))[0];
499
      }
500
      else//default value is update
501
      {
502
        dbaction = "update";
503
      }
504
      //serverCode = MetacatReplication.getServerCode(server);
505
      MetacatReplication.replLog("force replication request from " + server); 
506
      
507
      // get the document info from server
508
      URL docinfourl = new URL("https://" + server + 
509
                               "?server="+util.getLocalReplicationServerName()
510
                               +"&action=getdocumentinfo&docid=" + uniqueCode);
511
     
512
      String docInfoStr = MetacatReplication.getURLContent(docinfourl);
513

    
514
      //dih is the parser for the docinfo xml format
515
      DocInfoHandler dih = new DocInfoHandler();
516
      XMLReader docinfoParser = ReplicationHandler.initParser(dih);
517
      docinfoParser.parse(new InputSource(new StringReader(docInfoStr)));
518
      Hashtable docinfoHash = dih.getDocInfo();
519
      String user = (String)docinfoHash.get("user_owner");
520
     
521
      String docName = (String)docinfoHash.get("docname");
522
     
523
      String docType = (String)docinfoHash.get("doctype");
524
      
525
      String docHomeServer= (String)docinfoHash.get("home_server");
526
      MetaCatUtil.debugMessage("docHomeServer of data file: "+docHomeServer);
527
      
528
      
529
      
530
      //if action is delete, we don't delete the data file. Just archieve
531
      //the xml_documents
532
      if (dbaction.equals("delete"))
533
      {
534
        conn = util.getConnection();
535
        DocumentImpl.delete(conn,docid,user,null);
536
        util.returnConnection(conn);
537
      }
538
      //To data file insert or update is same
539
      else if (dbaction.equals("insert")||dbaction.equals("update"))
540
      {
541
        //Get data file and store it into local file system.
542
        // sending back readdata request to server
543
        URL url = new URL("https://" + server + "?server="
544
                +util.getLocalReplicationServerName()
545
                +"&action=readdata&docid=" + docid);
546
        String datafilePath = util.getOption("datafilepath");
547
        //register data file into xml_documents table and wite data file
548
        //into file system
549
        DocumentImpl.writeDataFile(url.openStream(), datafilePath, docName,
550
                              docType, docid, user,docHomeServer);
551
     }
552
    
553
  
554
    
555
    MetacatReplication.replLog("document " + docid + " added to DB with " +
556
                                 "action " + dbaction);
557
    } catch(Exception e) 
558
    {
559
      
560
      util.returnConnection(conn);
561
      util.debugMessage("ERROR in MetacatReplication.handleForceReplicate" +
562
                         "Request(): " + e.getMessage(), 30);
563
    }
564
  }
565
  /**
566
   * Grants or denies a lock to a requesting host.
567
   * The servlet parameters of interrest are:
568
   * docid: the docid of the file the lock is being requested for
569
   * currentdate: the timestamp of the document on the remote server
570
   * 
571
   */
572
  private void handleGetLockRequest(PrintWriter out, Hashtable params,
573
                                    HttpServletResponse response)
574
  {
575
    Connection conn = null;
576
    try
577
    {
578
      conn = MetacatReplication.getDBConnection("MetacatReplication." +
579
                                                "handleGetLockRequest");
580
      String docid = ((String[])params.get("docid"))[0];
581
      String remoteRev = ((String[])params.get("updaterev"))[0];
582
      DocumentImpl requestDoc = new DocumentImpl(conn, docid);
583
      MetacatReplication.replLog("lock request for " + docid);
584
      int localRevInt = requestDoc.getRev();
585
      int remoteRevInt = Integer.parseInt(remoteRev);
586
      
587
      if(remoteRevInt >= localRevInt)
588
      {
589
        if(!fileLocks.contains(docid))
590
        { //grant the lock if it is not already locked
591
          fileLocks.add(0, docid); //insert at the beginning of the queue Vector
592
          //send a message back to the the remote host authorizing the insert
593
          out.println("<lockgranted><docid>" +docid+ "</docid></lockgranted>");
594
          lockThread = new Thread(this);
595
          lockThread.setPriority(Thread.MIN_PRIORITY);
596
          lockThread.start();
597
          MetacatReplication.replLog("lock granted for " + docid);
598
        }
599
        else
600
        { //deny the lock
601
          out.println("<filelocked><docid>" + docid + "</docid></filelocked>");
602
          MetacatReplication.replLog("lock denied for " + docid + 
603
                                     "reason: file already locked");
604
        }
605
      }
606
      else
607
      {//deny the lock.
608
        out.println("<outdatedfile><docid>" + docid + "</docid></filelocked>");
609
        MetacatReplication.replLog("lock denied for " + docid + 
610
                                   "reason: client has outdated file");
611
      }
612
      conn.close();
613
    }
614
    catch(Exception e)
615
    {
616
      System.out.println("error requesting file lock from MetacatReplication." +
617
                         "handleGetLockRequest: " + e.getMessage());
618
      e.printStackTrace(System.out);
619
    }
620
  }
621
  
622
  /**
623
   * Sends all of the xml_documents information encoded in xml to a requestor
624
   * the format is:
625
   * <!ELEMENT documentinfo (docid, docname, doctype, doctitle, user_owner,
626
   *                  user_updated, home_server, public_access, rev)/>
627
   * all of the subelements of document info are #PCDATA
628
   */
629
  private void handleGetDocumentInfoRequest(PrintWriter out, Hashtable params, 
630
                                        HttpServletResponse response)
631
  {
632
    String docid = ((String[])(params.get("docid")))[0];
633
    StringBuffer sb = new StringBuffer();
634
    Connection conn = null;
635
    try
636
    {
637
      conn = MetacatReplication.getDBConnection("MetacatReplication." +
638
                                                "handleGetDocumentInfoRequest");
639
      DocumentImpl doc = new DocumentImpl(conn, docid);
640
      sb.append("<documentinfo><docid>").append(docid);
641
      sb.append("</docid><docname>").append(doc.getDocname());
642
      sb.append("</docname><doctype>").append(doc.getDoctype());
643
      sb.append("</doctype>");
644
      sb.append("<user_owner>").append(doc.getUserowner());
645
      sb.append("</user_owner><user_updated>").append(doc.getUserupdated());
646
      sb.append("</user_updated>");
647
      sb.append("<home_server>");
648
      sb.append(doc.getDocHomeServer());
649
      sb.append("</home_server>");
650
      sb.append("<public_access>").append(doc.getPublicaccess());
651
      sb.append("</public_access><rev>").append(doc.getRev());
652
      sb.append("</rev></documentinfo>");
653
      response.setContentType("text/xml");
654
      out.println(sb.toString());
655
      conn.close();
656
    }
657
    catch (Exception e)
658
    {
659
      System.out.println("error in " +
660
                         "metacatReplication.handlegetdocumentinforequest: " + 
661
                          e.getMessage());
662
    }
663
    
664
  }
665
   
666
  /**
667
   * Sends a datafile to a remote host
668
   */
669
  private void handleGetDataFileRequest(OutputStream outPut, 
670
                            Hashtable params, HttpServletResponse response)
671
                                 
672
  {
673
    //check the metacat was configured to handle data replication
674
    if (!(util.getOption("replicationdata")).equals("on"))
675
    {
676
      return;
677
    }
678
    
679
    String filepath = util.getOption("datafilepath");
680
    String docId = ((String[])(params.get("docid")))[0];
681
    //check if the doicd is null
682
    if (docId==null)
683
    {
684
      util.debugMessage("Didn't specify docid for replication");
685
      return;
686
    }
687
  
688
    if(!filepath.endsWith("/")) 
689
    {
690
          filepath += "/";
691
    }
692
    String filename = filepath + docId;      //MIME type
693
    
694
    String contentType = null;//getServletContext().getMimeType(filename);
695
   
696
    if (contentType == null) 
697
    {
698
       if (filename.endsWith(".xml")) 
699
       {
700
          contentType="text/xml";
701
       } 
702
       else if (filename.endsWith(".css")) 
703
       {
704
          contentType="text/css";
705
       } 
706
       else if (filename.endsWith(".dtd")) 
707
       {
708
          contentType="text/plain";
709
       } 
710
       else if (filename.endsWith(".xsd")) 
711
       {
712
          contentType="text/xml";
713
       } 
714
       else if (filename.endsWith("/")) 
715
       {
716
          contentType="text/html";
717
       } 
718
       else 
719
       {
720
          File f = new File(filename);
721
          if ( f.isDirectory() ) 
722
          {
723
             contentType="text/html";
724
          } 
725
          else
726
          {
727
             contentType="application/octet-stream";
728
          }
729
       }
730
   }
731
  
732
   response.setContentType(contentType);
733
   // if we decide to use "application/octet-stream" for all data returns
734
   // response.setContentType("application/octet-stream");
735
   FileInputStream fin = null;
736
   try 
737
   {
738
      
739
      fin = new FileInputStream(filename);
740
      
741
      byte[] buf = new byte[4 * 1024]; // 4K buffer
742
      int b = fin.read(buf);
743
      while (b != -1) 
744
      {
745
        
746
        outPut.write(buf, 0, b);
747
        b = fin.read(buf);
748
      }
749
    
750
      fin.close();
751
   
752
   }  
753
   catch(Exception e)
754
   {
755
      System.out.println("error getting data file from MetacatReplication." +
756
                         "handlGetDataFileRequest " + e.getMessage());
757
      e.printStackTrace(System.out);
758
   }
759
  
760
}
761
  
762
  
763
  /**
764
   * Sends a document to a remote host
765
   */
766
  private void handleGetDocumentRequest(PrintWriter out, Hashtable params, 
767
                                        HttpServletResponse response)
768
  {
769
    Connection conn = null;
770
    try
771
    {
772
      String docid = ((String[])(params.get("docid")))[0];
773
      conn = MetacatReplication.getDBConnection("MetacatReplication." +
774
                                                "handleGetDocumentRequest");
775
      DocumentImpl di = new DocumentImpl(conn, docid);
776
      response.setContentType("text/xml");
777
      out.print(di.toString());
778
      conn.close();
779
      MetacatReplication.replLog("document " + docid + " sent");
780
    
781
    }
782
    catch(Exception e)
783
    {
784
      System.out.println("error getting document from MetacatReplication." +
785
                         "handlGetDocumentRequest " + e.getMessage());
786
      e.printStackTrace(System.out);
787
    }
788
    
789
  }
790
  
791
  /**
792
   * Sends a list of all of the documents on this sever along with their
793
   * revision numbers.  
794
   * The format is:
795
   * <!ELEMENT replication (server, updates)>
796
   * <!ELEMENT server (#PCDATA)>
797
   * <!ELEMENT updates ((updatedDocument | deleteDocument)*)>
798
   * <!ELEMENT updatedDocument (docid, rev, datafile*)>
799
   * <!ELEMENT deletedDocument (docid, rev)>
800
   * <!ELEMENT docid (#PCDATA)>
801
   * <!ELEMENT rev (#PCDATA)>
802
   * <!ELEMENT datafile (#PCDATA)>
803
   * note that the rev in deletedDocument is always empty.  I just left
804
   * it in there to make the parser implementation easier.
805
   */
806
  private void handleUpdateRequest(PrintWriter out, Hashtable params, 
807
                                    HttpServletResponse response)
808
  {
809
    Connection conn = null;
810
    try
811
    {
812
      
813
      StringBuffer docsql = new StringBuffer();
814
      StringBuffer doclist = new StringBuffer();
815
      Vector packageFiles = new Vector();
816
      
817
      //get all docs that reside on this server
818
      doclist.append("<?xml version=\"1.0\"?><replication>");
819
      doclist.append("<server>").append(util.getOption("server"));
820
      doclist.append(util.getOption("replicationpath"));
821
      doclist.append("</server><updates>");
822
      
823
      docsql.append("select docid, rev, doctype from xml_documents ");
824
      if (!(util.getOption("hub")).equals("super"))
825
      {
826
        docsql.append("where server_location = 1");
827
      }
828
      
829
      //get any deleted documents
830
      StringBuffer delsql = new StringBuffer();
831
      delsql.append("select distinct docid from ");
832
      delsql.append("xml_revisions where docid not in (select docid from ");
833
      delsql.append("xml_documents) ");
834
      if (!(util.getOption("hub")).equals("super"))
835
      {
836
        delsql.append("and server_location = 1");
837
      }
838
     
839
      conn = MetacatReplication.getDBConnection("MetacatReplication." +
840
                                                "handleUpdateRequest");
841
      PreparedStatement pstmt = conn.prepareStatement(docsql.toString());
842
      pstmt.execute();
843
      ResultSet rs = pstmt.getResultSet();
844
      boolean tablehasrows = rs.next();
845
      //If metacat configed to replicate data file
846
      if ((util.getOption("replicationdata")).equals("on"))
847
      {
848
        while(tablehasrows)
849
        {
850
          String recordDoctype = rs.getString(3);
851
          Vector packagedoctypes = MetaCatUtil.getOptionList(
852
                                     MetaCatUtil.getOption("packagedoctype"));
853
          if(!packagedoctypes.contains(recordDoctype))
854
          {   //if this is a package file, put it at the end
855
              //because if a package file is read before all of the files it
856
              //refers to are loaded then there is an error
857
              if (!recordDoctype.equals("BIN"))
858
              {
859
                //for non-data file document
860
                doclist.append("<updatedDocument>");
861
                doclist.append("<docid>").append(rs.getString(1));
862
                doclist.append("</docid><rev>").append(rs.getInt(2));
863
                doclist.append("</rev>");
864
                doclist.append("</updatedDocument>");
865
              }
866
              else
867
              {
868
                //for data file document, in datafile attributes
869
                //we put "datafile" value there
870
                doclist.append("<updatedDocument>");
871
                doclist.append("<docid>").append(rs.getString(1));
872
                doclist.append("</docid><rev>").append(rs.getInt(2));
873
                doclist.append("</rev>");
874
                doclist.append("<datafile>");
875
                doclist.append(MetaCatUtil.getOption("datafileflag"));
876
                doclist.append("</datafile>");
877
                doclist.append("</updatedDocument>");
878
              } 
879
          }
880
          else
881
          { //the package files are saved to be put into the xml later.
882
              Vector v = new Vector();
883
              v.add(new String(rs.getString(1)));
884
              v.add(new Integer(rs.getInt(2)));
885
              packageFiles.add(new Vector(v));
886
          }
887
          tablehasrows = rs.next();
888
        }//while
889
      }//if
890
      else //metacat was configured not to send data file
891
      {
892
        while(tablehasrows)
893
        {
894
          String recordDoctype = rs.getString(3);
895
          if(!recordDoctype.equals("BIN")) 
896
          { //don't replicate data files
897
            Vector packagedoctypes = MetaCatUtil.getOptionList(
898
                                     MetaCatUtil.getOption("packagedoctype"));
899
            if(!packagedoctypes.contains(recordDoctype))
900
            {   //if this is a package file, put it at the end
901
              //because if a package file is read before all of the files it
902
              //refers to are loaded then there is an error
903
              doclist.append("<updatedDocument>");
904
              doclist.append("<docid>").append(rs.getString(1));
905
              doclist.append("</docid><rev>").append(rs.getInt(2));
906
              doclist.append("</rev>");
907
              doclist.append("</updatedDocument>");
908
            }
909
            else
910
            { //the package files are saved to be put into the xml later.
911
              Vector v = new Vector();
912
              v.add(new String(rs.getString(1)));
913
              v.add(new Integer(rs.getInt(2)));
914
              packageFiles.add(new Vector(v));
915
            }
916
         }//if
917
         tablehasrows = rs.next();
918
        }//while
919
      }//else
920
      
921
      pstmt = conn.prepareStatement(delsql.toString());
922
      pstmt.execute();
923
      rs = pstmt.getResultSet();
924
      tablehasrows = rs.next();
925
      while(tablehasrows)
926
      { //handle the deleted documents
927
        doclist.append("<deletedDocument><docid>").append(rs.getString(1));
928
        doclist.append("</docid><rev></rev></deletedDocument>");
929
        //note that rev is always empty for deleted docs
930
        tablehasrows = rs.next();
931
      }
932
      
933
      //now we can put the package files into the xml results
934
      for(int i=0; i<packageFiles.size(); i++)
935
      {
936
        Vector v = (Vector)packageFiles.elementAt(i);
937
        doclist.append("<updatedDocument>");
938
        doclist.append("<docid>").append((String)v.elementAt(0));
939
        doclist.append("</docid><rev>");
940
        doclist.append(((Integer)v.elementAt(1)).intValue());
941
        doclist.append("</rev>");
942
        doclist.append("</updatedDocument>");
943
      }
944
      
945
      doclist.append("</updates></replication>");
946
      MetaCatUtil.debugMessage("doclist: " + doclist.toString());
947
      pstmt.close();
948
      conn.close();
949
      response.setContentType("text/xml");
950
      out.println(doclist.toString());
951
     
952
    }
953
    catch(Exception e)
954
    {
955
      System.out.println("error in MetacatReplication.handleupdaterequest: " + 
956
                          e.getMessage());
957
      e.printStackTrace(System.out);
958
    }
959
    
960
  }
961
  
962
  /**
963
   * Returns the xml_catalog table encoded in xml
964
   */
965
  public static String getCatalogXML()
966
  {
967
    return handleGetCatalogRequest(null, null, null, false);
968
  }
969
  
970
  /**
971
   * Sends the contents of the xml_catalog table encoded in xml
972
   * The xml format is:
973
   * <!ELEMENT xml_catalog (row*)>
974
   * <!ELEMENT row (entry_type, source_doctype, target_doctype, public_id,
975
   *                system_id)>
976
   * All of the sub elements of row are #PCDATA
977
   
978
   * If printFlag == false then do not print to out.
979
   */
980
  private static String handleGetCatalogRequest(PrintWriter out, 
981
                                                Hashtable params,
982
                                                HttpServletResponse response,
983
                                                boolean printFlag)
984
  {
985
    Connection conn = null;
986
    PreparedStatement pstmt = null;
987
    try
988
    { 
989
      conn = MetacatReplication.getDBConnection("MetacatReplication." +
990
                                                "handleGetCatalogRequest");
991
      pstmt = conn.prepareStatement("select entry_type, " +
992
                              "source_doctype, target_doctype, public_id, " +
993
                              "system_id from xml_catalog");
994
      pstmt.execute();
995
      ResultSet rs = pstmt.getResultSet();
996
      boolean tablehasrows = rs.next();
997
      StringBuffer sb = new StringBuffer();
998
      sb.append("<?xml version=\"1.0\"?><xml_catalog>");
999
      while(tablehasrows)
1000
      {
1001
        sb.append("<row><entry_type>").append(rs.getString(1));
1002
        sb.append("</entry_type><source_doctype>").append(rs.getString(2));
1003
        sb.append("</source_doctype><target_doctype>").append(rs.getString(3));
1004
        sb.append("</target_doctype><public_id>").append(rs.getString(4));
1005
        sb.append("</public_id><system_id>").append(rs.getString(5));
1006
        sb.append("</system_id></row>");
1007
      
1008
        tablehasrows = rs.next();
1009
      }
1010
      sb.append("</xml_catalog>");
1011
      conn.close();
1012
      if(printFlag)
1013
      {
1014
        response.setContentType("text/xml");
1015
        out.println(sb.toString());
1016
      }
1017
      pstmt.close();
1018
      return sb.toString();
1019
    }
1020
    catch(Exception e)
1021
    {
1022
      try
1023
      {
1024
        pstmt.close();
1025
        conn.close();
1026
      }
1027
      catch(Exception ee)
1028
      {}
1029
      System.out.println("error in MetacatReplication.handleGetCatalogRequest:"+ 
1030
                          e.getMessage());
1031
      e.printStackTrace(System.out);
1032
    }
1033
    return null;
1034
  }
1035
  
1036
  /**
1037
   * Sends the current system date to the remote server.  Using this action
1038
   * for replication gets rid of any problems with syncronizing clocks 
1039
   * because a time specific to a document is always kept on its home server.
1040
   */
1041
  private void handleGetTimeRequest(PrintWriter out, Hashtable params, 
1042
                                    HttpServletResponse response)
1043
  {
1044
    SimpleDateFormat formatter = new SimpleDateFormat ("yy-MM-dd HH:mm:ss");
1045
    java.util.Date localtime = new java.util.Date();
1046
    String dateString = formatter.format(localtime);
1047
    response.setContentType("text/xml");
1048
    
1049
    out.println("<timestamp>" + dateString + "</timestamp>");
1050
  }
1051
  
1052
  /**
1053
   * this method handles the timeout for a file lock.  when a lock is 
1054
   * granted it is granted for 30 seconds.  When this thread runs out
1055
   * it deletes the docid from the queue, thus eliminating the lock.
1056
   */
1057
  public void run()
1058
  {
1059
    try
1060
    {
1061
      MetaCatUtil.debugMessage("thread started for docid: " + 
1062
                               (String)fileLocks.elementAt(0));
1063
      System.out.println("thread started for docid: " + 
1064
                               (String)fileLocks.elementAt(0));
1065
      Thread.sleep(30000); //the lock will expire in 30 seconds
1066
      MetaCatUtil.debugMessage("thread for docid: " + 
1067
                             (String)fileLocks.elementAt(fileLocks.size() - 1) + 
1068
                              " exiting.");
1069
      System.out.println("thread for docid: " + 
1070
                         (String)fileLocks.elementAt(fileLocks.size() - 1) + 
1071
                         " exiting.");
1072
      fileLocks.remove(fileLocks.size() - 1);
1073
      //fileLocks is treated as a FIFO queue.  If there are more than one lock
1074
      //in the vector, the first one inserted will be removed.
1075
    }
1076
    catch(Exception e)
1077
    {
1078
      System.out.println("error in file lock thread from MetacatReplication." + 
1079
                         "run: " + e.getMessage());
1080
    }
1081
  }
1082
  
1083
  /**
1084
   * Returns the name of a server given a serverCode
1085
   * @param serverCode the serverid of the server
1086
   * @return the servername or null if the specified serverCode does not
1087
   *         exist.
1088
   */
1089
  public static String getServer(int serverCode)
1090
  {
1091
    //System.out.println("serverid: " + serverCode);
1092
    Connection conn = null;
1093
    try
1094
    {
1095
      conn = MetacatReplication.getDBConnection("MetacatReplication." +
1096
                                                "getServer");
1097
      String sql = new String("select server from " +
1098
                              "xml_replication where serverid = " + 
1099
                              serverCode);
1100
      PreparedStatement pstmt = conn.prepareStatement(sql);
1101
      //System.out.println("getserver sql: " + sql);
1102
      pstmt.execute();
1103
      ResultSet rs = pstmt.getResultSet();
1104
      boolean tablehasrows = rs.next();
1105
      if(tablehasrows)
1106
      {
1107
        //System.out.println("server: " + rs.getString(1));
1108
        return rs.getString(1);
1109
      }
1110
      pstmt.close();
1111
      conn.close();
1112
    }
1113
    catch(Exception e)
1114
    {
1115
      System.out.println("Error in MetacatReplication.getServer: " + 
1116
                          e.getMessage());
1117
    }
1118
    return null;
1119
      //return null if the server does not exist
1120
  }
1121
  
1122
  /**
1123
   * Returns a server code given a server name
1124
   * @param server the name of the server
1125
   * @return integer > 0 representing the code of the server, 0 if the server
1126
   *  does not exist.
1127
   */
1128
  public static int getServerCode(String server) throws Exception
1129
  {
1130
    Connection conn = null;
1131
    PreparedStatement pstmt = null;
1132
    int serverCode = 0;
1133

    
1134
    try {
1135

    
1136
      conn = util.openDBConnection();
1137
      pstmt = conn.prepareStatement("SELECT serverid FROM xml_replication " +
1138
                                    "WHERE server LIKE '" + server + "'");
1139
      pstmt.execute();
1140
      ResultSet rs = pstmt.getResultSet();
1141
      boolean tablehasrows = rs.next();
1142
      if ( tablehasrows ) {  
1143
        serverCode = rs.getInt(1);
1144
        pstmt.close();
1145
        conn.close();
1146
        return serverCode;
1147
      }
1148
      
1149
    } catch(Exception e) {
1150
      throw e;
1151

    
1152
    } finally {
1153
      try {
1154
        pstmt.close();
1155
        conn.close();
1156
      } catch(Exception ee) {}
1157
    }
1158
    
1159
    return serverCode;
1160
  }
1161
  
1162
  /**
1163
   * This method returns the content of a url
1164
   * @param u the url to return the content from
1165
   * @return a string representing the content of the url
1166
   * @throws java.io.IOException
1167
   */
1168
  public static String getURLContent(URL u) throws java.io.IOException
1169
  {
1170
    //System.out.println("url: " + u.toString());
1171
    char istreamChar;
1172
    int istreamInt;
1173
    InputStreamReader istream = new InputStreamReader(u.openStream());
1174
    StringBuffer serverResponse = new StringBuffer();
1175
    while((istreamInt = istream.read()) != -1)
1176
    {
1177
      istreamChar = (char)istreamInt;
1178
      serverResponse.append(istreamChar);
1179
    }
1180
    
1181
    return serverResponse.toString();
1182
  }
1183
  
1184
  /**
1185
   * Method for writing replication messages to a log file specified in 
1186
   * metacat.properties
1187
   */
1188
  public static void replLog(String message)
1189
  {
1190
    try
1191
    {
1192
      FileOutputStream fos = new FileOutputStream(
1193
                                 util.getOption("replicationlog"), true);
1194
      PrintWriter pw = new PrintWriter(fos);
1195
      SimpleDateFormat formatter = new SimpleDateFormat ("yy-MM-dd HH:mm:ss");
1196
      java.util.Date localtime = new java.util.Date();
1197
      String dateString = formatter.format(localtime);
1198
      dateString += " :: " + message;
1199
      //time stamp each entry
1200
      pw.println(dateString);
1201
      pw.flush();
1202
    }
1203
    catch(Exception e)
1204
    {
1205
      System.out.println("error writing to replication log from " +
1206
                         "MetacatReplication.replLog: " + e.getMessage());
1207
      //e.printStackTrace(System.out);
1208
    }
1209
  }
1210
  
1211
  /**
1212
   * Returns true if the replicate field for server in xml_replication is 1.
1213
   * Returns false otherwise
1214
   */
1215
  public static boolean replToServer(String server)
1216
  {
1217
    Connection conn = null;
1218
    PreparedStatement pstmt = null;
1219
    try
1220
    {
1221
      conn = MetacatReplication.getDBConnection("MetacatReplication.replToServer");
1222
      pstmt = conn.prepareStatement("select replicate from " + 
1223
                                    "xml_replication where server like '" +
1224
                                     server + "'");
1225
      pstmt.execute();
1226
      ResultSet rs = pstmt.getResultSet();
1227
      boolean tablehasrows = rs.next();
1228
      if(tablehasrows)
1229
      {
1230
        int i = rs.getInt(1);
1231
        if(i == 1)
1232
        {
1233
          pstmt.close();
1234
          conn.close();
1235
          return true;
1236
        }
1237
        else
1238
        {
1239
          pstmt.close();
1240
          conn.close();
1241
          return false;
1242
        }
1243
      }
1244
    }
1245
    catch(Exception e)
1246
    {
1247
      System.out.println("error in MetacatReplication.replToServer: " + 
1248
                         e.getMessage());
1249
    }
1250
    finally
1251
    {
1252
      try
1253
      {
1254
        pstmt.close();
1255
        conn.close();
1256
      }
1257
      catch(Exception ee)
1258
      {}
1259
    }
1260
    return false;
1261
    //the default if this server does not exist is to not replicate to it.
1262
  }
1263
  
1264
  /**
1265
   * A method for redundantly trying to connect.  this method will attempt to 
1266
   * connect to the DB 3 times before failing.
1267
   * @param methodname the methodname from which this method is called.
1268
   */
1269
  public static Connection getDBConnection(String methodname) throws Exception
1270
  {
1271
    Connection conn = null;
1272
    try
1273
    {
1274
      try
1275
      { //this connection is prone to error for some reason so we 
1276
        //try to connect it three times before quiting.
1277
        conn = util.openDBConnection();
1278
        return conn;
1279
      }
1280
      catch(SQLException sqle)
1281
      {
1282
        try
1283
        {
1284
          conn = util.openDBConnection();
1285
          return conn;
1286
        }
1287
        catch(SQLException sqlee)
1288
        {
1289
          try
1290
          {
1291
            conn = util.openDBConnection();
1292
            return conn;
1293
          }
1294
          catch(SQLException sqleee)
1295
          {
1296
            System.out.println("error getting db connection in " + 
1297
                               methodname +  ": " +
1298
                               sqleee.getMessage());
1299
            return conn;
1300
          }
1301
        }
1302
      }
1303
    }
1304
    catch(Exception e)
1305
    {
1306
      throw new java.lang.Exception("error in " + methodname + ": " + 
1307
                          e.getMessage());
1308
    }
1309
  }
1310
}
(32-32/41)