/*--
  This file is a part of ZetaGrid, a simple and secure Grid Computing
  kernel.

  Copyright (c) 2001-2004 Sebastian Wedeniwski.  All rights reserved.

  Use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:

  1. The source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.

  2. The origin of this software must not be misrepresented; you must
     not claim that you wrote the original software.  If you plan to
     use this software in a product, please contact the author.

  3. Altered source versions must be plainly marked as such, and must
     not be misrepresented as being the original software. The author
     must be informed about these changes.

  4. The name of the author may not be used to endorse or promote
     products derived from this software without specific prior written
     permission.

  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

  Version 1.9.0, February 8, 2004

  This program is based on the work of:
     S. Wedeniwski
--*/

package zeta.tool;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import zeta.crypto.Decrypter;
import zeta.crypto.KeyManager;
import zeta.tool.check.CheckConsistency;
import zeta.tool.check.CheckManager;
import zeta.util.Base64;
import zeta.util.DatabaseUtils;
import zeta.util.Properties;
import zeta.util.StreamUtils;
import zeta.util.ThrowableHandler;

import BlowfishJ.BlowfishECB;

// ToDo: Interface!!! task_id!!!

public class GetData {

  public static void main(String[] args) throws IOException {
    if (args.length == 2) {
      decrypt(Integer.parseInt(args[0]), args[1]);
    } else {
      get();
    }
  }

  static boolean get() {
    getViaServlet();
    getViaFtp();
    boolean result = get("SELECT comp.work_unit_id,comp.range,comp.result,ws.hostname,comp.task_id FROM"
                         + " zeta.recomputation comp, zeta.workstation ws WHERE comp.result IS NOT NULL"
                         + " AND ws.id=comp.workstation_id AND ws.server_id=comp.server_id ORDER BY comp.work_unit_id", true);
    if (get("SELECT res.work_unit_id,comp.range,res.result,ws.hostname,comp.task_id FROM"
            + " zeta.result res, zeta.workstation ws, zeta.computation comp WHERE res.result IS NOT NULL"
            + " AND ws.id=comp.workstation_id AND ws.server_id=comp.server_id AND comp.work_unit_id=res.work_unit_id AND comp.task_id=res.task_id ORDER BY res.work_unit_id", false)) {
      result = true;
    }
    return result;
  }

  /**
   *  1: work_unit_id, 2: range, 3: result, 4: hostname, 5: task_id
  **/
  static boolean get(String sql, boolean removeData) {
    StringBuffer remove = new StringBuffer(500);
    boolean newFiles = false;
    Connection connection = null;
    Statement stmt = null;
    Statement stmt2 = null;
    try {
      Properties properties = new Properties();
      String path1 = properties.get("data.servlet.url");
      String path2 = properties.get("ftp.path");
      if (path1 != null && path1.length() > 0 || path2 != null && path2.length() > 0) {
        return false;
      }
      System.out.println("database");
      connection = getConnection();
      stmt = connection.createStatement();
      stmt2 = connection.createStatement();
      ResultSet rs = stmt.executeQuery(sql);
      while (rs.next()) {
        Blob blob = rs.getBlob(3);
        InputStream in = blob.getBinaryStream();
        long workUnitId = rs.getLong(1);
        int range = rs.getInt(2);
        getData(connection, stmt2, workUnitId, range, in, rs.getString(4), rs.getInt(5), ConstantProperties.TEMP_DIR + "zeta_zeros_" + workUnitId + '_' + range + ".zip", remove);
        newFiles = true;
        try {
          Thread.sleep(100);
        } catch (InterruptedException ie) {
        }
      }
      rs.close();
      if (removeData && remove.length() > 0) {
        remove.append(')');
        stmt.executeUpdate(remove.toString());
      }
    } catch (Exception e) {
      ThrowableHandler.handle(e);
    } finally {
      DatabaseUtils.close(stmt);
      DatabaseUtils.close(stmt2);
      DatabaseUtils.close(connection);
    }
    return newFiles;
  }

