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-20 17:25:53 -0700 (Mon, 20 May 2002) $'
11
 * '$Revision: 1097 $'
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
    } else if ( action.equals("test") ) {
223
      response.setContentType("text/html");
224
      out.println("<html><body>Test successfully</body></html>");
225
    }
226
    
227
    out.close();
228
    }//else
229
  }
230
  
231
  /** 
232
   * This method can add, delete and list the servers currently included in
233
   * xml_replication.
234
   * action           subaction            other needed params
235
   * ---------------------------------------------------------
236
   * servercontrol    add                  server
237
   * servercontrol    delete               server
238
   * servercontrol    list                 
239
   */
240
  private void handleServerControlRequest(PrintWriter out, Hashtable params,
241
                                          HttpServletResponse response)
242
  {
243
    String subaction = ((String[])params.get("subaction"))[0];
244
    Connection conn = null;
245

    
246
    try {
247
      conn = util.openDBConnection();
248
      PreparedStatement pstmt = null;
249

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

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

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

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

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

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

    
1172
    try {
1173

    
1174
      conn = util.openDBConnection();
1175
      pstmt = conn.prepareStatement("SELECT serverid FROM xml_replication " +
1176
                                    "WHERE server LIKE '" + server + "'");
1177
      pstmt.execute();
1178
      ResultSet rs = pstmt.getResultSet();
1179
      boolean tablehasrows = rs.next();
1180
      if ( tablehasrows ) {  
1181
        serverCode = rs.getInt(1);
1182
        pstmt.close();
1183
        conn.close();
1184
        return serverCode;
1185
      }
1186
      
1187
    } catch(Exception e) {
1188
      throw e;
1189

    
1190
    } finally {
1191
      try {
1192
        pstmt.close();
1193
        conn.close();
1194
      } catch(Exception ee) {}
1195
    }
1196
    
1197
    return serverCode;
1198
  }
1199
  
1200
  /**
1201
   * This method returns the content of a url
1202
   * @param u the url to return the content from
1203
   * @return a string representing the content of the url
1204
   * @throws java.io.IOException
1205
   */
1206
  public static String getURLContent(URL u) throws java.io.IOException
1207
  {
1208
    //System.out.println("url: " + u.toString());
1209
    char istreamChar;
1210
    int istreamInt;
1211
    InputStreamReader istream = new InputStreamReader(u.openStream());
1212
    StringBuffer serverResponse = new StringBuffer();
1213
    while((istreamInt = istream.read()) != -1)
1214
    {
1215
      istreamChar = (char)istreamInt;
1216
      serverResponse.append(istreamChar);
1217
    }
1218
    
1219
    return serverResponse.toString();
1220
  }
1221
  
1222
  /**
1223
   * Method for writing replication messages to a log file specified in 
1224
   * metacat.properties
1225
   */
1226
  public static void replLog(String message)
1227
  {
1228
    try
1229
    {
1230
      FileOutputStream fos = new FileOutputStream(
1231
                                 util.getOption("replicationlog"), true);
1232
      PrintWriter pw = new PrintWriter(fos);
1233
      SimpleDateFormat formatter = new SimpleDateFormat ("yy-MM-dd HH:mm:ss");
1234
      java.util.Date localtime = new java.util.Date();
1235
      String dateString = formatter.format(localtime);
1236
      dateString += " :: " + message;
1237
      //time stamp each entry
1238
      pw.println(dateString);
1239
      pw.flush();
1240
    }
1241
    catch(Exception e)
1242
    {
1243
      System.out.println("error writing to replication log from " +
1244
                         "MetacatReplication.replLog: " + e.getMessage());
1245
      //e.printStackTrace(System.out);
1246
    }
1247
  }
1248
  
1249
  /**
1250
   * Returns true if the replicate field for server in xml_replication is 1.
1251
   * Returns false otherwise
1252
   */
1253
  public static boolean replToServer(String server)
1254
  {
1255
    Connection conn = null;
1256
    PreparedStatement pstmt = null;
1257
    try
1258
    {
1259
      conn = MetacatReplication.getDBConnection("MetacatReplication.replToServer");
1260
      pstmt = conn.prepareStatement("select replicate from " + 
1261
                                    "xml_replication where server like '" +
1262
                                     server + "'");
1263
      pstmt.execute();
1264
      ResultSet rs = pstmt.getResultSet();
1265
      boolean tablehasrows = rs.next();
1266
      if(tablehasrows)
1267
      {
1268
        int i = rs.getInt(1);
1269
        if(i == 1)
1270
        {
1271
          pstmt.close();
1272
          conn.close();
1273
          return true;
1274
        }
1275
        else
1276
        {
1277
          pstmt.close();
1278
          conn.close();
1279
          return false;
1280
        }
1281
      }
1282
    }
1283
    catch(Exception e)
1284
    {
1285
      System.out.println("error in MetacatReplication.replToServer: " + 
1286
                         e.getMessage());
1287
    }
1288
    finally
1289
    {
1290
      try
1291
      {
1292
        pstmt.close();
1293
        conn.close();
1294
      }
1295
      catch(Exception ee)
1296
      {}
1297
    }
1298
    return false;
1299
    //the default if this server does not exist is to not replicate to it.
1300
  }
1301
  
1302
  /**
1303
   * A method for redundantly trying to connect.  this method will attempt to 
1304
   * connect to the DB 3 times before failing.
1305
   * @param methodname the methodname from which this method is called.
1306
   */
1307
  public static Connection getDBConnection(String methodname) throws Exception
1308
  {
1309
    Connection conn = null;
1310
    try
1311
    {
1312
      try
1313
      { //this connection is prone to error for some reason so we 
1314
        //try to connect it three times before quiting.
1315
        conn = util.openDBConnection();
1316
        return conn;
1317
      }
1318
      catch(SQLException sqle)
1319
      {
1320
        try
1321
        {
1322
          conn = util.openDBConnection();
1323
          return conn;
1324
        }
1325
        catch(SQLException sqlee)
1326
        {
1327
          try
1328
          {
1329
            conn = util.openDBConnection();
1330
            return conn;
1331
          }
1332
          catch(SQLException sqleee)
1333
          {
1334
            System.out.println("error getting db connection in " + 
1335
                               methodname +  ": " +
1336
                               sqleee.getMessage());
1337
            return conn;
1338
          }
1339
        }
1340
      }
1341
    }
1342
    catch(Exception e)
1343
    {
1344
      throw new java.lang.Exception("error in " + methodname + ": " + 
1345
                          e.getMessage());
1346
    }
1347
  }
1348
}
(34-34/43)