/*
 * $Id: ConverterServer.java,v 1.33 2007/10/30 13:46:31 marbobo Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: ConverterServer.java,v $
 * Revision 1.33  2007/10/30 13:46:31  marbobo
 * + removed unnecessary statements
 *
 * Revision 1.32  2007/08/28 06:29:35  marbobo
 * + Debug-Ausgabe korrigiert
 *
 * Revision 1.31  2007/08/16 11:10:58  marbobo
 * + Bugfixes
 * + Refactorings
 *
 * Revision 1.30  2007/02/12 07:15:58  lawern
 * Überarbeitung und Anpassung an Linux
 *
 */
package vkc.converter.server;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;

import org.apache.commons.codec.binary.Base64;

import vkc.converter.DocConverter;
import vkc.converter.SOfficeDocConverter;
import vkc.converter.common.Status;
import vkc.db.common.ConverterInfo;
import vkc.db.common.ConverterTiming;
import vkc.exception.DBException;
import vkc.exception.LicenseException;
import vkc.misc.VKCLogManager;
import vkc.server.ServerSupport;
import vkc.util.FileUtils;
import vkc.util.ObjectCloner;
import vkc.view.WebShare;

import com.roeschter.jsl.ServiceStopper;
import com.roeschter.jsl.Stopable;

/**
 * This class implements a multithreaded conversion server. It listens on a
 * given port, waits for client requests, and serves document conversions.
 * 
 * @author koppy
 */
public class ConverterServer extends ServerSupport implements Stopable {
    public static final String PROPERTIES_FILE = "converter.properties";

    private LookupDaemon lookupDaemon = null;

    private Thread lookupThread;

    private Properties properties = null;

    private ConverterServer() {
    }

    /**
     * Constructs a converter server with the given Properties
     * 
     * @param p
     *            the properties
     */
    public ConverterServer(Properties p) throws DBException {
        this();
        this.properties = p;
        this.conf = new ConverterInfo();

        String port = p.getProperty("vkc.converter.port");

        if (port == null) {
            port = "-1";
        }

        try {
            conf.setPort(Integer.parseInt(port));
        } catch (NumberFormatException e) {
            ConverterServer.exit("Specified Port ('vkc.converter.port') must be a numeric value!");
        }

        String logDirString = p.getProperty("vkc.converter.logdir");

        if (logDirString == null) {
            logDirString = ".";
        }

        File logDir = new File(logDirString);

        if (!logDir.exists()) {
            if (!logDir.mkdirs())
                ConverterServer.exit("Log Directory ('vkc.converter.logdir') could not be created! Pleas choose an existing one.");
        }

        if (!logDir.isDirectory()) {
            ConverterServer.exit("Log Directory ('vkc.converter.logdir') must point to a directory!");
        }

        String v = p.getProperty("vkc.converter.verbose");
        configureLogger(logDir.getAbsolutePath() + File.separator + "converter_", v != null && v.trim().equals("true"));
        configureConverter(p);

        // Database initialization
        Properties dbProps = p;
        // PersistentManager Initialization
        pm.addDatabaseByProperties(dbProps);

        // Kopie von Propertys in db1Props anlegen
        String[] optionalProps = {"driver_class", "username", "password", "pool.maxSize", 
                                  "pool.validationQuery", "pool.testWhileIdle", "pool.testOnReturn", 
                                  "pool.testOnBorrow", "pool.timeBetweenEvictionRunsMillis"};

        Properties db1Props = new Properties();
        db1Props.setProperty("vkc.db.url", p.getProperty("vkc.db.url"));

        for (int j = 0; j < optionalProps.length; j++) {
            String property = optionalProps[j];

            if (p.getProperty("vkc.db." + property) != null) {
                db1Props.setProperty("vkc.db." + property, p.getProperty("vkc.db." + property));
            }
        }

        // Nach weiteren DBURLS suchen
        for (int i = 2;; i++) {
            String dbUrl = p.getProperty("vkc.db" + i + ".url");

            if (dbUrl == null) {
                break;
            }

            // Additional database configured...
            dbProps.setProperty("vkc.db.url", dbUrl);

            for (int j = 0; j < optionalProps.length; j++) {
                String property = optionalProps[j];

                String propValue = p.getProperty("vkc.db" + i + "." + property);

                if (propValue == null) {
                    propValue = db1Props.getProperty("vkc.db." + property);
                }

                if (propValue != null) {
                    dbProps.setProperty("vkc.db." + property, propValue);
                }
            }

            pm.addDatabaseByProperties(dbProps);
        }

        // Registers the converter server in all databases
        // if already registered, nothing is done
        for (Iterator it = pm.getDbUrls().iterator(); it.hasNext();) {
            String url = null;

            try {
                url = (String) it.next();
                ConverterInfo.register(p, url);
            } catch (DBException e1) {
                VKCLogManager.errorLog(this, "", "Converter-Server für DB " + url + " nicht möglich!");
                it.remove();
            }
        }

        conf.initialize(new Integer(p.getProperty("vkc.converter.id")));

        if (conf.getPort() == -1) {
            ConverterServer.exit("The Converter port ('vkc.converter.port') was not set!");
        }

        // Lookup Daemon
        lookupDaemon = new LookupDaemon(this);
        lookupThread = new Thread(lookupDaemon);
        // lookupThread.setDaemon( true );
        lookupThread.setPriority(Thread.MIN_PRIORITY);
        lookupThread.start();
    }