  static void getViaServlet() {
    Connection connection = null;
    Statement stmt = null;
    HttpURLConnection connectionGet = null;
    int proxyIndex = 0;
    boolean again2 = false;
    do {
      String[] proxies = null;
      again2 = false;
      try {
        Properties properties = new Properties();
        String path = properties.get("data.servlet.url");
        if (path == null || path.length() == 0) {
          return;
        }
        String prp = properties.get("proxies");
        if (prp != null) {
          StringTokenizer st = new StringTokenizer(prp, ",");
          proxies = new String[st.countTokens()];
          for (int i = 0; i < proxies.length; ++i) {
            proxies[i] = st.nextToken();
          }
        }
        java.util.Properties props = System.getProperties();
        props.put("sun.net.inetaddr.ttl", "0");
        props.put("networkaddress.cache.ttl", "0");
        props.put("networkaddress.cache.negative.ttl", "0");
        connection = getConnection();
        stmt = connection.createStatement();
        byte[] serverKey = null;
        int encryptionId = properties.get("data.servlet.encryption.id", 0);
        if (encryptionId > 0) {
          ResultSet rs = stmt.executeQuery("SELECT key FROM zeta.server WHERE server_id=" + encryptionId);
          serverKey = (rs.next())? rs.getBytes(1) : null;
          rs.close();
        }
        if (proxies != null) {
          props.put("http.proxyHost", proxies[proxyIndex]);
          System.out.println("using proxy " + proxies[proxyIndex]);
        }
        URL url = new URL("http", properties.get("data.servlet.host", ""), properties.get("data.servlet.port", 80), path + '?' + encrypt(serverKey, "dir=."));
        connectionGet = (HttpURLConnection)url.openConnection();
        connectionGet.setRequestMethod("GET");
        connectionGet.setDoInput(true);
        connectionGet.connect();
        if (connectionGet.getResponseCode() == HttpURLConnection.HTTP_OK) {
          int size = connectionGet.getContentLength();
          ByteArrayOutputStream out = new ByteArrayOutputStream(size+10);
          StreamUtils.writeData(connectionGet.getInputStream(), out, false, true);
          connectionGet.disconnect();
          connectionGet = null;
          byte[] buffer = out.toByteArray();
          if (buffer.length == size) {
            proxyIndex = 0;
            String s = new String(buffer);
            final int l = s.length();
            List filenames = new ArrayList(Math.max(1, l/20));
            for (int i = 0; i < l; ++i) {
              int j = s.indexOf('\n', i);
              if (j == -1) {
                filenames.add(s.substring(i));
                break;
              }
              filenames.add(s.substring(i, j));
              i = j;
            }
            System.out.println("download " + filenames.size() + " files via servlet.");
            String filename = null;
            String deleteFilename = null;
            boolean again = false;
            Iterator iter = filenames.iterator();
            while (true) {
              if (!again) {
                if (iter.hasNext()) {
                  filename = (String)iter.next();
                } else {
                  filename = null;
                }
              }
              again = false;
              if (filename == null || filename.startsWith("zeta_zeros_")) {
                int taskId = 1; //ToDo: not fix
                long workUnitId = 0;
                int range = 0;
                String hostname = "";
                int idx = (filename == null)? 0 : filename.lastIndexOf('_');
                if (idx > 0) {
                  workUnitId = Long.parseLong(filename.substring(idx+1, filename.length()-4));
                  ResultSet rs = stmt.executeQuery("SELECT comp.range,ws.hostname FROM zeta.computation comp, zeta.workstation ws WHERE task_id=" + taskId
                                                 + " AND comp.workstation_id=ws.id AND comp.server_id=ws.server_id AND comp.work_unit_id=" + workUnitId);
                  if (rs.next()) {
                    range = rs.getInt(1);
                    hostname = rs.getString(2);
                  }
                  rs.close();
                }
                InputStream in = null;
                try {
                  StringBuffer pathExt = new StringBuffer(200);
                  pathExt.append(path);
                  if (range > 0 && filename != null) {
                    pathExt.append('?');
                    if (deleteFilename != null && !deleteFilename.equals(filename)) {
                      pathExt.append(encrypt(serverKey, "get=" + filename + "&del=" + deleteFilename));
                    } else {
                      pathExt.append(encrypt(serverKey, "get=" + filename));
                    }
                  } else if (deleteFilename != null) {
                    pathExt.append('?');
                    pathExt.append(encrypt(serverKey, "del=" + deleteFilename));
                  }
                  url = new URL("http", properties.get("data.servlet.host", ""), properties.get("data.servlet.port", 80), pathExt.toString());
                  connectionGet = (HttpURLConnection)url.openConnection();
                  connectionGet.setRequestMethod("GET");
                  connectionGet.setDoInput(true);
                  connectionGet.connect();
                  if (connectionGet.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    deleteFilename = filename;
                    if (range > 0) {
                      size = connectionGet.getContentLength();
                      out = new ByteArrayOutputStream(size+10);
                      WriterThread writerThread = new WriterThread(connectionGet.getInputStream(), out);
                      writerThread.start();
                      try {
                        for (int i = 0; i < 20; ++i) {
                          if (writerThread.isFinish()) {
                            break;
                          }
                          Thread.sleep(500);
                        }
                      } catch (InterruptedException ie) {
                        throw new IOException(ie.getMessage());
                      }
                      if (!writerThread.isFinish()) {
                        writerThread.interrupt();
                      }
                      //StreamUtils.writeData(connectionGet.getInputStream(), out, false, true);
                      connectionGet.disconnect();
                      connectionGet = null;
                      buffer = out.toByteArray();
                      if (buffer.length == size) {
                        in = new ByteArrayInputStream(buffer);
                        getData(connection, stmt, workUnitId, range, in, hostname, taskId, ConstantProperties.TEMP_DIR + "zeta_zeros_" + workUnitId + '_' + range + ".zip", null);
                      } else {
                        throw new IOException("data not completed (received " + buffer.length + " instead " + size + ')');
                      }
                    }
                    if (proxyIndex != 0) {
                      proxyIndex = 0;
                      props.put("http.proxyHost", proxies[proxyIndex]);
                      System.out.println("use proxy=" + proxies[proxyIndex]);
                    }
                  }
                } catch (IOException ioe) {
                  if (proxies != null) {
                    if (++proxyIndex < proxies.length) {
                      props.put("http.proxyHost", proxies[proxyIndex]);
                      System.out.println("use proxy=" + proxies[proxyIndex]);
                      again = true;
                      try {
                        Thread.sleep(500);
                      } catch (InterruptedException ie) {
                      }
                    } else {
                      ThrowableHandler.handle(ioe);
                      proxyIndex = 0;
                    }
                  }
                } catch (Exception e) {
                  ThrowableHandler.handle(e);
                } finally {
                  StreamUtils.close(in);
                }
              }
              if (filename == null) {
                break;
              }
            }
          } else {
            throw new IOException("data not completed (received " + buffer.length + " instead " + size + ')');
          }
        }
      } catch (Exception e) {
        ThrowableHandler.handle(e);
        if (proxies != null && ++proxyIndex < proxies.length) {
          again2 = true;
        } else {
          proxyIndex = 0;
        }
      } finally {
        if (connectionGet != null) {
          connectionGet.disconnect();
        }
        DatabaseUtils.close(stmt);
        DatabaseUtils.close(connection);
      }
    } while (again2);
  }

