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: 2005-11-08 14:14:16 -0800 (Tue, 08 Nov 2005) $'
11
 * '$Revision: 2724 $'
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 edu.ucsb.nceas.dbadapter.AbstractDatabase;
31
import java.util.*;
32
import java.util.Date;
33
import java.io.*;
34
import java.sql.*;
35
import java.net.*;
36
import java.lang.*;
37
import java.text.*;
38
import javax.servlet.*;
39
import javax.servlet.http.*;
40

    
41
import org.apache.log4j.Logger;
42
import org.xml.sax.*;
43

    
44
public class MetacatReplication extends HttpServlet implements Runnable
45
{
46
  private long timeInterval;
47
  private Date firstTime;
48
  private boolean timedReplicationIsOn = false;
49
  Timer replicationDaemon;
50
  private static MetaCatUtil util = new MetaCatUtil();
51
  private Vector fileLocks = new Vector();
52
  private Thread lockThread = null;
53
  private static final AbstractDatabase dbAdapter = MetaCatUtil.dbAdapter;
54
  public static final String FORCEREPLICATEDELETE = "forcereplicatedelete";
55
  private static final String TIMEREPLICATION = "timedreplication";
56
  private static final String TIMEREPLICATIONINTERVAl = "timedreplicationinterval";
57
  private static final String FIRSTTIME  = "firsttimedreplication";
58
  private static final int    TIMEINTERVALLIMIT = 7200000;
59
  private static Logger logMetacat = Logger.getLogger(MetacatReplication.class);
60

    
61
  /**
62
   * Initialize the servlet by creating appropriate database connections
63
   */
64
  public void init(ServletConfig config) throws ServletException
65
  {
66
     //initialize db connections to handle any update requests
67
    MetaCatUtil util = new MetaCatUtil();
68
    //deltaT = util.getOption("deltaT");
69
    //the default deltaT can be set from metacat.properties
70
    //create a thread to do the delta-T check but don't execute it yet
71
    replicationDaemon = new Timer(true);
72
    try
73
    {
74
       timedReplicationIsOn = (new Boolean(util.getOption(TIMEREPLICATION ).trim())).booleanValue();
75
       logMetacat.info("The timed replication on is"+timedReplicationIsOn);
76
       timeInterval = (new Long(util.getOption(TIMEREPLICATIONINTERVAl).trim())).longValue();
77
       logMetacat.warn("The timed replication time Inerval is "+ timeInterval);
78
       String firstTimeStr = util.getOption(FIRSTTIME);
79
       logMetacat.warn("first replication time form property is "+firstTimeStr);
80
       firstTime = ReplicationHandler.combinateCurrentDateAndGivenTime(firstTimeStr);
81
       logMetacat.warn("After combine current time, the real first time is "
82
                                +firstTime.toString()+" minisec");
83
       // set up time replication if it is on
84
       if (timedReplicationIsOn)
85
       {
86
           replicationDaemon.scheduleAtFixedRate(new ReplicationHandler(), firstTime, timeInterval);
87
           MetacatReplication.replLog("deltaT handler started with rate=" +
88
                   timeInterval + " mini seconds at " +firstTime.toString());
89
       }
90
    }
91
    catch (Exception e)
92
    {
93
        // the timed replication in Metacat.properties file has problem
94
        // so timed replication is setting to false;
95
        logMetacat.error("Couldn't set up timed replication "+
96
                     " in Metacat replication servlet init because " +
97
                 e.getMessage());
98
        MetacatReplication.replErrorLog("Couldn't set up timed replication "+
99
                " in Metacat replication servlet init because " +
100
                e.getMessage());
101
        timedReplicationIsOn = false;
102
    }
103
    
104
  }
105

    
106
  public void destroy()
107
  {
108
    replicationDaemon.cancel();
109
   
110
  }
111

    
112
  public void doGet (HttpServletRequest request, HttpServletResponse response)
113
                     throws ServletException, IOException
114
  {
115
    // Process the data and send back the response
116
    handleGetOrPost(request, response);
117
  }
118

    
119
  public void doPost(HttpServletRequest request, HttpServletResponse response)
120
                     throws ServletException, IOException
121
  {
122
    // Process the data and send back the response
123
    handleGetOrPost(request, response);
124
  }
125

    
126
  private void handleGetOrPost(HttpServletRequest request,
127
                               HttpServletResponse response)
128
                               throws ServletException, IOException
129
  {
130
    //PrintWriter out = response.getWriter();
131
    //ServletOutputStream outPut = response.getOutputStream();
132
    Hashtable params = new Hashtable();
133
    Enumeration paramlist = request.getParameterNames();
134

    
135

    
136

    
137
// NOT NEEDED - doesn't provide enough security because of possible IP spoofing
138
// REPLACED with running replication comminications over HTTPS
139
//    String requestingServerIP = request.getRemoteAddr();
140
//    InetAddress iaddr = InetAddress.getByName(requestingServerIP);
141
//    String requestingServer = iaddr.getHostName();
142

    
143
    while (paramlist.hasMoreElements()) {
144
      String name = (String)paramlist.nextElement();
145
      String[] value = request.getParameterValues(name);
146
      params.put(name, value);
147
    }
148

    
149
    String action = ((String[])params.get("action"))[0];
150
    String server = null;
151

    
152
    try {
153
      // check if the server is included in the list of replicated servers
154
      if ( !action.equals("servercontrol") &&
155
           !action.equals("stop") &&
156
           !action.equals("start") &&
157
           !action.equals("getall") ) {
158

    
159
        server = ((String[])params.get("server"))[0];
160
        if ( getServerCodeForServerName(server) == 0 ) {
161
          System.out.println("Action \"" + action +
162
                             "\" rejected for server: " + server);
163
          return;
164
        } else {
165
          System.out.println("Action \"" + action +
166
                             "\" accepted for server: " + server);
167
        }
168
      }
169
      else
170
      {
171
          // start, stop, getall and servercontrol need to check
172
          // if user is administor
173
          HttpSession sess = request.getSession(true);
174
          String sess_id = "";
175
          String username = "";
176
          String[] groupnames = {""};
177
          Hashtable sessionHash = MetaCatServlet.getSessionHash();
178
          if (params.containsKey("sessionid")) 
179
          {
180
             sess_id = ((String[]) params.get("sessionid"))[0];
181
             logMetacat.info("in has sessionid "+ sess_id);
182
             if (sessionHash.containsKey(sess_id)) 
183
             {
184
                  logMetacat.info("find the id " + sess_id + " in hash table");
185
                  sess = (HttpSession) sessionHash.get(sess_id);
186
             }
187
           } 
188
           username = (String) sess.getAttribute("username");
189
           logMetacat.warn("The user name from session is: "+ username);
190
           groupnames = (String[]) sess.getAttribute("groupnames");
191
           if (!MetaCatUtil.isAdministrator(username, groupnames)) 
192
           {
193
               PrintWriter out = response.getWriter();
194
               out.print("<error>");
195
               out.print("The user \"" + username +
196
                       "\" is not authorized for this action.");
197
               out.print("</error>");
198
               out.close();
199
               logMetacat.warn("The user \"" + username +
200
                       "\" is not authorized for this action: " +action);
201
               replErrorLog("The user \"" + username +
202
                       "\" is not authorized for this action: " +action);
203
               return;
204
           }
205
                        
206
      }// this is final else
207
    } catch (Exception e) {
208
      System.out.println("Error in MetacatReplication.handleGetOrPost: " +
209
                         e.getMessage() );
210
      return;
211
    }
212
    
213
    if ( action.equals("readdata") )
214
    {
215
      OutputStream outStream = response.getOutputStream();
216
      //to get the data file.
217
      handleGetDataFileRequest(outStream, params, response);
218
      outStream.close();
219
    }
220
    else if ( action.equals("forcereplicatedatafile") )
221
    {
222
      //read a specific docid from remote host, and store it into local host
223
      handleForceReplicateDataFileRequest(params);
224

    
225
    }
226
    else
227
    {
228
    PrintWriter out = response.getWriter();
229
    if ( action.equals("stop") ) {
230
      //stop the replication server
231
      replicationDaemon.cancel();
232
      replicationDaemon = new Timer(true);
233
      timedReplicationIsOn = false;
234
      MetaCatUtil.setOption(TIMEREPLICATION, (new Boolean(timedReplicationIsOn)).toString());
235
      out.println("Replication Handler Stopped");
236
      MetacatReplication.replLog("deltaT handler stopped");
237

    
238

    
239
    } else if ( action.equals("start") ) {
240
       String firstTimeStr = "";
241
      //start the replication server
242
       if ( params.containsKey("rate") ) {
243
        timeInterval = new Long(
244
               new String(((String[])params.get("rate"))[0])).longValue();
245
        if(timeInterval < TIMEINTERVALLIMIT) {
246
            out.println("Replication deltaT rate cannot be less than "+
247
                    TIMEINTERVALLIMIT + " millisecs and system automatically setup the rate to "+TIMEINTERVALLIMIT);
248
            //deltaT<30 is a timing mess!
249
            timeInterval = TIMEINTERVALLIMIT;
250
        }
251
      } else {
252
        timeInterval = TIMEINTERVALLIMIT ;
253
      }
254
      logMetacat.info("New rate is: " + timeInterval + " mini seconds.");
255
      if ( params.containsKey("firsttime"))
256
      {
257
         firstTimeStr = ((String[])params.get("firsttime"))[0];
258
         try
259
         {
260
           firstTime = ReplicationHandler.combinateCurrentDateAndGivenTime(firstTimeStr);
261
           logMetacat.info("The first time setting is "+firstTime.toString());
262
         }
263
         catch (Exception e)
264
         {
265
            throw new ServletException(e.getMessage());
266
         }
267
         logMetacat.warn("After combine current time, the real first time is "
268
                                  +firstTime.toString()+" minisec");
269
      }
270
      else
271
      {
272
          MetacatReplication.replErrorLog("You should specify the first time " +
273
                                          "to start a time replication");
274
          logMetacat.warn("You should specify the first time " +
275
                                  "to start a time replication");
276
          return;
277
      }
278
      
279
      timedReplicationIsOn = true;
280
      // save settings to property file
281
      MetaCatUtil.setOption(TIMEREPLICATION, (new Boolean(timedReplicationIsOn)).toString());
282
      // note we couldn't use firstTime object because it has date info
283
      // we only need time info such as 10:00 PM
284
      MetaCatUtil.setOption(FIRSTTIME, firstTimeStr);
285
      MetaCatUtil.setOption(TIMEREPLICATIONINTERVAl, (new Long(timeInterval)).toString());
286
      replicationDaemon.cancel();
287
      replicationDaemon = new Timer(true);
288
      replicationDaemon.scheduleAtFixedRate(new ReplicationHandler(), firstTime,
289
                                            timeInterval);
290
      out.println("Replication Handler Started");
291
      MetacatReplication.replLog("deltaT handler started with rate=" +
292
                                    timeInterval + " milliseconds at " +firstTime.toString());
293

    
294

    
295
    } else if ( action.equals("getall") ) {
296
      //updates this server exactly once
297
      replicationDaemon.schedule(new ReplicationHandler(), 0);
298
      response.setContentType("text/html");
299
      out.println("<html><body>\"Get All\" Done</body></html>");
300

    
301
    } else if ( action.equals("forcereplicate") ) {
302
      //read a specific docid from remote host, and store it into local host
303
      handleForceReplicateRequest(out, params, response);
304

    
305
    } else if ( action.equals(FORCEREPLICATEDELETE) ) {
306
      //read a specific docid from remote host, and store it into local host
307
      handleForceReplicateDeleteRequest(out, params, response);
308

    
309
    } else if ( action.equals("update") ) {
310
      //request an update list from the server
311
      handleUpdateRequest(out, params, response);
312

    
313
    } else if ( action.equals("read") ) {
314
      //request a specific document from the server
315
      //note that this could be replaced by a call to metacatServlet
316
      //handleGetDocumentAction().
317
      handleGetDocumentRequest(out, params, response);
318
    } else if ( action.equals("getlock") ) {
319
      handleGetLockRequest(out, params, response);
320

    
321
    } else if ( action.equals("getdocumentinfo") ) {
322
      handleGetDocumentInfoRequest(out, params, response);
323

    
324
    } else if ( action.equals("gettime") ) {
325
      handleGetTimeRequest(out, params, response);
326

    
327
    } else if ( action.equals("getcatalog") ) {
328
      handleGetCatalogRequest(out, params, response, true);
329

    
330
    } else if ( action.equals("servercontrol") ) {
331
      handleServerControlRequest(out, params, response);
332
    } else if ( action.equals("test") ) {
333
      response.setContentType("text/html");
334
      out.println("<html><body>Test successfully</body></html>");
335
    }
336

    
337
    out.close();
338
    }//else
339
  }
340

    
341
  /**
342
   * This method can add, delete and list the servers currently included in
343
   * xml_replication.
344
   * action           subaction            other needed params
345
   * ---------------------------------------------------------
346
   * servercontrol    add                  server
347
   * servercontrol    delete               server
348
   * servercontrol    list
349
   */
350
  private void handleServerControlRequest(PrintWriter out, Hashtable params,
351
                                          HttpServletResponse response)
352
  {
353
    String subaction = ((String[])params.get("subaction"))[0];
354
    DBConnection dbConn = null;
355
    int serialNumber = -1;
356
    PreparedStatement pstmt = null;
357
    String replicate =null;
358
    String server = null;
359
    String dataReplicate = null;
360
    String hub = null;
361
    try {
362
      //conn = util.openDBConnection();
363
      dbConn=DBConnectionPool.
364
               getDBConnection("MetacatReplication.handleServerControlRequest");
365
      serialNumber=dbConn.getCheckOutSerialNumber();
366

    
367
      // add server to server list
368
      if ( subaction.equals("add") ) {
369
        replicate = ((String[])params.get("replicate"))[0];
370
        server = ((String[])params.get("server"))[0];
371

    
372
        //Get data replication value
373
        dataReplicate = ((String[])params.get("datareplicate"))[0];
374
        //Get hub value
375
        hub = ((String[])params.get("hub"))[0];
376

    
377
        /*pstmt = dbConn.prepareStatement("INSERT INTO xml_replication " +
378
                  "(server, last_checked, replicate, datareplicate, hub) " +
379
                                      "VALUES ('" + server + "', to_date(" +
380
                                      "'01/01/00', 'MM/DD/YY'), '" +
381
                                      replicate +"', '" +dataReplicate+"', '"
382
                                      + hub +"')");*/
383
        pstmt = dbConn.prepareStatement("INSERT INTO xml_replication " +
384
                  "(server, last_checked, replicate, datareplicate, hub) " +
385
                                      "VALUES ('" + server + "', "+
386
                                      dbAdapter.toDate("01/01/1980", "MM/DD/YYYY")
387
                                      + ", '" +
388
                                      replicate +"', '" +dataReplicate+"', '"
389
                                      + hub +"')");
390

    
391
        pstmt.execute();
392
        pstmt.close();
393
        dbConn.commit();
394
        out.println("Server " + server + " added");
395
        response.setContentType("text/html");
396
        out.println("<html><body><table border=\"1\">");
397
        out.println("<tr><td><b>server</b></td><td><b>last_checked</b></td><td>");
398
        out.println("<b>replicate</b></td>");
399
        out.println("<td><b>datareplicate</b></td>");
400
        out.println("<td><b>hub</b></td></tr>");
401
        pstmt = dbConn.prepareStatement("SELECT * FROM xml_replication");
402
        //increase dbconnection usage
403
        dbConn.increaseUsageCount(1);
404

    
405
        pstmt.execute();
406
        ResultSet rs = pstmt.getResultSet();
407
        boolean tablehasrows = rs.next();
408
        while(tablehasrows) {
409
          out.println("<tr><td>" + rs.getString(2) + "</td><td>");
410
          out.println(rs.getString(3) + "</td><td>");
411
          out.println(rs.getString(4) + "</td><td>");
412
          out.println(rs.getString(5) + "</td><td>");
413
          out.println(rs.getString(6) + "</td></tr>");
414

    
415
          tablehasrows = rs.next();
416
        }
417
        out.println("</table></body></html>");
418

    
419
        // download certificate with the public key on this server
420
        // and import it as a trusted certificate
421
        String certURL = ((String[])params.get("certificate"))[0];
422
        downloadCertificate(certURL);
423

    
424
      // delete server from server list
425
      } else if ( subaction.equals("delete") ) {
426
        server = ((String[])params.get("server"))[0];
427
        pstmt = dbConn.prepareStatement("DELETE FROM xml_replication " +
428
                                      "WHERE server LIKE '" + server + "'");
429
        pstmt.execute();
430
        pstmt.close();
431
        dbConn.commit();
432
        out.println("Server " + server + " deleted");
433
        response.setContentType("text/html");
434
        out.println("<html><body><table border=\"1\">");
435
        out.println("<tr><td><b>server</b></td><td><b>last_checked</b></td><td>");
436
        out.println("<b>replicate</b></td>");
437
        out.println("<td><b>datareplicate</b></td>");
438
        out.println("<td><b>hub</b></td></tr>");
439

    
440
        pstmt = dbConn.prepareStatement("SELECT * FROM xml_replication");
441
        //increase dbconnection usage
442
        dbConn.increaseUsageCount(1);
443
        pstmt.execute();
444
        ResultSet rs = pstmt.getResultSet();
445
        boolean tablehasrows = rs.next();
446
        while(tablehasrows)
447
        {
448
          out.println("<tr><td>" + rs.getString(2) + "</td><td>");
449
          out.println(rs.getString(3) + "</td><td>");
450
          out.println(rs.getString(4) + "</td><td>");
451
          out.println(rs.getString(5) + "</td><td>");
452
          out.println(rs.getString(6) + "</td></tr>");
453
          tablehasrows = rs.next();
454
        }
455
        out.println("</table></body></html>");
456

    
457
      // list servers in server list
458
      } else if ( subaction.equals("list") ) {
459
        response.setContentType("text/html");
460
        out.println("<html><body><table border=\"1\">");
461
        out.println("<tr><td><b>server</b></td><td><b>last_checked</b></td><td>");
462
        out.println("<b>replicate</b></td>");
463
        out.println("<td><b>datareplicate</b></td>");
464
        out.println("<td><b>hub</b></td></tr>");
465
        pstmt = dbConn.prepareStatement("SELECT * FROM xml_replication");
466
        pstmt.execute();
467
        ResultSet rs = pstmt.getResultSet();
468
        boolean tablehasrows = rs.next();
469
        while(tablehasrows) {
470
          out.println("<tr><td>" + rs.getString(2) + "</td><td>");
471
          out.println(rs.getString(3) + "</td><td>");
472
          out.println(rs.getString(4) + "</td><td>");
473
          out.println(rs.getString(5) + "</td><td>");
474
          out.println(rs.getString(6) + "</td></tr>");
475
          tablehasrows = rs.next();
476
        }
477
        out.println("</table></body></html>");
478
      }
479
      else
480
      {
481

    
482
        out.println("<error>Unkonwn subaction</error>");
483

    
484
      }
485
      pstmt.close();
486
      //conn.close();
487

    
488
    } catch(Exception e) {
489
      System.out.println("Error in " +
490
                         "MetacatReplication.handleServerControlRequest " +
491
                         e.getMessage());
492
      e.printStackTrace(System.out);
493
    }
494
    finally
495
    {
496
      try
497
      {
498
        pstmt.close();
499
      }//try
500
      catch (SQLException ee)
501
      {
502
        logMetacat.error("Error in " +
503
                "MetacatReplication.handleServerControlRequest to close pstmt "
504
                 + ee.getMessage());
505
      }//catch
506
      finally
507
      {
508
        DBConnectionPool.returnDBConnection(dbConn, serialNumber);
509
      }//finally
510
    }//finally
511

    
512
  }
513

    
514
  // download certificate with the public key from certURL and
515
  // upload it onto this server; it then must be imported as a
516
  // trusted certificate
517
  private void downloadCertificate (String certURL)
518
                throws FileNotFoundException, IOException, MalformedURLException
519
  {
520
    MetaCatUtil util = new MetaCatUtil();
521
    String certPath = util.getOption("certPath"); //the path to be uploaded to
522

    
523
    // get filename from the URL of the certificate
524
    String filename = certURL;
525
    int slash = Math.max(filename.lastIndexOf('/'), filename.lastIndexOf('\\'));
526
    if ( slash > -1 ) {
527
      filename = filename.substring(slash + 1);
528
    }
529

    
530
    // open file output strem to write the input into it
531
    File f = new File(certPath, filename);
532
    synchronized (f) {
533
      try {
534
        if ( f.exists() ) {
535
          throw new IOException("File already exist: " + f.getCanonicalFile());
536
          //if ( f.exists() && !f.canWrite() ) {
537
          //  throw new IOException("Not writable: " + f.getCanonicalFile());
538
        }
539
      } catch (SecurityException se) {
540
        // if a security manager exists,
541
        // its checkRead method is called for f.exist()
542
        // or checkWrite method is called for f.canWrite()
543
        throw se;
544
      }
545

    
546
      // create a buffered byte output stream
547
      // that uses a default-sized output buffer
548
      FileOutputStream fos = new FileOutputStream(f);
549
      BufferedOutputStream out = new BufferedOutputStream(fos);
550

    
551
      // this should be http url
552
      URL url = new URL(certURL);
553
      BufferedInputStream bis = null;
554
      try {
555
        bis = new BufferedInputStream(url.openStream());
556
        byte[] buf = new byte[4 * 1024]; // 4K buffer
557
        int b = bis.read(buf);
558
        while (b != -1) {
559
          out.write(buf, 0, b);
560
          b = bis.read(buf);
561
        }
562
      } finally {
563
        if (bis != null) bis.close();
564
      }
565
      // the input and the output streams must be closed
566
      bis.close();
567
            out.flush();
568
            out.close();
569
            fos.close();
570
    } // end of synchronized(f)
571
  }
572

    
573
  /**
574
   * when a forcereplication request comes in, local host sends a read request
575
   * to the requesting server (remote server) for the specified docid.
576
   * Then store it in local database.
577
   */
578
  private void handleForceReplicateRequest(PrintWriter out, Hashtable params,
579
                                           HttpServletResponse response)
580
  {
581
    String server = ((String[])params.get("server"))[0]; // the server that
582
    String docid = ((String[])params.get("docid"))[0]; // sent the document
583
    String dbaction = "UPDATE"; // the default action is UPDATE
584
    boolean override = false;
585
    int serverCode = 1;
586
    DBConnection dbConn = null;
587
    int serialNumber = -1;
588

    
589
    try {
590
      //if the url contains a dbaction then the default action is overridden
591
      if(params.containsKey("dbaction")) {
592
        dbaction = ((String[])params.get("dbaction"))[0];
593
        //serverCode = MetacatReplication.getServerCode(server);
594
        //override = true; //we are now overriding the default action
595
      }
596
      MetacatReplication.replLog("force replication request from " + server);
597
      logMetacat.info("Force replication request from: "+ server);
598
      logMetacat.info("Force replication docid: "+docid);
599
      logMetacat.info("Force replication action: "+dbaction);
600
      // sending back read request to remote server
601
      URL u = new URL("https://" + server + "?server="
602
                +util.getLocalReplicationServerName()
603
                +"&action=read&docid=" + docid);
604
      String xmldoc = MetacatReplication.getURLContent(u);
605

    
606
      // get the document info from server
607
      URL docinfourl = new URL("https://" + server +
608
                               "?server="+util.getLocalReplicationServerName()
609
                               +"&action=getdocumentinfo&docid=" + docid);
610

    
611
      String docInfoStr = MetacatReplication.getURLContent(docinfourl);
612

    
613
      //dih is the parser for the docinfo xml format
614
      DocInfoHandler dih = new DocInfoHandler();
615
      XMLReader docinfoParser = ReplicationHandler.initParser(dih);
616
      docinfoParser.parse(new InputSource(new StringReader(docInfoStr)));
617
      Hashtable docinfoHash = dih.getDocInfo();
618

    
619
      // Get user owner of this docid
620
      String user = (String)docinfoHash.get("user_owner");
621
      // Get home server of this docid
622
      String homeServer=(String)docinfoHash.get("home_server");
623
      String createdDate = (String)docinfoHash.get("date_created");
624
      String updatedDate = (String)docinfoHash.get("date_updated");
625
      logMetacat.info("homeServer: "+homeServer);
626
      // Get Document type
627
      String docType = (String)docinfoHash.get("doctype");
628
      logMetacat.info("docType: "+docType);
629
      String parserBase = null;
630
      // this for eml2 and we need user eml2 parser
631
      if (docType != null &&
632
          (docType.trim()).equals(DocumentImpl.EML2_0_0NAMESPACE))
633
      {
634
         logMetacat.warn("This is an eml200 document!");
635
         parserBase = DocumentImpl.EML200;
636
      }
637
      else if (docType != null && (docType.trim()).equals(DocumentImpl.EML2_0_1NAMESPACE))
638
      {
639
         logMetacat.warn("This is an eml2.0.1 document!");
640
         parserBase = DocumentImpl.EML200;
641
      }
642
      else if (docType != null && (docType.trim()).equals(DocumentImpl.EML2_1_0NAMESPACE))
643
      {
644
         logMetacat.warn("This is an eml2.1.0 document!");
645
         parserBase = DocumentImpl.EML210;
646
      }
647
      logMetacat.warn("The parserBase is: "+parserBase);
648

    
649
      // Get DBConnection from pool
650
      dbConn=DBConnectionPool.
651
              getDBConnection("MetacatReplication.handleForceReplicateRequest");
652
      serialNumber=dbConn.getCheckOutSerialNumber();
653
      // write the document to local database
654
      DocumentImplWrapper wrapper = new DocumentImplWrapper(parserBase, false);
655
      wrapper.writeReplication(dbConn, new StringReader(xmldoc), null, null,
656
                               dbaction, docid, user, null, homeServer, 
657
                               server, createdDate, updatedDate);
658

    
659
      MetacatReplication.replLog("document " + docid + " added to DB with " +
660
                                 "action " + dbaction);
661
    }//try
662
    catch(Exception e)
663
    {
664
      MetacatReplication.replErrorLog("document " + docid +
665
                                      " failed to added to DB with " +
666
                                      "action " + dbaction + " because "+
667
                                       e.getMessage());
668
      logMetacat.error("ERROR in MetacatReplication.handleForceReplicate" +
669
                         "Request(): " + e.getMessage());
670

    
671
    }//catch
672
    finally
673
    {
674
      // Return the checked out DBConnection
675
      DBConnectionPool.returnDBConnection(dbConn, serialNumber);
676
    }//finally
677
  }
678

    
679
/*
680
 * when a forcereplication delete request comes in, local host will delete this
681
 * document
682
 */
683
private void handleForceReplicateDeleteRequest(PrintWriter out, Hashtable params,
684
                                         HttpServletResponse response)
685
{
686
  String server = ((String[])params.get("server"))[0]; // the server that
687
  String docid = ((String[])params.get("docid"))[0]; // sent the document
688
  try
689
  {
690
    MetacatReplication.replLog("force replication delete request from " + server);
691
    MetacatReplication.replLog("force replication delete docid " + docid);
692
    logMetacat.info("Force replication delete request from: "+ server);
693
    logMetacat.info("Force replication delete docid: "+docid);
694
    DocumentImpl.delete(docid, null, null, server);
695
    MetacatReplication.replLog("document " + docid + " was successfully deleted ");
696
    logMetacat.info("document " + docid + " was successfully deleted ");
697
  }
698
  catch(Exception e)
699
  {
700
    MetacatReplication.replErrorLog("document " + docid +
701
                                    " failed to delete because "+
702
                                     e.getMessage());
703
    logMetacat.error("ERROR in MetacatReplication.handleForceDeleteReplicate" +
704
                       "Request(): " + e.getMessage());
705

    
706
  }//catch
707

    
708
}
709

    
710

    
711
  /**
712
   * when a forcereplication data file request comes in, local host sends a
713
   * readdata request to the requesting server (remote server) for the specified
714
   * docid. Then store it in local database and file system
715
   */
716
  private void handleForceReplicateDataFileRequest(Hashtable params)
717
  {
718

    
719
    //make sure there is some parameters
720
    if(params.isEmpty())
721
    {
722
      return;
723
    }
724
    // Get remote server
725
    String server = ((String[])params.get("server"))[0];
726
    // the docid should include rev number
727
    String docid = ((String[])params.get("docid"))[0];
728
    // Make sure there is a docid and server
729
    if (docid==null || server==null || server.equals(""))
730
    {
731
      logMetacat.error("Didn't specify docid or server for replication");
732
      return;
733
    }
734

    
735
    // Overide or not
736
    boolean override = false;
737
    // dbaction - update or insert
738
    String dbaction=null;
739

    
740
    try
741
    {
742
      //docid was switch to two parts uinque code and rev
743
      //String uniqueCode=MetaCatUtil.getDocIdFromString(docid);
744
      //int rev=MetaCatUtil.getVersionFromString(docid);
745
      if(params.containsKey("dbaction"))
746
      {
747
        dbaction = ((String[])params.get("dbaction"))[0];
748
      }
749
      else//default value is update
750
      {
751
        dbaction = "update";
752
      }
753

    
754
      MetacatReplication.replLog("force replication request from " + server);
755
      logMetacat.info("Force replication request from: "+ server);
756
      logMetacat.info("Force replication docid: "+docid);
757
      logMetacat.info("Force replication action: "+dbaction);
758
      // get the document info from server
759
      URL docinfourl = new URL("https://" + server +
760
                               "?server="+util.getLocalReplicationServerName()
761
                               +"&action=getdocumentinfo&docid=" + docid);
762

    
763
      String docInfoStr = MetacatReplication.getURLContent(docinfourl);
764

    
765
      //dih is the parser for the docinfo xml format
766
      DocInfoHandler dih = new DocInfoHandler();
767
      XMLReader docinfoParser = ReplicationHandler.initParser(dih);
768
      docinfoParser.parse(new InputSource(new StringReader(docInfoStr)));
769
      Hashtable docinfoHash = dih.getDocInfo();
770
      String user = (String)docinfoHash.get("user_owner");
771

    
772
      String docName = (String)docinfoHash.get("docname");
773

    
774
      String docType = (String)docinfoHash.get("doctype");
775

    
776
      String docHomeServer= (String)docinfoHash.get("home_server");
777
      
778
      String createdDate = (String)docinfoHash.get("date_created");
779
      
780
      String updatedDate = (String)docinfoHash.get("date_updated");
781
      logMetacat.info("docHomeServer of datafile: "+docHomeServer);
782

    
783

    
784

    
785
      //if action is delete, we don't delete the data file. Just archieve
786
      //the xml_documents
787
      /*if (dbaction.equals("delete"))
788
      {
789
        //conn = util.getConnection();
790
        DocumentImpl.delete(docid,user,null);
791
        //util.returnConnection(conn);
792
      }*/
793
      //To data file insert or update is same
794
      if (dbaction.equals("insert")||dbaction.equals("update"))
795
      {
796
        //Get data file and store it into local file system.
797
        // sending back readdata request to server
798
        URL url = new URL("https://" + server + "?server="
799
                +util.getLocalReplicationServerName()
800
                +"&action=readdata&docid=" + docid);
801
        String datafilePath = util.getOption("datafilepath");
802
        //register data file into xml_documents table and wite data file
803
        //into file system
804
        DocumentImpl.writeDataFileInReplication(url.openStream(), datafilePath,
805
                            docName, docType, docid, user,docHomeServer,server, 
806
                            DocumentImpl.DOCUMENTTABLE, false, createdDate, updatedDate);
807
                            //false means non-timed replication
808
     }
809

    
810

    
811

    
812
    MetacatReplication.replLog("datafile " + docid + " added to DB with " +
813
                                 "action " + dbaction);
814
    }
815
    catch(Exception e)
816
    {
817

    
818
      MetacatReplication.replErrorLog("Datafile " + docid +
819
                                      " failed to added to DB with " +
820
                                      "action " + dbaction + " because "+
821
                                       e.getMessage());
822
      logMetacat.error
823
              ("ERROR in MetacatReplication.handleForceDataFileReplicate" +
824
                         "Request(): " + e.getMessage());
825
    }
826
  }
827
  /**
828
   * Grants or denies a lock to a requesting host.
829
   * The servlet parameters of interrest are:
830
   * docid: the docid of the file the lock is being requested for
831
   * currentdate: the timestamp of the document on the remote server
832
   *
833
   */
834
  private void handleGetLockRequest(PrintWriter out, Hashtable params,
835
                                    HttpServletResponse response)
836
  {
837

    
838
    try
839
    {
840

    
841
      String docid = ((String[])params.get("docid"))[0];
842
      String remoteRev = ((String[])params.get("updaterev"))[0];
843
      DocumentImpl requestDoc = new DocumentImpl(docid);
844
      MetacatReplication.replLog("lock request for " + docid);
845
      int localRevInt = requestDoc.getRev();
846
      int remoteRevInt = Integer.parseInt(remoteRev);
847

    
848
      if(remoteRevInt >= localRevInt)
849
      {
850
        if(!fileLocks.contains(docid))
851
        { //grant the lock if it is not already locked
852
          fileLocks.add(0, docid); //insert at the beginning of the queue Vector
853
          //send a message back to the the remote host authorizing the insert
854
          out.println("<lockgranted><docid>" +docid+ "</docid></lockgranted>");
855
          lockThread = new Thread(this);
856
          lockThread.setPriority(Thread.MIN_PRIORITY);
857
          lockThread.start();
858
          MetacatReplication.replLog("lock granted for " + docid);
859
        }
860
        else
861
        { //deny the lock
862
          out.println("<filelocked><docid>" + docid + "</docid></filelocked>");
863
          MetacatReplication.replLog("lock denied for " + docid +
864
                                     "reason: file already locked");
865
        }
866
      }
867
      else
868
      {//deny the lock.
869
        out.println("<outdatedfile><docid>" + docid + "</docid></filelocked>");
870
        MetacatReplication.replLog("lock denied for " + docid +
871
                                   "reason: client has outdated file");
872
      }
873
      //conn.close();
874
    }
875
    catch(Exception e)
876
    {
877
      System.out.println("error requesting file lock from MetacatReplication." +
878
                         "handleGetLockRequest: " + e.getMessage());
879
      e.printStackTrace(System.out);
880
    }
881
  }
882

    
883
  /**
884
   * Sends all of the xml_documents information encoded in xml to a requestor
885
   * the format is:
886
   * <!ELEMENT documentinfo (docid, docname, doctype, doctitle, user_owner,
887
   *                  user_updated, home_server, public_access, rev)/>
888
   * all of the subelements of document info are #PCDATA
889
   */
890
  private void handleGetDocumentInfoRequest(PrintWriter out, Hashtable params,
891
                                        HttpServletResponse response)
892
  {
893
    String docid = ((String[])(params.get("docid")))[0];
894
    StringBuffer sb = new StringBuffer();
895

    
896
    try
897
    {
898

    
899
      DocumentImpl doc = new DocumentImpl(docid);
900
      sb.append("<documentinfo><docid>").append(docid);
901
      sb.append("</docid><docname>").append(doc.getDocname());
902
      sb.append("</docname><doctype>").append(doc.getDoctype());
903
      sb.append("</doctype>");
904
      sb.append("<user_owner>").append(doc.getUserowner());
905
      sb.append("</user_owner><user_updated>").append(doc.getUserupdated());
906
      sb.append("</user_updated>");
907
      sb.append("<date_created>");
908
      sb.append(doc.getCreateDate());
909
      sb.append("</date_created>");
910
      sb.append("<date_updated>");
911
      sb.append(doc.getUpdateDate());
912
      sb.append("</date_updated>");
913
      sb.append("<home_server>");
914
      sb.append(doc.getDocHomeServer());
915
      sb.append("</home_server>");
916
      sb.append("<public_access>").append(doc.getPublicaccess());
917
      sb.append("</public_access><rev>").append(doc.getRev());
918
      sb.append("</rev></documentinfo>");
919
      response.setContentType("text/xml");
920
      out.println(sb.toString());
921

    
922
    }
923
    catch (Exception e)
924
    {
925
      System.out.println("error in " +
926
                         "metacatReplication.handlegetdocumentinforequest: " +
927
                          e.getMessage());
928
    }
929

    
930
  }
931

    
932
  /**
933
   * Sends a datafile to a remote host
934
   */
935
  private void handleGetDataFileRequest(OutputStream outPut,
936
                            Hashtable params, HttpServletResponse response)
937

    
938
  {
939
    // File path for data file
940
    String filepath = util.getOption("datafilepath");
941
    // Request docid
942
    String docId = ((String[])(params.get("docid")))[0];
943
    //check if the doicd is null
944
    if (docId==null)
945
    {
946
      logMetacat.error("Didn't specify docid for replication");
947
      return;
948
    }
949

    
950
    //try to open a https stream to test if the request server's public key
951
    //in the key store, this is security issue
952
    try
953
    {
954
      String server = ((String[])params.get("server"))[0];
955
      URL u = new URL("https://" + server + "?server="
956
                +util.getLocalReplicationServerName()
957
                +"&action=test");
958
      String test = MetacatReplication.getURLContent(u);
959
      //couldn't pass the test
960
      if (test.indexOf("successfully")==-1)
961
      {
962
        //response.setContentType("text/xml");
963
        //outPut.println("<error>Couldn't pass the trust test</error>");
964
        logMetacat.error("Couldn't pass the trust test");
965
        return;
966
      }
967
    }//try
968
    catch (Exception ee)
969
    {
970
      return;
971
    }//catch
972

    
973
    if(!filepath.endsWith("/"))
974
    {
975
          filepath += "/";
976
    }
977
    // Get file aboslute file name
978
    String filename = filepath + docId;
979

    
980
    //MIME type
981
    String contentType = null;
982
    if (filename.endsWith(".xml"))
983
    {
984
        contentType="text/xml";
985
    }
986
    else if (filename.endsWith(".css"))
987
    {
988
        contentType="text/css";
989
    }
990
    else if (filename.endsWith(".dtd"))
991
    {
992
        contentType="text/plain";
993
    }
994
    else if (filename.endsWith(".xsd"))
995
    {
996
        contentType="text/xml";
997
    }
998
    else if (filename.endsWith("/"))
999
    {
1000
        contentType="text/html";
1001
    }
1002
    else
1003
    {
1004
        File f = new File(filename);
1005
        if ( f.isDirectory() )
1006
        {
1007
           contentType="text/html";
1008
        }
1009
        else
1010
        {
1011
           contentType="application/octet-stream";
1012
        }
1013
     }
1014

    
1015
   // Set the mime type
1016
   response.setContentType(contentType);
1017

    
1018
   // Get the content of the file
1019
   FileInputStream fin = null;
1020
   try
1021
   {
1022
      // FileInputStream to metacat
1023
      fin = new FileInputStream(filename);
1024
      // 4K buffer
1025
      byte[] buf = new byte[4 * 1024];
1026
      // Read data from file input stream to byte array
1027
      int b = fin.read(buf);
1028
      // Write to outStream from byte array
1029
      while (b != -1)
1030
      {
1031
        outPut.write(buf, 0, b);
1032
        b = fin.read(buf);
1033
      }
1034
      // close file input stream
1035
      fin.close();
1036

    
1037
   }//try
1038
   catch(Exception e)
1039
   {
1040
      System.out.println("error getting data file from MetacatReplication." +
1041
                         "handlGetDataFileRequest " + e.getMessage());
1042
      e.printStackTrace(System.out);
1043
   }//catch
1044

    
1045
}
1046

    
1047

    
1048
  /**
1049
   * Sends a document to a remote host
1050
   */
1051
  private void handleGetDocumentRequest(PrintWriter out, Hashtable params,
1052
                                        HttpServletResponse response)
1053
  {
1054

    
1055
    try
1056
    {
1057
      //try to open a https stream to test if the request server's public key
1058
      //in the key store, this is security issue
1059
      String server = ((String[])params.get("server"))[0];
1060
      URL u = new URL("https://" + server + "?server="
1061
                +util.getLocalReplicationServerName()
1062
                +"&action=test");
1063
      String test = MetacatReplication.getURLContent(u);
1064
      //couldn't pass the test
1065
      if (test.indexOf("successfully")==-1)
1066
      {
1067
        response.setContentType("text/xml");
1068
        out.println("<error>Couldn't pass the trust test "+test+" </error>");
1069
        out.close();
1070
        return;
1071
      }
1072

    
1073
      String docid = ((String[])(params.get("docid")))[0];
1074

    
1075
      DocumentImpl di = new DocumentImpl(docid);
1076
      response.setContentType("text/xml");
1077
      out.print(di.toString(null, null, true));
1078

    
1079
      MetacatReplication.replLog("document " + docid + " sent");
1080

    
1081
    }
1082
    catch(Exception e)
1083
    {
1084
      logMetacat.error("error getting document from MetacatReplication."
1085
                          +"handlGetDocumentRequest " + e.getMessage());
1086
      //e.printStackTrace(System.out);
1087
      response.setContentType("text/xml");
1088
      out.println("<error>"+e.getMessage()+"</error>");
1089
    }
1090

    
1091
  }
1092

    
1093
  /**
1094
   * Sends a list of all of the documents on this sever along with their
1095
   * revision numbers.
1096
   * The format is:
1097
   * <!ELEMENT replication (server, updates)>
1098
   * <!ELEMENT server (#PCDATA)>
1099
   * <!ELEMENT updates ((updatedDocument | deleteDocument | revisionDocument)*)>
1100
   * <!ELEMENT updatedDocument (docid, rev, datafile*)>
1101
   * <!ELEMENT deletedDocument (docid, rev)>
1102
   * <!ELEMENT revisionDocument (docid, rev, datafile*)>
1103
   * <!ELEMENT docid (#PCDATA)>
1104
   * <!ELEMENT rev (#PCDATA)>
1105
   * <!ELEMENT datafile (#PCDATA)>
1106
   * note that the rev in deletedDocument is always empty.  I just left
1107
   * it in there to make the parser implementation easier.
1108
   */
1109
  private void handleUpdateRequest(PrintWriter out, Hashtable params,
1110
                                    HttpServletResponse response)
1111
  {
1112
    // Checked out DBConnection
1113
    DBConnection dbConn = null;
1114
    // DBConenction serial number when checked it out
1115
    int serialNumber = -1;
1116
    PreparedStatement pstmt = null;
1117
    // Server list to store server info of xml_replication table
1118
    ReplicationServerList serverList = null;
1119

    
1120
    try
1121
    {
1122
      // Check out a DBConnection from pool
1123
      dbConn=DBConnectionPool.
1124
                  getDBConnection("MetacatReplication.handleUpdateRequest");
1125
      serialNumber=dbConn.getCheckOutSerialNumber();
1126
      // Create a server list from xml_replication table
1127
      serverList = new ReplicationServerList();
1128

    
1129
      // Get remote server name from param
1130
      String server = ((String[])params.get("server"))[0];
1131
      // If no servr name in param, return a error
1132
      if ( server == null || server.equals(""))
1133
      {
1134
        response.setContentType("text/xml");
1135
        out.println("<error>Request didn't specify server name</error>");
1136
        out.close();
1137
        return;
1138
      }//if
1139

    
1140
      //try to open a https stream to test if the request server's public key
1141
      //in the key store, this is security issue
1142
      URL u = new URL("https://" + server + "?server="
1143
                +util.getLocalReplicationServerName()
1144
                +"&action=test");
1145
      String test = MetacatReplication.getURLContent(u);
1146
      //couldn't pass the test
1147
      if (test.indexOf("successfully")==-1)
1148
      {
1149
        response.setContentType("text/xml");
1150
        out.println("<error>Couldn't pass the trust test</error>");
1151
        out.close();
1152
        return;
1153
      }
1154

    
1155

    
1156
      // Check if local host configure to replicate xml documents to remote
1157
      // server. If not send back a error message
1158
      if (!serverList.getReplicationValue(server))
1159
      {
1160
        response.setContentType("text/xml");
1161
        out.println
1162
        ("<error>Configuration not allow to replicate document to you</error>");
1163
        out.close();
1164
        return;
1165
      }//if
1166

    
1167
      // Store the sql command
1168
      StringBuffer docsql = new StringBuffer();
1169
      StringBuffer revisionSql = new StringBuffer();
1170
      // Stroe the docid list
1171
      StringBuffer doclist = new StringBuffer();
1172
      // Store the deleted docid list
1173
      StringBuffer delsql = new StringBuffer();
1174
      // Store the data set file
1175
      Vector packageFiles = new Vector();
1176

    
1177
      // Append local server's name and replication servlet to doclist
1178
      doclist.append("<?xml version=\"1.0\"?><replication>");
1179
      doclist.append("<server>").append(util.getLocalReplicationServerName());
1180
      //doclist.append(util.getOption("replicationpath"));
1181
      doclist.append("</server><updates>");
1182

    
1183
      // Get correct docid that reside on this server according the requesting
1184
      // server's replicate and data replicate value in xml_replication table
1185
      docsql.append(dbAdapter.getReplicationDocumentListSQL());
1186
      //docsql.append("select docid, rev, doctype from xml_documents where (docid not in (select a.docid from xml_documents a, xml_revisions b where a.docid=b.docid and a.rev<=b.rev)) ");
1187
      revisionSql.append("select docid, rev, doctype from xml_revisions ");
1188
      // If the localhost is not a hub to the remote server, only replicate
1189
      // the docid' which home server is local host (server_location =1)
1190
      if (!serverList.getHubValue(server))
1191
      {
1192
    	String serverLocationDoc = " and a.server_location = 1";
1193
        String serverLocationRev = "where server_location = 1";
1194
        docsql.append(serverLocationDoc);
1195
        revisionSql.append(serverLocationRev);
1196
      }
1197
      logMetacat.info("Doc sql: "+docsql.toString());
1198

    
1199
      // Get any deleted documents
1200
      delsql.append("select distinct docid from ");
1201
      delsql.append("xml_revisions where docid not in (select docid from ");
1202
      delsql.append("xml_documents) ");
1203
      // If the localhost is not a hub to the remote server, only replicate
1204
      // the docid' which home server is local host (server_location =1)
1205
      if (!serverList.getHubValue(server))
1206
      {
1207
        delsql.append("and server_location = 1");
1208
      }
1209
      logMetacat.info("Deleted sql: "+delsql.toString());
1210

    
1211

    
1212

    
1213
      // Get docid list of local host
1214
      pstmt = dbConn.prepareStatement(docsql.toString());
1215
      pstmt.execute();
1216
      ResultSet rs = pstmt.getResultSet();
1217
      boolean tablehasrows = rs.next();
1218
      //If metacat configed to replicate data file
1219
      //if ((util.getOption("replicationsenddata")).equals("on"))
1220
      boolean replicateData = serverList.getDataReplicationValue(server);
1221
      if (replicateData)
1222
      {
1223
        while(tablehasrows)
1224
        {
1225
          String recordDoctype = rs.getString(3);
1226
          Vector packagedoctypes = MetaCatUtil.getOptionList(
1227
                                     MetaCatUtil.getOption("packagedoctype"));
1228
          //if this is a package file, put it at the end
1229
          //because if a package file is read before all of the files it
1230
          //refers to are loaded then there is an error
1231
          if(recordDoctype != null && !packagedoctypes.contains(recordDoctype))
1232
          {
1233
              //If this is not data file
1234
              if (!recordDoctype.equals("BIN"))
1235
              {
1236
                //for non-data file document
1237
                doclist.append("<updatedDocument>");
1238
                doclist.append("<docid>").append(rs.getString(1));
1239
                doclist.append("</docid><rev>").append(rs.getInt(2));
1240
                doclist.append("</rev>");
1241
                doclist.append("</updatedDocument>");
1242
              }//if
1243
              else
1244
              {
1245
                //for data file document, in datafile attributes
1246
                //we put "datafile" value there
1247
                doclist.append("<updatedDocument>");
1248
                doclist.append("<docid>").append(rs.getString(1));
1249
                doclist.append("</docid><rev>").append(rs.getInt(2));
1250
                doclist.append("</rev>");
1251
                doclist.append("<datafile>");
1252
                doclist.append(MetaCatUtil.getOption("datafileflag"));
1253
                doclist.append("</datafile>");
1254
                doclist.append("</updatedDocument>");
1255
              }//else
1256
          }//if packagedoctpes
1257
          else
1258
          { //the package files are saved to be put into the xml later.
1259
              Vector v = new Vector();
1260
              v.add(new String(rs.getString(1)));
1261
              v.add(new Integer(rs.getInt(2)));
1262
              packageFiles.add(new Vector(v));
1263
          }//esle
1264
          tablehasrows = rs.next();
1265
        }//while
1266
      }//if
1267
      else //metacat was configured not to send data file
1268
      {
1269
        while(tablehasrows)
1270
        {
1271
          String recordDoctype = rs.getString(3);
1272
          if(!recordDoctype.equals("BIN"))
1273
          { //don't replicate data files
1274
            Vector packagedoctypes = MetaCatUtil.getOptionList(
1275
                                     MetaCatUtil.getOption("packagedoctype"));
1276
            if(recordDoctype != null && !packagedoctypes.contains(recordDoctype))
1277
            {   //if this is a package file, put it at the end
1278
              //because if a package file is read before all of the files it
1279
              //refers to are loaded then there is an error
1280
              doclist.append("<updatedDocument>");
1281
              doclist.append("<docid>").append(rs.getString(1));
1282
              doclist.append("</docid><rev>").append(rs.getInt(2));
1283
              doclist.append("</rev>");
1284
              doclist.append("</updatedDocument>");
1285
            }
1286
            else
1287
            { //the package files are saved to be put into the xml later.
1288
              Vector v = new Vector();
1289
              v.add(new String(rs.getString(1)));
1290
              v.add(new Integer(rs.getInt(2)));
1291
              packageFiles.add(new Vector(v));
1292
            }
1293
         }//if
1294
         tablehasrows = rs.next();
1295
        }//while
1296
      }//else
1297

    
1298
      pstmt = dbConn.prepareStatement(delsql.toString());
1299
      //usage count should increas 1
1300
      dbConn.increaseUsageCount(1);
1301

    
1302
      pstmt.execute();
1303
      rs = pstmt.getResultSet();
1304
      tablehasrows = rs.next();
1305
      while(tablehasrows)
1306
      { //handle the deleted documents
1307
        doclist.append("<deletedDocument><docid>").append(rs.getString(1));
1308
        doclist.append("</docid><rev></rev></deletedDocument>");
1309
        //note that rev is always empty for deleted docs
1310
        tablehasrows = rs.next();
1311
      }
1312

    
1313
      //now we can put the package files into the xml results
1314
      for(int i=0; i<packageFiles.size(); i++)
1315
      {
1316
        Vector v = (Vector)packageFiles.elementAt(i);
1317
        doclist.append("<updatedDocument>");
1318
        doclist.append("<docid>").append((String)v.elementAt(0));
1319
        doclist.append("</docid><rev>");
1320
        doclist.append(((Integer)v.elementAt(1)).intValue());
1321
        doclist.append("</rev>");
1322
        doclist.append("</updatedDocument>");
1323
      }
1324
      // add revision doc list  
1325
      doclist.append(prepareRevisionDoc(dbConn,revisionSql.toString(),replicateData));
1326
        
1327
      doclist.append("</updates></replication>");
1328
      logMetacat.info("doclist: " + doclist.toString());
1329
      pstmt.close();
1330
      //conn.close();
1331
      response.setContentType("text/xml");
1332
      out.println(doclist.toString());
1333

    
1334
    }
1335
    catch(Exception e)
1336
    {
1337
      logMetacat.error("error in MetacatReplication." +
1338
                         "handleupdaterequest: " + e.getMessage());
1339
      //e.printStackTrace(System.out);
1340
      response.setContentType("text/xml");
1341
      out.println("<error>"+e.getMessage()+"</error>");
1342
    }
1343
    finally
1344
    {
1345
      try
1346
      {
1347
        pstmt.close();
1348
      }//try
1349
      catch (SQLException ee)
1350
      {
1351
        logMetacat.error("Error in MetacatReplication." +
1352
                "handleUpdaterequest to close pstmt: "+ee.getMessage());
1353
      }//catch
1354
      finally
1355
      {
1356
        DBConnectionPool.returnDBConnection(dbConn, serialNumber);
1357
      }//finally
1358
    }//finally
1359

    
1360
  }//handlUpdateRequest
1361
  
1362
  /*
1363
   * This method will get the xml string for document in xml_revision
1364
   * The schema look like <!ELEMENT revisionDocument (docid, rev, datafile*)>
1365
   */
1366
  private String prepareRevisionDoc(DBConnection dbConn, String revSql, 
1367
                            boolean replicateData) throws Exception
1368
  {
1369
      logMetacat.warn("The revision document sql is "+ revSql);
1370
      StringBuffer revDocList = new StringBuffer();
1371
      PreparedStatement pstmt = dbConn.prepareStatement(revSql);
1372
      //usage count should increas 1
1373
      dbConn.increaseUsageCount(1);
1374

    
1375
      pstmt.execute();
1376
      ResultSet rs = pstmt.getResultSet();
1377
      boolean tablehasrows = rs.next();
1378
      while(tablehasrows)
1379
      {
1380
        String recordDoctype = rs.getString(3);
1381
        
1382
        //If this is data file and it isn't configured to replicate data
1383
        if (recordDoctype.equals("BIN") && !replicateData)
1384
        {  
1385
            // do nothing
1386
            continue;
1387
        }
1388
        else
1389
        {  
1390
            
1391
            revDocList.append("<revisionDocument>");
1392
            revDocList.append("<docid>").append(rs.getString(1));
1393
            revDocList.append("</docid><rev>").append(rs.getInt(2));
1394
            revDocList.append("</rev>");
1395
            // data file
1396
            if (recordDoctype.equals("BIN"))
1397
            {
1398
                revDocList.append("<datafile>");
1399
                revDocList.append(MetaCatUtil.getOption("datafileflag"));
1400
                revDocList.append("</datafile>");
1401
            }
1402
            revDocList.append("</revisionDocument>");
1403
        
1404
         }//else
1405
         tablehasrows = rs.next();
1406
      }
1407
      //System.out.println("The revision list is"+ revDocList.toString());
1408
      return revDocList.toString();
1409
  }
1410

    
1411
  /**
1412
   * Returns the xml_catalog table encoded in xml
1413
   */
1414
  public static String getCatalogXML()
1415
  {
1416
    return handleGetCatalogRequest(null, null, null, false);
1417
  }
1418

    
1419
  /**
1420
   * Sends the contents of the xml_catalog table encoded in xml
1421
   * The xml format is:
1422
   * <!ELEMENT xml_catalog (row*)>
1423
   * <!ELEMENT row (entry_type, source_doctype, target_doctype, public_id,
1424
   *                system_id)>
1425
   * All of the sub elements of row are #PCDATA
1426

    
1427
   * If printFlag == false then do not print to out.
1428
   */
1429
  private static String handleGetCatalogRequest(PrintWriter out,
1430
                                                Hashtable params,
1431
                                                HttpServletResponse response,
1432
                                                boolean printFlag)
1433
  {
1434
    DBConnection dbConn = null;
1435
    int serialNumber = -1;
1436
    PreparedStatement pstmt = null;
1437
    try
1438
    {
1439
      /*conn = MetacatReplication.getDBConnection("MetacatReplication." +
1440
                                                "handleGetCatalogRequest");*/
1441
      dbConn=DBConnectionPool.
1442
                 getDBConnection("MetacatReplication.handleGetCatalogRequest");
1443
      serialNumber=dbConn.getCheckOutSerialNumber();
1444
      pstmt = dbConn.prepareStatement("select entry_type, " +
1445
                              "source_doctype, target_doctype, public_id, " +
1446
                              "system_id from xml_catalog");
1447
      pstmt.execute();
1448
      ResultSet rs = pstmt.getResultSet();
1449
      boolean tablehasrows = rs.next();
1450
      StringBuffer sb = new StringBuffer();
1451
      sb.append("<?xml version=\"1.0\"?><xml_catalog>");
1452
      while(tablehasrows)
1453
      {
1454
        sb.append("<row><entry_type>").append(rs.getString(1));
1455
        sb.append("</entry_type><source_doctype>").append(rs.getString(2));
1456
        sb.append("</source_doctype><target_doctype>").append(rs.getString(3));
1457
        sb.append("</target_doctype><public_id>").append(rs.getString(4));
1458
        sb.append("</public_id><system_id>").append(rs.getString(5));
1459
        sb.append("</system_id></row>");
1460

    
1461
        tablehasrows = rs.next();
1462
      }
1463
      sb.append("</xml_catalog>");
1464
      //conn.close();
1465
      if(printFlag)
1466
      {
1467
        response.setContentType("text/xml");
1468
        out.println(sb.toString());
1469
      }
1470
      pstmt.close();
1471
      return sb.toString();
1472
    }
1473
    catch(Exception e)
1474
    {
1475

    
1476
      logMetacat.error("error in MetacatReplication.handleGetCatalogRequest:"+
1477
                          e.getMessage());
1478
      e.printStackTrace(System.out);
1479
      if(printFlag)
1480
      {
1481
        out.println("<error>"+e.getMessage()+"</error>");
1482
      }
1483
    }
1484
    finally
1485
    {
1486
      try
1487
      {
1488
        pstmt.close();
1489
      }//try
1490
      catch (SQLException ee)
1491
      {
1492
        logMetacat.error("Error in MetacatReplication.handleGetCatalogRequest: "
1493
           +ee.getMessage());
1494
      }//catch
1495
      finally
1496
      {
1497
        DBConnectionPool.returnDBConnection(dbConn, serialNumber);
1498
      }//finally
1499
    }//finally
1500

    
1501
    return null;
1502
  }
1503

    
1504
  /**
1505
   * Sends the current system date to the remote server.  Using this action
1506
   * for replication gets rid of any problems with syncronizing clocks
1507
   * because a time specific to a document is always kept on its home server.
1508
   */
1509
  private void handleGetTimeRequest(PrintWriter out, Hashtable params,
1510
                                    HttpServletResponse response)
1511
  {
1512
    SimpleDateFormat formatter = new SimpleDateFormat ("MM/dd/yy HH:mm:ss");
1513
    java.util.Date localtime = new java.util.Date();
1514
    String dateString = formatter.format(localtime);
1515
    response.setContentType("text/xml");
1516

    
1517
    out.println("<timestamp>" + dateString + "</timestamp>");
1518
  }
1519

    
1520
  /**
1521
   * this method handles the timeout for a file lock.  when a lock is
1522
   * granted it is granted for 30 seconds.  When this thread runs out
1523
   * it deletes the docid from the queue, thus eliminating the lock.
1524
   */
1525
  public void run()
1526
  {
1527
    try
1528
    {
1529
      logMetacat.info("thread started for docid: " +
1530
                               (String)fileLocks.elementAt(0));
1531

    
1532
      Thread.sleep(30000); //the lock will expire in 30 seconds
1533
      logMetacat.info("thread for docid: " +
1534
                             (String)fileLocks.elementAt(fileLocks.size() - 1) +
1535
                              " exiting.");
1536

    
1537
      fileLocks.remove(fileLocks.size() - 1);
1538
      //fileLocks is treated as a FIFO queue.  If there are more than one lock
1539
      //in the vector, the first one inserted will be removed.
1540
    }
1541
    catch(Exception e)
1542
    {
1543
      logMetacat.error("error in file lock thread from " +
1544
                                "MetacatReplication.run: " + e.getMessage());
1545
    }
1546
  }
1547

    
1548
  /**
1549
   * Returns the name of a server given a serverCode
1550
   * @param serverCode the serverid of the server
1551
   * @return the servername or null if the specified serverCode does not
1552
   *         exist.
1553
   */
1554
  public static String getServerNameForServerCode(int serverCode)
1555
  {
1556
    //System.out.println("serverid: " + serverCode);
1557
    DBConnection dbConn = null;
1558
    int serialNumber = -1;
1559
    PreparedStatement pstmt = null;
1560
    try
1561
    {
1562
      dbConn=DBConnectionPool.
1563
                  getDBConnection("MetacatReplication.getServer");
1564
      serialNumber=dbConn.getCheckOutSerialNumber();
1565
      String sql = new String("select server from " +
1566
                              "xml_replication where serverid = " +
1567
                              serverCode);
1568
      pstmt = dbConn.prepareStatement(sql);
1569
      //System.out.println("getserver sql: " + sql);
1570
      pstmt.execute();
1571
      ResultSet rs = pstmt.getResultSet();
1572
      boolean tablehasrows = rs.next();
1573
      if(tablehasrows)
1574
      {
1575
        //System.out.println("server: " + rs.getString(1));
1576
        return rs.getString(1);
1577
      }
1578

    
1579
      //conn.close();
1580
    }
1581
    catch(Exception e)
1582
    {
1583
      System.out.println("Error in MetacatReplication.getServer: " +
1584
                          e.getMessage());
1585
    }
1586
    finally
1587
    {
1588
      try
1589
      {
1590
        pstmt.close();
1591
      }//try
1592
      catch (SQLException ee)
1593
      {
1594
        logMetacat.error("Error in MetacactReplication.getserver: "+
1595
                                    ee.getMessage());
1596
      }//catch
1597
      finally
1598
      {
1599
        DBConnectionPool.returnDBConnection(dbConn, serialNumber);
1600
      }//fianlly
1601
    }//finally
1602

    
1603

    
1604

    
1605
    return null;
1606
      //return null if the server does not exist
1607
  }
1608

    
1609
  /**
1610
   * Returns a server code given a server name
1611
   * @param server the name of the server
1612
   * @return integer > 0 representing the code of the server, 0 if the server
1613
   *  does not exist.
1614
   */
1615
  public static int getServerCodeForServerName(String server) throws Exception
1616
  {
1617
    DBConnection dbConn = null;
1618
    int serialNumber = -1;
1619
    PreparedStatement pstmt = null;
1620
    int serverCode = 0;
1621

    
1622
    try {
1623

    
1624
      //conn = util.openDBConnection();
1625
      dbConn=DBConnectionPool.
1626
                  getDBConnection("MetacatReplication.getServerCode");
1627
      serialNumber=dbConn.getCheckOutSerialNumber();
1628
      pstmt = dbConn.prepareStatement("SELECT serverid FROM xml_replication " +
1629
                                    "WHERE server LIKE '" + server + "'");
1630
      pstmt.execute();
1631
      ResultSet rs = pstmt.getResultSet();
1632
      boolean tablehasrows = rs.next();
1633
      if ( tablehasrows ) {
1634
        serverCode = rs.getInt(1);
1635
        pstmt.close();
1636
        //conn.close();
1637
        return serverCode;
1638
      }
1639

    
1640
    } catch(Exception e) {
1641
      throw e;
1642

    
1643
    } finally {
1644
      try
1645
      {
1646
        pstmt.close();
1647
        //conn.close();
1648
       }//try
1649
       catch(Exception ee)
1650
       {
1651
         logMetacat.error("Error in MetacatReplicatio.getServerCode: "
1652
                                  +ee.getMessage());
1653

    
1654
       }//catch
1655
       finally
1656
       {
1657
         DBConnectionPool.returnDBConnection(dbConn, serialNumber);
1658
       }//finally
1659
    }//finally
1660

    
1661
    return serverCode;
1662
  }
1663

    
1664
  /**
1665
   * Method to get a host server information for given docid
1666
   * @param conn a connection to the database
1667
   */
1668
  public static Hashtable getHomeServerInfoForDocId(String docId)
1669
  {
1670
    Hashtable sl = new Hashtable();
1671
    DBConnection dbConn = null;
1672
    int serialNumber = -1;
1673
    //MetaCatUtil ut=new MetaCatUtil();
1674
    docId=MetaCatUtil.getDocIdFromString(docId);
1675
    PreparedStatement pstmt=null;
1676
    int serverLocation;
1677
    try
1678
    {
1679
      //get conection
1680
      dbConn=DBConnectionPool.
1681
                  getDBConnection("ReplicationHandler.getHomeServer");
1682
      serialNumber=dbConn.getCheckOutSerialNumber();
1683
      //get a server location from xml_document table
1684
      pstmt=dbConn.prepareStatement("select server_location from xml_documents "
1685
                                            +"where docid = ?");
1686
      pstmt.setString(1, docId);
1687
      pstmt.execute();
1688
      ResultSet serverName = pstmt.getResultSet();
1689
      //get a server location
1690
      if(serverName.next())
1691
      {
1692
        serverLocation=serverName.getInt(1);
1693
        pstmt.close();
1694
      }
1695
      else
1696
      {
1697
        pstmt.close();
1698
        //ut.returnConnection(conn);
1699
        return null;
1700
      }
1701
      pstmt=dbConn.prepareStatement("select server, last_checked, replicate " +
1702
                        "from xml_replication where serverid = ?");
1703
      //increase usage count
1704
      dbConn.increaseUsageCount(1);
1705
      pstmt.setInt(1, serverLocation);
1706
      pstmt.execute();
1707
      ResultSet rs = pstmt.getResultSet();
1708
      boolean tableHasRows = rs.next();
1709
      if (tableHasRows)
1710
      {
1711

    
1712
          String server = rs.getString(1);
1713
          String last_checked = rs.getString(2);
1714
          if(!server.equals("localhost"))
1715
          {
1716
            sl.put(server, last_checked);
1717
          }
1718

    
1719
      }
1720
      else
1721
      {
1722
        pstmt.close();
1723
        //ut.returnConnection(conn);
1724
        return null;
1725
      }
1726
      pstmt.close();
1727
    }
1728
    catch(Exception e)
1729
    {
1730
      System.out.println("error in replicationHandler.getHomeServer(): " +
1731
                         e.getMessage());
1732
    }
1733
    finally
1734
    {
1735
      try
1736
      {
1737
        pstmt.close();
1738
        //ut.returnConnection(conn);
1739
      }
1740
      catch (Exception ee)
1741
      {
1742
        logMetacat.error("Eror irn rplicationHandler.getHomeServer() "+
1743
                          "to close pstmt: "+ee.getMessage());
1744
      }
1745
      finally
1746
      {
1747
        DBConnectionPool.returnDBConnection(dbConn, serialNumber);
1748
      }
1749

    
1750
    }//finally
1751
    return sl;
1752
  }
1753

    
1754
  /**
1755
   * Returns a home server location  given a accnum
1756
   * @param accNum , given accNum for a document
1757
   *
1758
   */
1759
  public static int getHomeServerCodeForDocId(String accNum) throws Exception
1760
  {
1761
    DBConnection dbConn = null;
1762
    int serialNumber = -1;
1763
    PreparedStatement pstmt = null;
1764
    int serverCode = 1;
1765
    //MetaCatUtil ut = new MetaCatUtil();
1766
    String docId=MetaCatUtil.getDocIdFromString(accNum);
1767

    
1768
    try
1769
    {
1770

    
1771
      // Get DBConnection
1772
      dbConn=DBConnectionPool.
1773
                  getDBConnection("ReplicationHandler.getServerLocation");
1774
      serialNumber=dbConn.getCheckOutSerialNumber();
1775
      pstmt=dbConn.prepareStatement("SELECT server_location FROM xml_documents "
1776
                              + "WHERE docid LIKE '" + docId + "'");
1777
      pstmt.execute();
1778
      ResultSet rs = pstmt.getResultSet();
1779
      boolean tablehasrows = rs.next();
1780
      //If a document is find, return the server location for it
1781
      if ( tablehasrows )
1782
      {
1783
        serverCode = rs.getInt(1);
1784
        pstmt.close();
1785
        //conn.close();
1786
        return serverCode;
1787
      }
1788
      //if couldn't find in xml_documents table, we think server code is 1
1789
      //(this is new document)
1790
      else
1791
      {
1792
        pstmt.close();
1793
        //conn.close();
1794
        return serverCode;
1795
      }
1796

    
1797
    }
1798
    catch(Exception e)
1799
    {
1800

    
1801
      throw e;
1802

    
1803
    }
1804
    finally
1805
    {
1806
      try
1807
      {
1808
        pstmt.close();
1809
        //conn.close();
1810

    
1811
      }
1812
      catch(Exception ee)
1813
      {
1814
        logMetacat.error("Erorr in Replication.getServerLocation "+
1815
                     "to close pstmt"+ee.getMessage());
1816
      }
1817
      finally
1818
      {
1819
        DBConnectionPool.returnDBConnection(dbConn, serialNumber);
1820
      }//finally
1821
    }//finally
1822
   //return serverCode;
1823
  }
1824

    
1825

    
1826

    
1827
  /**
1828
   * This method returns the content of a url
1829
   * @param u the url to return the content from
1830
   * @return a string representing the content of the url
1831
   * @throws java.io.IOException
1832
   */
1833
  public static String getURLContent(URL u) throws java.io.IOException
1834
  {
1835
    char istreamChar;
1836
    int istreamInt;
1837
    logMetacat.info("Before open the stream"+u.toString());
1838
    InputStream input = u.openStream();
1839
    logMetacat.info("Afetr open the stream"+u.toString());
1840
    InputStreamReader istream = new InputStreamReader(input);
1841
    StringBuffer serverResponse = new StringBuffer();
1842
    while((istreamInt = istream.read()) != -1)
1843
    {
1844
      istreamChar = (char)istreamInt;
1845
      serverResponse.append(istreamChar);
1846
    }
1847
    istream.close();
1848
    input.close();
1849

    
1850
    return serverResponse.toString();
1851
  }
1852

    
1853
  /**
1854
   * Method for writing replication messages to a log file specified in
1855
   * metacat.properties
1856
   */
1857
  public static void replLog(String message)
1858
  {
1859
    try
1860
    {
1861
      FileOutputStream fos = new FileOutputStream(
1862
                                 util.getOption("replicationlog"), true);
1863
      PrintWriter pw = new PrintWriter(fos);
1864
      SimpleDateFormat formatter = new SimpleDateFormat ("yy-MM-dd HH:mm:ss");
1865
      java.util.Date localtime = new java.util.Date();
1866
      String dateString = formatter.format(localtime);
1867
      dateString += " :: " + message;
1868
      //time stamp each entry
1869
      pw.println(dateString);
1870
      pw.flush();
1871
    }
1872
    catch(Exception e)
1873
    {
1874
      System.out.println("error writing to replication log from " +
1875
                         "MetacatReplication.replLog: " + e.getMessage());
1876
      //e.printStackTrace(System.out);
1877
    }
1878
  }
1879

    
1880
  /**
1881
   * Method for writing replication messages to a log file specified in
1882
   * metacat.properties
1883
   */
1884
  public static void replErrorLog(String message)
1885
  {
1886
    try
1887
    {
1888
      FileOutputStream fos = new FileOutputStream(
1889
                                 util.getOption("replicationerrorlog"), true);
1890
      PrintWriter pw = new PrintWriter(fos);
1891
      SimpleDateFormat formatter = new SimpleDateFormat ("yy-MM-dd HH:mm:ss");
1892
      java.util.Date localtime = new java.util.Date();
1893
      String dateString = formatter.format(localtime);
1894
      dateString += " :: " + message;
1895
      //time stamp each entry
1896
      pw.println(dateString);
1897
      pw.flush();
1898
    }
1899
    catch(Exception e)
1900
    {
1901
      System.out.println("error writing to replication log from " +
1902
                         "MetacatReplication.replLog: " + e.getMessage());
1903
      //e.printStackTrace(System.out);
1904
    }
1905
  }
1906

    
1907
  /**
1908
   * Returns true if the replicate field for server in xml_replication is 1.
1909
   * Returns false otherwise
1910
   */
1911
  public static boolean replToServer(String server)
1912
  {
1913
    DBConnection dbConn = null;
1914
    int serialNumber = -1;
1915
    PreparedStatement pstmt = null;
1916
    try
1917
    {
1918
      dbConn=DBConnectionPool.
1919
                  getDBConnection("MetacatReplication.repltoServer");
1920
      serialNumber=dbConn.getCheckOutSerialNumber();
1921
      pstmt = dbConn.prepareStatement("select replicate from " +
1922
                                    "xml_replication where server like '" +
1923
                                     server + "'");
1924
      pstmt.execute();
1925
      ResultSet rs = pstmt.getResultSet();
1926
      boolean tablehasrows = rs.next();
1927
      if(tablehasrows)
1928
      {
1929
        int i = rs.getInt(1);
1930
        if(i == 1)
1931
        {
1932
          pstmt.close();
1933
          //conn.close();
1934
          return true;
1935
        }
1936
        else
1937
        {
1938
          pstmt.close();
1939
          //conn.close();
1940
          return false;
1941
        }
1942
      }
1943
    }
1944
    catch(Exception e)
1945
    {
1946
      System.out.println("error in MetacatReplication.replToServer: " +
1947
                         e.getMessage());
1948
    }
1949
    finally
1950
    {
1951
      try
1952
      {
1953
        pstmt.close();
1954
        //conn.close();
1955
      }//try
1956
      catch(Exception ee)
1957
      {
1958
        logMetacat.error("Error in MetacatReplication.replToServer: "
1959
                                  +ee.getMessage());
1960
      }//catch
1961
      finally
1962
      {
1963
        DBConnectionPool.returnDBConnection(dbConn, serialNumber);
1964
      }//finally
1965
    }//finally
1966
    return false;
1967
    //the default if this server does not exist is to not replicate to it.
1968
  }
1969

    
1970

    
1971
}
(46-46/65)