    /**
     * Returns the configuration of this converter-server. Every time this
     * method is accessed, the configuration is updated by the current values in
     * the database.
     * 
     * @return the ConverterInfo description
     */
    public ConverterInfo getConfiguration(String dbUrl) {
        try {
            conf.associateWithDbUrl(dbUrl);
            conf.refresh();
        } catch (DBException e) {
            VKCLogManager.errorLog(this, "", "Refresh of info " + conf.getName() + ", ID=" + conf.getId() + ", db=" + dbUrl + " failed!", e);
        }

        return (ConverterInfo) this.conf;
    }

    /**
     * (non-Javadoc)
     * 
     * @see vkc.server.ServerSupport#process(java.net.Socket)
     */
    protected void process(Socket client) throws Exception {
        Thread p = new Thread(new ConversionProcessor(this, client));
        p.setPriority(Thread.NORM_PRIORITY / 2);
        p.start();
    }

    /**
     * Returns the timing of this ConverterServer
     * 
     * @return
     * @throws DBException
     */
    public ConverterTiming getTiming() throws DBException {
        return ((ConverterInfo) conf).getTiming();
    }

    /**
     * Configures the logger
     * <p>
     * 
     * @param prefix
     * @param logToConsole
     */
    private void configureLogger(String prefix, boolean logToConsole) {
        VKCLogManager.setEnableLogging(true);
        VKCLogManager.setLogToConsole(logToConsole);
        VKCLogManager.init(prefix, ".log");
    }

    /**
     * Configures the converter
     * 
     * @param p
     *            the properties
     */
    private void configureConverter(Properties p) {
        String converterDir = p.getProperty("vkc.converter.cpath");

        if (converterDir == null) {
            ConverterServer.exit("Converter base path ('vkc.converter.cpath') not specified!");
        }

        File cDir = new File(converterDir);

        DocConverter.setBasePath(cDir.getAbsolutePath());

        String office = p.getProperty("vkc.converter.office");

        if (office == null) {
            office = "msoffice";
        }

        DocConverter.setOffice(office);
        VKCLogManager.infoLog(this, "OfficeInstallation ? ==> " + DocConverter.openOffice);
        VKCLogManager.infoLog(this, "DocConverterDir ==> " + cDir.getAbsolutePath());

        if (DocConverter.openOffice) {
            try {
                String quickStart = p.getProperty("vkc.converter.quickstart");

                if (quickStart == null) {
                    ConverterServer.exit("OpenOffice Quickstart Executable ('vkc.converter.quickstart') not specified!");
                }

                String ooserver = p.getProperty("vkc.converter.ooserver");
                String host = "localhost";
                String port = "8100";

                if (ooserver != null) {
                    String[] l = ooserver.split(":");

                    if (l.length < 2) {
                        ConverterServer.exit("Open Office Server ('vkc.converter.ooserver') not specified correctly!\n" + "Must be of the form <hostname:port>");
                    }

                    host = l[0];
                    port = l[1];
                } else {
                    VKCLogManager.infoLog(this, "Open Office Server ('vkc.converter.ooserver') not specified!\n" + "Using standard: localhost:8100");
                }

                SOfficeDocConverter.startOpenOffice(quickStart, host, port);
            } catch (Exception ex) {
                VKCLogManager.errorLog("ConverterServer", "", "OpenOffice is not installed or the given installation path is wrong!", ex);
            }
        }

        String os = p.getProperty("vkc.converter.os");
        
        if (os == null) {
            os = "windows";
        }

        DocConverter.setOs(os);
    }