  private static String encrypt(byte[] serverKey, String text) {
    if (serverKey != null) {
      try {
        byte[] statements = text.getBytes("UTF-8");
        int statementsLength = statements.length;
        statements = StreamUtils.align8(statements);
        BlowfishECB bfecb = new BlowfishECB(serverKey);
        bfecb.encrypt(statements);
        bfecb.cleanUp();
        text = "actions=" + Base64.encode(statements) + "&actions_length=" + statementsLength;
      } catch (UnsupportedEncodingException ue) {
      }
    }
    return text;
  }

  static void getViaFtp() {
    Connection connection = null;
    Statement stmt = null;
    Statement stmt2 = null;
    FTP ftp = null;
    try {
      Properties properties = new Properties();
      String path = properties.get("ftp.path");
      if (path == null || path.length() == 0) {
        return;
      }
      connection = getConnection();
      stmt = connection.createStatement();
      stmt2 = connection.createStatement();
      ftp = new FTP();
      ftp.setSocketTimeout(properties.get("ftp.socket.timeout", 50000));
      ftp.setBufferSize(properties.get("ftp.buffer.size", 10240));
      if (properties.get("ftp.debug", "true").equals("true")) {
        ftp.setDebug(System.out);
      } else {
        ftp.setDebug(null);
      }
      ftp.connect(properties.get("ftp.host", ""));
      ftp.login(properties.get("ftp.user", ""), properties.get("ftp.password", ""));
      ftp.cd(path);
      boolean again = false;
      int numberOfFiles = -1;
      do {
        again = false;
        numberOfFiles = -1;
        List dir = ftp.longDir();
        String maxTime = "";
        Iterator i = dir.iterator();
        while (i.hasNext()) {
          String s = (String)i.next();
          int idx = s.indexOf("zeta_zeros_");
          if (idx >= 13) {
            int shift = (s.charAt(idx-13) == ' ')? 12 : 13;
            s = s.substring(idx-shift);
            if (s.length() > 12 && (!again || maxTime.substring(0, maxTime.indexOf(':')-3).equals(s.substring(0, s.indexOf(':')-3))) && maxTime.compareTo(s) < 0) {
              maxTime = s.substring(0, 12);
            }
            if (!again) {
              idx = maxTime.indexOf(':');
              int idx2 = s.indexOf(':');
              if (idx > 2 && idx2 > 2 && (idx != idx2 || !maxTime.substring(0, idx-3).equals(s.substring(0, idx2-3)))) {
                final String[] month = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
                for (idx = 0; idx < month.length && !month[idx].equals(maxTime.substring(0, 3)); ++idx);
                for (idx2 = 0; idx2 < month.length && !month[idx2].equals(s.substring(0, 3)); ++idx2);
                if (idx == month.length || idx2 == month.length) {
                  System.out.println("Wrong month " + maxTime + ", " + s);
                  System.exit(1);
                }
                if (idx2 < idx) {
                  maxTime = s;
                } else if (idx2 == idx && maxTime.compareTo(s.substring(0, maxTime.length())) > 0) {
                  maxTime = s;
                }
                again = true;
              }
            }
            ++numberOfFiles;
          }
        }
        System.out.println("download " + Math.max(0, numberOfFiles) + " files via ftp.");
        ftp.setTransferSleep(properties.get("ftp.sleep", 200));
        for (i = dir.iterator(); i.hasNext();) {
          String s = (String)i.next();
          int idx = s.indexOf("zeta_zeros_");
          if (idx >= 13) {
            int shift = (s.charAt(idx-13) == ' ')? 12 : 13;
            if (again && maxTime.substring(0, maxTime.indexOf(':')-3).equals(s.substring(idx-shift, s.indexOf(':')-3)) || !again && maxTime.compareTo(s.substring(idx-shift, idx)) > 0) {
              try {
                String filename = s.substring(idx);
                idx = s.lastIndexOf('_');
                if (idx > 0) {
                  System.out.print(formatter.format(new Date()));
                  System.out.flush();
                  ftp.get(filename, ConstantProperties.TEMP_DIR + filename, FTP.MODE_BINARY);
                  int taskId = 1; //ToDo: not fix
                  long workUnitId = Long.parseLong(s.substring(idx+1, s.length()-4));
                  ResultSet rs = stmt.executeQuery("SELECT comp.range,ws.hostname FROM zeta.computation comp, zeta.workstation ws WHERE task_id=" + taskId
                                                 + " AND comp.workstation_id=ws.id AND comp.server_id=ws.server_id AND comp.work_unit_id=" + workUnitId);
                  if (rs.next()) {
                    int range = rs.getInt(1);
                    InputStream in = null;
                    try {
                      in = new FileInputStream(ConstantProperties.TEMP_DIR + filename);
                      getData(connection, stmt2, workUnitId, range, in, rs.getString(2), taskId, ConstantProperties.TEMP_DIR + "zeta_zeros_" + workUnitId + '_' + range + ".zip", null);
                    } catch (IOException ioe) {
                      ThrowableHandler.handle(ioe);
                    } finally {
                      StreamUtils.close(in);
                    }
                    new File(ConstantProperties.TEMP_DIR + filename).delete();
                  }
                  rs.close();
                  ftp.deleteFile(filename);
                }
              } catch (SQLException se) {
                ThrowableHandler.handle(se);
                DatabaseUtils.close(stmt);
                DatabaseUtils.close(stmt2);
                DatabaseUtils.close(connection);
                try {
                  Thread.sleep(2000);
                } catch (InterruptedException ie) {
                }
                connection = getConnection();
                stmt = connection.createStatement();
                stmt2 = connection.createStatement();
              } catch (SocketTimeoutException ste) {
                ThrowableHandler.handle(ste);
                ftp.disconnect();
                ftp = null;
                ftp = new FTP();
                ftp.setSocketTimeout(properties.get("ftp.socket.timeout", 50000));
                ftp.setBufferSize(properties.get("ftp.buffer.size", 10240));
                if (properties.get("ftp.debug", "true").equals("true")) {
                  ftp.setDebug(System.out);
                } else {
                  ftp.setDebug(null);
                }
                ftp.connect(properties.get("ftp.host", ""));
                ftp.login(properties.get("ftp.user", ""), properties.get("ftp.password", ""));
                ftp.cd(path);
              }
            }
          }
        }
      } while (again && numberOfFiles >= 20);
    } catch (Throwable e) {   // an OutOfMemoryError may occur
      ThrowableHandler.handle(e);
      if (e.getMessage().indexOf("closing control connection") >= 0) {
        ftp = null;
      }
    } finally {
      DatabaseUtils.close(stmt);
      DatabaseUtils.close(stmt2);
      DatabaseUtils.close(connection);
      if (ftp != null) {
        ftp.disconnect();
      }
    }
  }

  static boolean getData(Connection connection, Statement stmt2, long workUnitId, int range, InputStream dataIn, String hostname, int taskId, String outputFilename, StringBuffer remove) throws IOException, SQLException {
    File file = new File(outputFilename);
    System.out.print(hostname + ": " + file.getName());
    System.out.flush();
    FileOutputStream out = new FileOutputStream(file);
    StreamUtils.writeData(dataIn, out, true, true);
    try {
      decrypt(taskId, outputFilename, connection);
      File file1 = new File(ConstantProperties.FINAL_DIR + '\\' + taskId + '\\' + outputFilename.substring(ConstantProperties.TEMP_DIR.length()));
      File file2 = new File(outputFilename);
      if (file2.exists() && (!file1.exists() || file1.delete())) {
        file2.renameTo(file1);
      }
      if (file1.exists() && file1.length() > 0) {
        if (remove != null) {
          if (remove.length() == 0) {
            remove.append("UPDATE zeta.recomputation SET result=NULL WHERE NOT result IS NULL AND work_unit_id IN (");
            remove.append(workUnitId);
          } else {
            remove.append(',');
            remove.append(workUnitId);
          }
        }
        try {
          stmt2.executeUpdate("UPDATE zeta.result SET result=NULL WHERE task_id=" + taskId + " AND work_unit_id=" + workUnitId);
        } catch (SQLException se) {
          ThrowableHandler.handle(se);
        }
      }
      System.out.println(".");
      return true;
    } catch (FileNotFoundException fnfe) {
      ThrowableHandler.handle(fnfe);
    } catch (Exception ioe) {
      System.out.println(file.getName() + ": ZIP Error!");
      file.delete();
      StringWriter writer = new StringWriter(1000);
      ioe.printStackTrace(new PrintWriter(writer));
      writer.flush();
      if (ioe instanceof IOException) {
        try {
          if (!StreamUtils.checkAvailDiskSpace(new ByteArrayInputStream(new byte[6*1024*1024]), new File(ConstantProperties.TEMP_DIR + "$$$.zip"))) {
            System.err.println(writer.toString());
            return false;
          }
        } catch (IOException ie) {
          ie.printStackTrace();
          return false;
        }
      }
      Properties properties = new Properties();
      int serverId = properties.get("server_id", 1);
      ResultSet rs2 = stmt2.executeQuery("SELECT server_id FROM zeta.computation WHERE work_unit_id=" + workUnitId + " AND task_id=1");
      if (rs2.next()) {
        serverId = rs2.getInt(1);
      } else {
        System.err.println("FATAL Error: server_id");
        System.exit(1);
      }
      rs2.close();
      rs2 = stmt2.executeQuery("SELECT stop FROM zeta.recomputation WHERE work_unit_id=" + workUnitId + " AND task_id=1");
      if (rs2.next()) {
        Timestamp stop = rs2.getTimestamp(1);
        rs2.close();
        if (stop != null) {
          DatabaseUtils.executeAndLogUpdate(serverId, stmt2,
                                            "UPDATE zeta.recomputation SET (start,count,stop,result,reason)=(CURRENT TIMESTAMP,count+1,NULL,NULL,'" + DatabaseUtils.encodeName(writer.toString()) + "') WHERE task_id=1 AND work_unit_id=" + workUnitId);
        }
      } else {
        rs2.close();
        rs2 = stmt2.executeQuery("SELECT version,workstation_id,user_id FROM zeta.computation WHERE work_unit_id=" + workUnitId + " AND task_id=1");
        if (rs2.next()) {
          String version = rs2.getString(1);
          int workstationId = rs2.getInt(2);
          int userId = rs2.getInt(3);
          rs2.close();
          // ToDo: add column parameters
          DatabaseUtils.executeAndLogUpdate(serverId, stmt2,
                                            "INSERT INTO zeta.recomputation (task_id,work_unit_id,range,server_id,version,workstation_id,user_id,start,reason) VALUES (1,"
                                            + workUnitId + ',' + range + ',' + serverId + ",'" + version + "'," + workstationId + ',' + userId + ",CURRENT TIMESTAMP,'"
                                            + DatabaseUtils.encodeName(writer.toString()) + "')");
          stmt2.executeUpdate("UPDATE zeta.result SET result=NULL where work_unit_id=" + workUnitId);
        } else {
          rs2.close();
        }
      }
    }
    return false;
  }