    /**
     * Prints the usage to stdout
     * 
     */
    private static void exit(String cause) {
        System.out.println("\nIllegal or too few properties: " + cause);
        System.out.println("Please check 'converter.properties' once more");

        System.exit(-1);
    }

    /**
     * Shut down the server
     */
    public void shutdown() {
        if (Status.SERVER_DOWN.equals(conf.getStatus())) {
            return;
        }

        try {
            conf.setStatus(Status.SERVER_DOWN);
            conf.makeStatusPersistent();
        } catch (DBException e) {
            VKCLogManager.errorLog(this, "", "Exception occured while setting serverstatus to 'down'", e);
        }

        lookupDaemon.requestShutdown();

        // Werden noch irgendwelche Jobs ausgeführt
        int jobsCount = this.getConfiguration().getJobsCount();

        if (jobsCount > 0) {
            VKCLogManager.infoLog(this, "Delaying shutdown...jobs in execution = " + jobsCount);
        }

        while (jobsCount > 0) {
            synchronized (lookupDaemon) {
                if (jobsCount == 1 && lookupDaemon.getCurrentConversionStatus() != null) {
                    int convThreshold = Integer.parseInt(properties.getProperty("vkc.converter.servicestop.conversionThresholdInMinutes"));

                    if (lookupDaemon.getCurrentConversionStatus().getStarted().getTime() < System.currentTimeMillis() - convThreshold * 60L * 1000L) {
                        // Konvertierung dauert schon zu lange, das wird ehh nichts mehr! --> abbrechen
                        VKCLogManager.infoLog(this, "Conversion " + lookupDaemon.getCurrentConversionStatus() + " takes longer than " + convThreshold + " minutes.");
                        VKCLogManager.infoLog(this, "---> Will be aborted!");
                        break;
                    }
                }
            }

            try {
                Thread.sleep(10000);
            } catch (InterruptedException e1) {
            }

            jobsCount = this.getConfiguration().getJobsCount();
        }

        try {
            if (server != null) {
                server.close();
            }
        } catch (IOException e) {
        } finally {
            server = null;
        }

        pm.shutdown();

        lookupThread.interrupt();

        if (DocConverter.openOffice) {
            SOfficeDocConverter.stopOpenOffice();
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.roeschter.jsl.Stopable#onServiceStop()
     */
    public void onServiceStop() {
        shutdown();
        VKCLogManager.infoLog(this, this.conf.getName() + " successfully shutdown.");
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.roeschter.jsl.Stopable#timeToWait()
     */
    public int timeToWait() {
        return Integer.parseInt(properties.getProperty("vkc.converter.servicestop.timeToWaitInMinutes")) * 60 * 1000;
    }

    private static final void init() throws LicenseException {
        try {
//            byte[] b = FileUtils.getFileAsByteArray(new File("license.dat"));
//            ByteArrayInputStream bin = new ByteArrayInputStream(Base64.decodeBase64(b));
//            ObjectInputStream ois = new ObjectInputStream(bin);
//            String s = (String) ois.readObject();
//            HashMap systemParameters = (HashMap) ObjectCloner.toObject(s);
            WebShare.setPermanentSystemParameters(new HashMap());
//            s = (String) ois.readObject();
//            HashMap tabViews = (HashMap) ObjectCloner.toObject(s);
            WebShare.setPermanentTabViews(new HashMap());
        } catch (Exception e) {
            throw new LicenseException(e+"Licensefile not found or corrupted");
        }
    }

    /**
     * Main entry point
     * 
     * @param argv
     * @throws Exception
     */
    public static void main(String argv[]) throws Exception {
        init();
        Properties p = new Properties();
        // Falls die Properties im Arbeitsverzeichnis sind,
        // werden sie geladen, sonst diejenigen aus dem vkc/converter/server
        // Verzeichnis
        File f = new File(System.getProperty("user.dir")+"\\"+ConverterServer.PROPERTIES_FILE);

        String propFilename = ConverterServer.PROPERTIES_FILE;

        if (f.exists()) {
            propFilename = "/" + propFilename;
        }

        p.load(ConverterServer.class.getResourceAsStream(propFilename));

        ConverterServer server = new ConverterServer(p);
        
        System.setProperty("service.stop.port", p.getProperty("vkc.converter.stopport", "8375"));
        ServiceStopper.stop(server);
        new Thread(server).start();
    }
}