  static void decrypt(int taskId, String filename) throws IOException {
    decrypt(taskId, filename, null);
  }

  static void decrypt(int taskId, String filename, Connection connection) throws IOException {
    if (defaultDecryptionNumber == null) {
      Connection con = null;
      Statement stmt = null;
      try {
        if (connection == null) {
          con = getConnection();
          stmt = con.createStatement();
        } else {
          stmt = connection.createStatement();
        }
        ResultSet rs = stmt.executeQuery("SELECT decryption_number FROM zeta.task WHERE id=0");
        if (rs.next()) {
          defaultDecryptionNumber = rs.getString(1);
        }
        rs.close();
      } catch (Exception e) {
        ThrowableHandler.handle(e);
      } finally {
        DatabaseUtils.close(stmt);
        DatabaseUtils.close(con);
      }
    }
    Properties properties = new Properties();
    ZipInputStream zip = null;
    ZipOutputStream zipOut = null;
    FileOutputStream out = null;
    FileInputStream in = null;
    try {
      zip = new ZipInputStream(new FileInputStream(filename));
      zipOut = new ZipOutputStream(new FileOutputStream(ConstantProperties.TEMP_DIR + "$$$.zip"));
      zipOut.setLevel(Deflater.BEST_COMPRESSION);
      for (int k = 0; k < 2; ++k) {
        ZipEntry zEntry = zip.getNextEntry();
        if (zEntry == null) {
          throw new IOException(filename + " wrong entry!");
        }
        String s = zEntry.getName();
        try {
          out = new FileOutputStream(ConstantProperties.TEMP_DIR + s);
          StreamUtils.writeData(zip, out, false, true);
          out = null;
        } catch (ZipException ze) {
          ThrowableHandler.handle(ze);
          StreamUtils.close(out);
          out = null;
          if (s.endsWith(".log.$$$")) {
            String s2 = s.substring(0, s.length()-4);
            File file = new File(ConstantProperties.TEMP_DIR + s);
            File file2 = new File(ConstantProperties.TEMP_DIR + s2);
            file2.delete();
            file.renameTo(file2);
            if (!file2.exists() && !file2.createNewFile()) throw ze;
            s = s2;
          } else if (s.endsWith(".log")) {
            File file = new File(ConstantProperties.TEMP_DIR + s);
            if (!file.exists() && !file.createNewFile()) throw ze;
          } else {
            throw ze;
          }
        }
        String s2 = s;
        File file = new File(ConstantProperties.TEMP_DIR + s);
        if (file.length() == 0) {
          throw new ZipException(file.getName() + ": Empty!");
          /*if (s.endsWith(".log.$$$")) {
            s2 = s.substring(0, s.length()-4);
            file = new File(ConstantProperties.TEMP_DIR + s);
            File file2 = new File(ConstantProperties.TEMP_DIR + s2);
            file2.delete();
            file.renameTo(file2);
          }*/
        } else if (s.endsWith(".$$$")) {
          s2 = s.substring(0, s.length()-4);
          Decrypter decrypter = new Decrypter(KeyManager.getEncryptorKey(null));    // ToDo: configurable
          try {
            decrypter.decrypt(defaultDecryptionNumber, ConstantProperties.TEMP_DIR + s, ConstantProperties.TEMP_DIR + s2);
          } catch (RuntimeException e) {
            file = new File(ConstantProperties.TEMP_DIR + s2);
            if (!file.delete()) {
              System.err.println("File not deleted: " + file.getPath());
            }
            file = new File(ConstantProperties.TEMP_DIR + s);
            throw e;
          } finally {
            if (!file.delete()) {
              System.err.println("File not deleted: " + file.getPath());
            }
          }
        }
        file = new File(ConstantProperties.TEMP_DIR + s2);
        if (file.exists() && file.length() == 0 && !file.getName().endsWith(".log") && file.delete()) {
          new File(ConstantProperties.TEMP_DIR + s2 + ".bz2").renameTo(file);
        }
        if (!file.exists()) {
          System.err.println("File does not exist: " + s2);
          if (connection != null) {
            Statement stmt = null;
            try {
              stmt = connection.createStatement();
              int idx = s2.indexOf('_', 11);
              String workUnitId = s2.substring(11, idx);
              ResultSet rs = stmt.executeQuery("SELECT work_unit_id FROM zeta.recomputation WHERE work_unit_id=" + workUnitId + " AND task_id=" + 1);
              if (rs.next()) {
                rs.close();
                //System.exit(1);
              } else {
                rs.close();
                int serverId = properties.get("server_id", 1);
                rs = stmt.executeQuery("SELECT server_id FROM zeta.computation WHERE work_unit_id=" + workUnitId + " AND task_id=1");
                if (rs.next()) {
                  serverId = rs.getInt(1);
                } else {
                  System.err.println("FATAL Error: server_id");
                  System.exit(1);
                }
                rs.close();
                String range = s2.substring(idx+1, s2.length()-4);
                // ToDo: add column parameters
                DatabaseUtils.executeAndLogUpdate(serverId, stmt,
                                                  "INSERT INTO zeta.recomputation (task_id,work_unit_id,range,server_id) VALUES (1," + workUnitId + ',' + range + ',' + serverId + ')');
                stmt.executeUpdate("UPDATE zeta.result SET result=NULL where work_unit_id=" + workUnitId);
              }
            } catch (SQLException se) {
              ThrowableHandler.handle(se);
              System.exit(1);
            } finally {
              DatabaseUtils.close(stmt);
            }
          }
          throw new FileNotFoundException(s2);
        }
        long workUnitId = 0;
        int range = 0;
        try {
          int idx = s2.indexOf('_', 11);
          workUnitId = Long.parseLong(s2.substring(11, idx));
          range = Integer.parseInt(s2.substring(idx+1, s2.length()-4));
        } catch (Exception e) {
          ThrowableHandler.handle(e);
        }
        try {
          CheckConsistency checkConsistency = CheckManager.getCheckManager().getCheckConsistency(taskId);
          if (checkConsistency != null) {
            if (!checkConsistency.check(workUnitId, range, ConstantProperties.TEMP_DIR + s2, connection)) {
              throw new RuntimeException("Check consistency failed!");
            }
          }
          zipOut.putNextEntry(new ZipEntry(s2));
          in = new FileInputStream(ConstantProperties.TEMP_DIR + s2);
          StreamUtils.writeData(in, zipOut, true, false);
          zipOut.flush();
          in = null;
        } finally {
          file = new File(ConstantProperties.TEMP_DIR + s2);
          file.delete();
        }
      }
      zipOut.close();
      zipOut = null;
      zip.close();
      zip = null;
      File file1 = new File(filename);
      File file2 = new File(ConstantProperties.TEMP_DIR + "$$$.zip");
      if (file2.exists() && file1.delete()) file2.renameTo(file1);
    } finally {
      StreamUtils.close(in);
      StreamUtils.close(out);
      StreamUtils.close(zip);
      StreamUtils.close(zipOut);
    }
  }

  public static Connection getConnection() throws Exception {
    Properties properties = new Properties();
    Class.forName(properties.get("db_driver", "COM.ibm.db2.jdbc.app.DB2Driver")).newInstance();
    return DriverManager.getConnection(properties.get("db_url", "jdbc:db2:zeta"), properties.get("db_user", ""), properties.get("db_password", ""));
  }

  private static SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss ");

  /**
   *  The default decryption number for the half-certified Diffie-Hellman protocol.
  **/
  private static String defaultDecryptionNumber = null;

  private static class WriterThread extends Thread {
    WriterThread(InputStream in, OutputStream out) {
      this.in = in;
      this.out = out;
    }

    public void run() {
      try {
        StreamUtils.writeData(in, out, false, false);
      } catch (IOException ioe) {
      } finally {
        finish = true;
      }
    }

    void finish() {
      StreamUtils.close(in);
    }

    boolean isFinish() {
      return finish;
    }

    private InputStream in;
    private OutputStream out;
    private boolean finish = false;
  }
}