com.twinsoft.convertigo.engine.EnginePropertiesManager.java Source code

Java tutorial

Introduction

Here is the source code for com.twinsoft.convertigo.engine.EnginePropertiesManager.java

Source

/*
 * Copyright (c) 2001-2011 Convertigo SA.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 *
 * $URL$
 * $Author$
 * $Revision$
 * $Date$
 */

package com.twinsoft.convertigo.engine;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.helpers.OptionConverter;

import com.twinsoft.convertigo.engine.AuthenticatedSessionManager.Role;
import com.twinsoft.convertigo.engine.MinificationManager.MinificationOptions;
import com.twinsoft.convertigo.engine.events.PropertyChangeEvent;
import com.twinsoft.convertigo.engine.events.PropertyChangeEventListener;
import com.twinsoft.convertigo.engine.util.Crypto2;
import com.twinsoft.convertigo.engine.util.GenericUtils;

public class EnginePropertiesManager {
    enum Visibility {
        VISIBLE, HIDDEN, HIDDEN_CLOUD
    };

    @Retention(RetentionPolicy.RUNTIME)
    @interface CategoryOptions {
        Visibility visibility() default Visibility.VISIBLE;

        Role[] viewRoles() default {};

        Role[] configRoles() default {};
    }

    @Retention(RetentionPolicy.RUNTIME)
    @interface PropertyOptions {
        Visibility visibility() default Visibility.VISIBLE;

        boolean advance() default false;

        Class<? extends ComboEnum> combo() default EmptyCombo.class;

        PropertyType propertyType() default PropertyType.Text;

        boolean ciphered() default false;
    }

    /**
     * The propertEnginePropertiesManagerr the Convertigo engine.
     */
    public static final String PROPERTIES_FILE_NAME = "/engine.properties";

    public static final String SYSTEM_PROP_PREFIX = "convertigo.engine.";

    public interface ComboEnum {
        String getDisplay();

        String getValue();
    }

    public enum EmptyCombo implements ComboEnum {
        ;

        public String getDisplay() {
            return null;
        }

        public String getValue() {
            return null;
        }
    }

    public enum LogLevels implements ComboEnum {
        INHERITED("", "Inherited from root logger"), FATAL, ERROR, WARN, INFO, DEBUG, TRACE;

        final String display;
        final String value;

        LogLevels(String value, String display) {
            this.display = display;
            this.value = value;
        }

        LogLevels() {
            this.display = name();
            this.value = name();
        }

        public String getDisplay() {
            return display;
        }

        public String getValue() {
            return value;
        }
    }

    public enum XsltEngine implements ComboEnum {
        xalan_xslt("xalan/xslt", "Java Xalan"), xalan_xsltc("xalan/xsltc", "Java Xalan (XSLTC)");

        final String display;
        final String value;

        XsltEngine(String value, String display) {
            this.display = display;
            this.value = value;
        }

        XsltEngine(String display) {
            this.display = display;
            this.value = name();
        }

        public String getDisplay() {
            return display;
        }

        public String getValue() {
            return value;
        }
    }

    public enum ProxyMode implements ComboEnum {
        off("disabled"), auto("automatic"), manual("manual");

        final String display;
        final String value;

        ProxyMode(String display) {
            this.display = display;
            this.value = name();
        }

        public String getDisplay() {
            return display;
        }

        public String getValue() {
            return value;
        }
    }

    public enum ProxyMethod implements ComboEnum {
        anonymous("anonymous"), basic("basic"), ntlm("NTLM");

        final String display;
        final String value;

        ProxyMethod(String display) {
            this.display = display;
            this.value = name();
        }

        public String getDisplay() {
            return display;
        }

        public String getValue() {
            return value;
        }
    }

    public enum SecurityTokenMode implements ComboEnum {
        memory("memory"), database("database");

        final String display;
        final String value;

        SecurityTokenMode(String display) {
            this.display = display;
            this.value = name();
        }

        public String getDisplay() {
            return display;
        }

        public String getValue() {
            return value;
        }
    }

    public enum PropertyType {
        Text, PasswordHash, PasswordPlain, Boolean, Combo, Array
    };

    public enum PropertyCategory {
        Main("Main parameters"), Account("Accounts"), @CategoryOptions(viewRoles = { Role.LOGS_VIEW,
                Role.LOGS_CONFIG }, configRoles = { Role.LOGS_CONFIG })
        Logs("Logs"), @CategoryOptions(viewRoles = { Role.LOGS_VIEW, Role.LOGS_CONFIG }, configRoles = {
                Role.LOGS_CONFIG })
        Context("Real-time activity monitoring"), XmlGeneration("XML generation"), XulRunner(
                "HTML parser"), HttpClient("HTTP client"), Network("Network"), Proxy("Proxy"), SecurityToken(
                        "Security token"), @CategoryOptions(viewRoles = { Role.CERTIFICATE_VIEW,
                                Role.CERTIFICATE_CONFIG }, configRoles = { Role.CERTIFICATE_CONFIG })
        Ssl("SSL"), @CategoryOptions(viewRoles = { Role.CACHE_VIEW, Role.CACHE_CONFIG }, configRoles = {
                Role.CACHE_CONFIG })
        Cache("Cache"), @CategoryOptions(visibility = Visibility.HIDDEN_CLOUD)
        Carioca("Legacy Carioca portal"), @CategoryOptions(visibility = Visibility.HIDDEN_CLOUD)
        Analytics("Analytics"), Notifications("Notifications"), Minification("Minification"), MobileBuilder(
                "Mobile builder"), FullSync("Full sync"), @CategoryOptions(visibility = Visibility.HIDDEN)
        ExternalBrowser("External browser");

        final String displayName;

        PropertyCategory(String displayName) {
            this.displayName = displayName;
        }

        public String getDisplayName() {
            return displayName;
        }

        public boolean isVisible() {
            CategoryOptions categoryOptions = GenericUtils.getAnnotation(CategoryOptions.class, this);
            if (categoryOptions != null) {
                Visibility visibility = categoryOptions.visibility();
                if (Engine.isCloudMode()) {
                    return visibility == Visibility.VISIBLE;
                } else {
                    return visibility == Visibility.VISIBLE || visibility == Visibility.HIDDEN_CLOUD;
                }
            }
            return true;
        }

        public Role[] viewRoles() {
            CategoryOptions categoryOptions = GenericUtils.getAnnotation(CategoryOptions.class, this);
            if (categoryOptions != null) {
                return categoryOptions.viewRoles();
            }
            return null;
        }

        public Role[] configRoles() {
            CategoryOptions categoryOptions = GenericUtils.getAnnotation(CategoryOptions.class, this);
            if (categoryOptions != null) {
                return categoryOptions.configRoles();
            }
            return null;
        }

        public static PropertyCategory[] getSortedValues() {
            PropertyCategory[] properties = values();
            Arrays.sort(properties, PropertyByNameComparator.INSTANCE);
            return properties;
        }

        private static class PropertyByNameComparator implements Comparator<PropertyCategory> {

            public static final Comparator<PropertyCategory> INSTANCE = new PropertyByNameComparator();

            public int compare(PropertyCategory enum1, PropertyCategory enum2) {
                if (enum1.equals(Main)) {
                    return -1;
                }
                if (enum2.equals(Main)) {
                    return 1;
                }
                return enum1.getDisplayName().compareTo(enum2.getDisplayName());
            }

        }
    };

    @PropertyOptions
    public enum PropertyName {
        /** MAIN */
        @PropertyOptions(visibility = Visibility.HIDDEN_CLOUD)
        APPLICATION_SERVER_CONVERTIGO_URL("application_server.convertigo.url", "http://localhost:18080/convertigo",
                "Convertigo Server application URL",
                PropertyCategory.Main), @PropertyOptions(visibility = Visibility.HIDDEN)
        APPLICATION_SERVER_MASHUP_URL("application_server.mashup.url", "http://localhost:18080/convertigo",
                "Mashup composer server base URL", PropertyCategory.Main), DOCUMENT_THREADING_MAX_WORKER_THREADS(
                        "document.threading.max_worker_threads", "100", "Maximum number of worker threads",
                        PropertyCategory.Main), CONVERTIGO_MAX_CONTEXTS("convertigo.max_context", "750",
                                "Maximum number of contexts", PropertyCategory.Main),

        /** MAIN ADVANCE */
        @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        CONVERTIGO_PRODUCT_VERSION_CHECK("convertigo.product_version_check", "true", "Product version check",
                PropertyCategory.Main), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        DOCUMENT_THREADING_USE_STOP_METHOD("document.threading.use_stop_method", "false",
                "Use the Java Thread.stop() method in order to finish threads",
                PropertyCategory.Main), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean, visibility = Visibility.HIDDEN_CLOUD)
        LINUX_LAUNCH_XVNC("linux.xvnc.launch", "true",
                "(Linux only) Launch Xvnc server using DISPLAY environment variable at startup",
                PropertyCategory.Main), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN_CLOUD)
        LINUX_XVNC_DEPTH("linux.xvnc.depth", "16", "(Linux only) Depth parameter for the Xvnc, default is 16",
                PropertyCategory.Main), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN_CLOUD)
        LINUX_XVNC_GEOMETRY("linux.xvnc.geometry", "320x240",
                "(Linux only) Geometry parameter for Xvnc, default is 320x240",
                PropertyCategory.Main), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean, visibility = Visibility.HIDDEN)
        MIGRATION_3_0_0("migration.3.0.0", "false", "Migration 3.0.0",
                PropertyCategory.Main), @PropertyOptions(advance = true)
        POOL_MANAGER_TIMEOUT("pool.manager.timeout", "-1",
                "Time allowed for pool management task in seconds (-1 for disable)",
                PropertyCategory.Main), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean, visibility = Visibility.HIDDEN_CLOUD)
        PROJECTS_DATA_COMPATIBILITY_MODE("projects_data.compatibility_mode", "false",
                "Enable the compatibility mode for projects data (required for JSP usage); engine restart required",
                PropertyCategory.Main), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        SEQUENCE_STEPS_USE_SAME_JSESSION("sequence.steps.use_same_jsession", "true",
                "Use same JSESSIONID for sequences and steps",
                PropertyCategory.Main), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        SOAP_REQUEST_ADD_XML_ENCODING_CHARSET("soap.request.add_xml_encoding_charset", "false",
                "Add XML encoding charset for SOAP requests",
                PropertyCategory.Main), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        THROW_HTTP_500("throw_http_500", "false", "Throw HTTP 500 in case of unrecoverable servlet error",
                PropertyCategory.Main), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        HIDING_ERROR_INFORMATION("hiding_error_information", "false",
                "Hide detailed information in case of unrecoverable servlet error",
                PropertyCategory.Main), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        THROW_HTTP_500_SOAP_FAULT("throw_http_500.soap_fault", "true", "Throw HTTP 500 in case of SOAP fault",
                PropertyCategory.Main), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean, visibility = Visibility.HIDDEN)
        UPDATE_STEPS("update.steps", "false", "Update steps",
                PropertyCategory.Main), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN)
        CRYPTO_PASSPHRASE("crypto.passphrase", "A8dkLmsdfkKze0e34FGh", "Cryptographic services passphrase",
                PropertyCategory.Main),

        /** ACCOUNTS */
        ADMIN_USERNAME("admin.username", "admin", "Admin username",
                PropertyCategory.Account), @PropertyOptions(propertyType = PropertyType.PasswordHash)
        ADMIN_PASSWORD("admin.password", "" + "admin".hashCode(), "Admin password",
                PropertyCategory.Account), TEST_PLATFORM_USERNAME("testplatform.username", "",
                        "Test Platform username (leave it blank for anonymous access)",
                        PropertyCategory.Account), @PropertyOptions(propertyType = PropertyType.PasswordHash)
        TEST_PLATFORM_PASSWORD("testplatform.password", "" + "".hashCode(), "Test Platform password",
                PropertyCategory.Account),

        /** LOGS */
        LOG4J_LOGGER_CEMS("log4j.logger.cems", LogLevels.INFO.getValue() + ", CemsAppender", "Log4J root logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_ADMIN("log4j.logger.cems.Admin", LogLevels.WARN.getValue(), "Log4J admin logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Text)
        LOG4J_LOGGER_CEMS_CONTEXT_AUDIT("log4j.logger.cems.Context.Audit",
                LogLevels.INFO.getValue() + ", AuditAppender", "Log4J audit context logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_BEANS("log4j.logger.cems.Beans", LogLevels.INHERITED.getValue(), "Log4J beans logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_BILLERS("log4j.logger.cems.Billers", LogLevels.WARN.getValue(), "Log4J billers logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_CACHEMANAGER("log4j.logger.cems.CacheManager", LogLevels.WARN.getValue(),
                "Log4J cache manager logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_CERTIFICATEMANAGER("log4j.logger.cems.CertificateManager", LogLevels.WARN.getValue(),
                "Log4J certificate manager logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_CONTEXT("log4j.logger.cems.Context", LogLevels.INHERITED.getValue(),
                "Log4J context logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_CONTEXTMANAGER("log4j.logger.cems.ContextManager", LogLevels.WARN.getValue(),
                "Log4J context manager logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_CDBMANAGER("log4j.logger.cems.CouchDbManager", LogLevels.WARN.getValue(),
                "Log4J couch DB manager output logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_DATABASEOBJECTMANAGER("log4j.logger.cems.DatabaseObjectManager",
                LogLevels.WARN.getValue(), "Log4J database objects manager logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_DEVICES("log4j.logger.cems.Devices", LogLevels.INFO.getValue(),
                "Log4J devices output logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_EMULATORS("log4j.logger.cems.Emulators", LogLevels.INFO.getValue(),
                "Log4J emulators output logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_ENGINE("log4j.logger.cems.Engine", LogLevels.INHERITED.getValue(), "Log4J engine logger",
                PropertyCategory.Logs),
        /** #3437 : Disabled ExternalBrowser feature because it's not terminated
        @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_EXTERNALBROWSER ("log4j.logger.cems.ExternalBrowser", LogLevels.INFO.getValue(), "Log4J external browser output logger", PropertyCategory.Logs),
        */
        @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_JOBMANAGER("log4j.logger.cems.JobManager", LogLevels.WARN.getValue(),
                "Log4J job manager logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_PROXYMANAGER("log4j.logger.cems.ProxyManager", LogLevels.INFO.getValue(),
                "Log4J proxy manager logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_SCHEDULER("log4j.logger.cems.Scheduler", LogLevels.INFO.getValue(),
                "Log4J scheduler output logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_SECURITYTOKENMANAGER("log4j.logger.cems.SecurityTokenManager", LogLevels.INFO.getValue(),
                "Log4J security token manager output logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_SITECLIPPER("log4j.logger.cems.SiteClipper", LogLevels.INFO.getValue(),
                "Log4J site clipper output logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_STATISTICS("log4j.logger.cems.Statistics", LogLevels.WARN.getValue(),
                "Log4J statistics logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_TRACEPLAYERMANAGER("log4j.logger.cems.TracePlayerManager", LogLevels.WARN.getValue(),
                "Log4J trace player manager logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_USAGEMONITOR("log4j.logger.cems.UsageMonitor", LogLevels.WARN.getValue(),
                "Log4J usage monitor logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_CONTEXT_USER("log4j.logger.cems.Context.User", LogLevels.INHERITED.getValue(),
                "Log4J user context logger",
                PropertyCategory.Logs), @PropertyOptions(propertyType = PropertyType.Combo, combo = LogLevels.class)
        LOG4J_LOGGER_CEMS_USER("log4j.logger.cems.User", LogLevels.INFO.getValue(), "Log4J user output logger",
                PropertyCategory.Logs),

        /** LOGS ADVANCE */
        @PropertyOptions(advance = true, visibility = Visibility.HIDDEN)
        LOG_EXPLICIT_VARIABLES("log.explicit_variables",
                "contextid,project,sequence,connector,transaction,user,clientip,clienthostname",
                "Explicit variables", PropertyCategory.Logs),
        // New appender for the Audit logger.
        LOG4J_APPENDER_AUDITAPPENDER("log4j.appender.AuditAppender", "org.apache.log4j.RollingFileAppender",
                "Log4J audit appender",
                PropertyCategory.Logs), @PropertyOptions(visibility = Visibility.HIDDEN_CLOUD)
        LOG4J_APPENDER_AUDITAPPENDER_FILE("log4j.appender.AuditAppender.File", "${log.directory}/audit.log",
                "Log4J audit appender file",
                PropertyCategory.Logs), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN_CLOUD)
        LOG4J_APPENDER_AUDITAPPENDER_LAYOUT("log4j.appender.AuditAppender.layout", "org.apache.log4j.PatternLayout",
                "Log4J audit appender layout",
                PropertyCategory.Logs), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN)
        LOG4J_APPENDER_AUDITAPPENDER_LAYOUT_CONVERSIONPATTERN(
                "log4j.appender.AuditAppender.layout.ConversionPattern", "!%c{1} | %d | %-5p | %m%n",
                "Log4J audit appender layout conversion pattern",
                PropertyCategory.Logs), @PropertyOptions(advance = true)
        LOG4J_APPENDER_AUDITAPPENDER_MAXBACKUPINDEX("log4j.appender.AuditAppender.MaxBackupIndex", "100",
                "Log4J audit appender max backup index", PropertyCategory.Logs), @PropertyOptions(advance = true)
        LOG4J_APPENDER_AUDITAPPENDER_MAXFILESIZE("log4j.appender.AuditAppender.MaxFileSize", "10MB",
                "Log4J audit appender max file size",
                PropertyCategory.Logs), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN_CLOUD)
        LOG4J_APPENDER_CEMSAPPENDER("log4j.appender.CemsAppender", "org.apache.log4j.RollingFileAppender",
                "Log4J default appender", PropertyCategory.Logs), @PropertyOptions(advance = true)
        LOG4J_APPENDER_CEMSAPPENDER_ENCODING("log4j.appender.CemsAppender.Encoding", "UTF-8",
                "Log4J default appender encoding (requires JVM restart)",
                PropertyCategory.Logs), @PropertyOptions(visibility = Visibility.HIDDEN_CLOUD)
        LOG4J_APPENDER_CEMSAPPENDER_FILE("log4j.appender.CemsAppender.File", "${log.directory}/engine.log",
                "Log4J default appender file",
                PropertyCategory.Logs), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN_CLOUD)
        LOG4J_APPENDER_CEMSAPPENDER_LAYOUT("log4j.appender.CemsAppender.layout", "org.apache.log4j.PatternLayout",
                "Log4J default appender layout",
                PropertyCategory.Logs), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN)
        LOG4J_APPENDER_CEMSAPPENDER_LAYOUT_CONVERSIONPATTERN("log4j.appender.CemsAppender.layout.ConversionPattern",
                "!%-28c{1} | %d | %-5p | %-32t | %X{ContextualParameters}%m%n",
                "Log4J default appender layout conversion pattern",
                PropertyCategory.Logs), @PropertyOptions(advance = true)
        LOG4J_APPENDER_CEMSAPPENDER_MAXBACKUPINDEX("log4j.appender.CemsAppender.MaxBackupIndex", "100",
                "Log4J default appender max backup index", PropertyCategory.Logs), @PropertyOptions(advance = true)
        LOG4J_APPENDER_CEMSAPPENDER_MAXFILESIZE("log4j.appender.CemsAppender.MaxFileSize", "10MB",
                "Log4J default appender max file size",
                PropertyCategory.Logs), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        LOG4J_ADDITIVITY_CEMS("log4j.additivity.cems", "false", "Log4J root logger additivity",
                PropertyCategory.Logs), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN)
        LOG_START_OF_LINE_CHARACTER("log.start_of_line_character", "!", "Start-of-line character",
                PropertyCategory.Logs),

        /** NETWORK */
        @PropertyOptions(propertyType = PropertyType.Boolean)
        NET_REVERSE_DNS("net.reverse_dns", "false", "Use DNS reverse search for finding host names",
                PropertyCategory.Network), FILE_UPLOAD_MAX_REQUEST_SIZE("net.upload.max_request_size", "-1",
                        "Maximum allowed size of a complete multipart request (in bytes). Value -1 indicates no limit.",
                        PropertyCategory.Network), FILE_UPLOAD_MAX_FILE_SIZE("net.upload.max_request_size",
                                "10485760", "Maximum allowed size of a single uploaded file (in bytes).",
                                PropertyCategory.Network),

        /** HTTPCLIENT */
        HTTP_CLIENT_MAX_TOTAL_CONNECTIONS("http_client.max_total_connections", "100",
                "Maximal number of HTTP connections (from 1 to 65535)",
                PropertyCategory.HttpClient), HTTP_CLIENT_MAX_CONNECTIONS_PER_HOST(
                        "http_client.max_connections_per_host", "50",
                        "Maximal number of HTTP connections per host (from 1 to 255)", PropertyCategory.HttpClient),

        /** CONNECTORS MONITORING */
        @PropertyOptions(propertyType = PropertyType.Boolean)
        CONNECTORS_MONITORING("connectors.monitoring", "false",
                "Display running connectors in monitor of Legacy connectors",
                PropertyCategory.Context), @PropertyOptions(propertyType = PropertyType.Boolean)
        DOCUMENT_LOG_SCREEN_DUMPS("document.log.screen_dumps", "false",
                "Trace in logs the screen dumps of the running Legacy connectors", PropertyCategory.Context),

        /** XML GENERATION */
        @PropertyOptions(propertyType = PropertyType.Boolean)
        DOCUMENT_INCLUDE_STATISTICS("document.include_statistics", "false",
                "Insert statistics in the generated document", PropertyCategory.XmlGeneration),

        /** XML GENERATION ADVANCE */
        @PropertyOptions(advance = true, propertyType = PropertyType.Combo, combo = XsltEngine.class)
        DOCUMENT_XSLT_ENGINE("document.xslt_engine", XsltEngine.xalan_xsltc.getValue(), "XSLT engine",
                PropertyCategory.XmlGeneration), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        DOCUMENT_NAMESPACE_AWARE("document.namespace.aware", "false", "Set namespace aware",
                PropertyCategory.XmlGeneration), @PropertyOptions(advance = true)
        DOCUMENT_FROMSCHEMA_DEPTH("document.fromschema.depth", "100",
                "Maximum number of elements for XML sample generation based on schema",
                PropertyCategory.XmlGeneration),

        /** PROXY */
        @PropertyOptions(propertyType = PropertyType.Combo, combo = ProxyMode.class)
        PROXY_SETTINGS_MODE("htmlProxy.mode", ProxyMode.off.getValue(), "Proxy mode",
                PropertyCategory.Proxy), PROXY_SETTINGS_PORT("htmlProxy.port", "8080", "Proxy port",
                        PropertyCategory.Proxy), PROXY_SETTINGS_HOST("htmlProxy.host", "localhost", "Proxy host",
                                PropertyCategory.Proxy), PROXY_SETTINGS_BY_PASS_DOMAINS("htmlProxy.bpdomains",
                                        "localhost,127.0.0.1", "Do not apply proxy settings on",
                                        PropertyCategory.Proxy), PROXY_SETTINGS_AUTO("htmlProxy.auto", "",
                                                "Autoconfiguration proxy url",
                                                PropertyCategory.Proxy), @PropertyOptions(propertyType = PropertyType.Combo, combo = ProxyMethod.class)
        PROXY_SETTINGS_METHOD("htmlProxy.method", ProxyMethod.anonymous.getValue(), "Proxy authentication method",
                PropertyCategory.Proxy), PROXY_SETTINGS_USER("htmlProxy.user", "", "Username",
                        PropertyCategory.Proxy), @PropertyOptions(propertyType = PropertyType.PasswordPlain, ciphered = true)
        PROXY_SETTINGS_PASSWORD("htmlProxy.password", "", "Password", PropertyCategory.Proxy),

        /** XULRUNNER */
        XULRUNNER_MAX_CONNECTIONS("xulrunner.max-connections", "65535", "Max connections (from 1 to 65535)",
                PropertyCategory.XulRunner), XULRUNNER_MAX_CONNECTIONS_PER_SERVER(
                        "xulrunner.max-connections-per-server", "255", "Max connections per server (from 1 to 255)",
                        PropertyCategory.XulRunner), XULRUNNER_MAX_PERSISTENT_CONNECTIONS_PER_SERVER(
                                "xulrunner.max-persistent-connections-per-server", "10",
                                "Max persistent connections per server (from 1 to 10)",
                                PropertyCategory.XulRunner), XULRUNNER_USERAGENT("xulrunner.useragent", "",
                                        "Override User-Agent", PropertyCategory.XulRunner),

        /** XULRUNNER ADVANCE */
        @PropertyOptions(advance = true)
        XULRUNNER_ACCEPT_LANGUAGES("xulrunner.accept_languages", "", "Override Accept-Language header",
                PropertyCategory.XulRunner), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        XULRUNNER_ALLOW_IMAGE("xulrunner.allow.image", "true", "Allow image",
                PropertyCategory.XulRunner), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        XULRUNNER_ALLOW_PLUGIN("xulrunner.allow.plugin", "false", "Allow plugin",
                PropertyCategory.XulRunner), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        XULRUNNER_CHECK_CACHE("xulrunner.check-cache", "true",
                "Check cache validity (false could increase latency)",
                PropertyCategory.XulRunner), @PropertyOptions(advance = true, propertyType = PropertyType.Boolean)
        XULRUNNER_INTERRUPT_PARSING("xulrunner.interrupt-parsing", "false",
                "Enable screen rendering during parse (should be disabled)",
                PropertyCategory.XulRunner), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN_CLOUD)
        XULRUNNER_URL("xulrunner.url", "${convertigo.webapp_path}/WEB-INF/xulrunner", "XulRunner path",
                PropertyCategory.XulRunner), @PropertyOptions(advance = true, visibility = Visibility.HIDDEN_CLOUD)
        XULRUNNER_WORK("xulrunner.work", "${user.workspace}/xulrunner-work", "XulRunner work directory",
                PropertyCategory.XulRunner),

        /** SECURITY TOKEN */
        SECURITY_TOKEN_LIFE_TIME("security_token.life_time", "20", "Security token lifetime (in seconds)",
                PropertyCategory.SecurityToken), @PropertyOptions(propertyType = PropertyType.PasswordHash)
        SECURITY_TOKEN_PASSWORD("security_token.password", "" + "c8o-password".hashCode(),
                "Security token generator password",
                PropertyCategory.SecurityToken), @PropertyOptions(advance = true, propertyType = PropertyType.Combo, combo = SecurityTokenMode.class)
        SECURITY_TOKEN_MODE("security_token.mode", SecurityTokenMode.memory.getValue(), "Storage mode",
                PropertyCategory.SecurityToken), @PropertyOptions(advance = true)
        SECURITY_TOKEN_PERSISTENCE_DIALECT("security_token.persistence.dialect",
                "org.hibernate.dialect.MySQL5InnoDBDialect", "SQL Dialect",
                PropertyCategory.SecurityToken), @PropertyOptions(advance = true)
        SECURITY_TOKEN_PERSISTENCE_JDBC_DRIVER("security_token.persistence.jdbc.driver", "org.mariadb.jdbc.Driver",
                "JDBC driver", PropertyCategory.SecurityToken), @PropertyOptions(advance = true)
        SECURITY_TOKEN_PERSISTENCE_JDBC_URL("security_token.persistence.jdbc.url",
                "jdbc:mysql://localhost:3306/c8oSecurityToken", "JDBC URL",
                PropertyCategory.SecurityToken), @PropertyOptions(advance = true)
        SECURITY_TOKEN_PERSISTENCE_JDBC_USERNAME("security_token.persistence.jdbc.username", "", "JDBC username",
                PropertyCategory.SecurityToken), @PropertyOptions(advance = true, propertyType = PropertyType.PasswordPlain, ciphered = true)
        SECURITY_TOKEN_PERSISTENCE_JDBC_PASSWORD("security_token.persistence.jdbc.password", "", "JDBC password",
                PropertyCategory.SecurityToken), @PropertyOptions(advance = true)
        SECURITY_TOKEN_PERSISTENCE_MAX_RETRY("security_token.persistence.jdbc.maxretry", "4",
                "JDBC max retry on connection failed", PropertyCategory.SecurityToken),

        /** SSL */
        @PropertyOptions(propertyType = PropertyType.Boolean)
        SSL_DEBUG("ssl.debug", "false",
                "SSL debug output (requires JVM restart); only available for HTTP connectors (i.e. NOT useful for HTML connectors)",
                PropertyCategory.Ssl), @PropertyOptions(propertyType = PropertyType.Array)
        SSL_ISSUERS("ssl.issuers", "", "SSL issuers", PropertyCategory.Ssl),

        /** CACHE */
        @PropertyOptions(visibility = Visibility.HIDDEN_CLOUD)
        CACHE_MANAGER_CLASS("cache_manager.class", "com.twinsoft.convertigo.engine.cache.FileCacheManager",
                "Cache manager class",
                PropertyCategory.Cache), @PropertyOptions(visibility = Visibility.HIDDEN_CLOUD)
        CACHE_MANAGER_FILECACHE_DIRECTORY("cache_manager.filecache.directory", "${user.workspace}/cache",
                "File cache directory", PropertyCategory.Cache), CACHE_MANAGER_SCAN_DELAY(
                        "cache_manager.scan_delay", "60", "Cache scan delay (in seconds)",
                        PropertyCategory.Cache), @PropertyOptions(advance = false, propertyType = PropertyType.Boolean)
        DISABLE_CACHE("disable.cache", "false", "Disable Cache", PropertyCategory.Cache),

        /** CARIOCA */
        CARIOCA_DEFAULT_USER_NAME("carioca.default.user.name", "admin", "Default user name",
                PropertyCategory.Carioca), CARIOCA_DEFAULT_USER_PASSWORD("carioca.default.user.password", "admin",
                        "Default user password", PropertyCategory.Carioca), CARIOCA_SESSION_KEY_LIFE_TIME(
                                "carioca.session_key.life_time", "60", "Default session key life time (in seconds)",
                                PropertyCategory.Carioca), CARIOCA_URL("carioca.url", "${user.workspace}/minime",
                                        "Carioca access URL", PropertyCategory.Carioca),

        /** ANALYTICS */
        @PropertyOptions(propertyType = PropertyType.Boolean)
        ANALYTICS_PERSISTENCE_ENABLED("billing.enabled", "false", "Enable persistence analytics (JDBC)",
                PropertyCategory.Analytics), @PropertyOptions(propertyType = PropertyType.Boolean)
        ANALYTICS_GOOGLE_ENABLED("billing.google.enabled", "false", "Enable google analytics",
                PropertyCategory.Analytics),

        /** ANALYTICS ADVANCE */
        @PropertyOptions(advance = true)
        ANALYTICS_PERSISTENCE_DIALECT("billing.persistence.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect",
                "Persistence SQL Dialect", PropertyCategory.Analytics), @PropertyOptions(advance = true)
        ANALYTICS_PERSISTENCE_JDBC_DRIVER("billing.persistence.jdbc.driver", "org.mariadb.jdbc.Driver",
                "Persistence JDBC driver",
                PropertyCategory.Analytics), @PropertyOptions(advance = true, propertyType = PropertyType.PasswordPlain, ciphered = true)
        ANALYTICS_PERSISTENCE_JDBC_PASSWORD("billing.persistence.jdbc.password", "", "Persistence JDBC password",
                PropertyCategory.Analytics), @PropertyOptions(advance = true)
        ANALYTICS_PERSISTENCE_JDBC_URL("billing.persistence.jdbc.url", "jdbc:mysql://localhost:3306/c8oAnalytics",
                "Persistence JDBC URL", PropertyCategory.Analytics), @PropertyOptions(advance = true)
        ANALYTICS_PERSISTENCE_JDBC_USERNAME("billing.persistence.jdbc.username", "", "Persistence JDBC username",
                PropertyCategory.Analytics), @PropertyOptions(advance = true)
        ANALYTICS_PERSISTENCE_MAX_RETRY("billing.persistence.jdbc.maxretry", "2",
                "JDBC max retry on connection failed", PropertyCategory.Analytics), @PropertyOptions(advance = true)
        ANALYTICS_GOOGLE_ID("billing.google.analytics.id", "", "Google Analytics ID", PropertyCategory.Analytics),

        /** NOTIFICATIONS */
        @PropertyOptions(propertyType = PropertyType.Boolean)
        NOTIFICATIONS_NOTIFY_PROJECT_DEPLOYMENT("notifications.notify.project_deployment", "false",
                "Notify project deployment", PropertyCategory.Notifications), @PropertyOptions(advance = true)
        NOTIFICATIONS_TARGET_EMAIL("notifications.target_email", "", "Target email",
                PropertyCategory.Notifications), @PropertyOptions(advance = true)
        NOTIFICATIONS_SMTP_HOST("notifications.smtp.host", "", "STMP host",
                PropertyCategory.Notifications), @PropertyOptions(advance = true)
        NOTIFICATIONS_SMTP_PORT("notifications.smtp.port", "465", "STMP port",
                PropertyCategory.Notifications), @PropertyOptions(advance = true)
        NOTIFICATIONS_SMTP_USER("notifications.smtp.user", "", "STMP user",
                PropertyCategory.Notifications), @PropertyOptions(advance = true, propertyType = PropertyType.PasswordPlain, ciphered = true)
        NOTIFICATIONS_SMTP_PASSWORD("notifications.smtp.password", "", "STMP password",
                PropertyCategory.Notifications),

        /** MINIFICATION */
        @PropertyOptions(propertyType = PropertyType.Combo, combo = MinificationOptions.class)
        MINIFICATION_LEVEL("minification.level", MinificationOptions.strong.name(), "Common minification level",
                PropertyCategory.Minification), @PropertyOptions(propertyType = PropertyType.Boolean)
        MINIFICATION_STATS("minification.stats", "true", "Show statistics",
                PropertyCategory.Minification), @PropertyOptions(propertyType = PropertyType.Boolean)
        MINIFICATION_FILENAMES("minification.filenames", "true", "Show filenames", PropertyCategory.Minification),

        /** MOBILE BUILDER */
        @PropertyOptions(propertyType = PropertyType.PasswordPlain, ciphered = true)
        MOBILE_BUILDER_AUTHENTICATION_TOKEN("mobile.builder.auth_token", "", "Mobile builder authentication token",
                PropertyCategory.MobileBuilder),

        //ANDROID
        MOBILE_BUILDER_ANDROID_CERTIFICATE_TITLE("mobile.builder.android_certificate_title", "",
                "Android certificate title",
                PropertyCategory.MobileBuilder), @PropertyOptions(propertyType = PropertyType.PasswordPlain, ciphered = true)
        MOBILE_BUILDER_ANDROID_CERTIFICATE_PW("mobile.builder.android_certificate_pw", "",
                "Android certificate password",
                PropertyCategory.MobileBuilder), @PropertyOptions(propertyType = PropertyType.PasswordPlain, ciphered = true)
        MOBILE_BUILDER_ANDROID_KEYSTORE_PW("mobile.builder.android_keystore_pw", "", "Android keyStore password",
                PropertyCategory.MobileBuilder),

        //iOS
        MOBILE_BUILDER_IOS_CERTIFICATE_TITLE("mobile.builder.ios_certificate_title", "", "iOS certificate title",
                PropertyCategory.MobileBuilder), @PropertyOptions(propertyType = PropertyType.PasswordPlain, ciphered = true)
        MOBILE_BUILDER_IOS_CERTIFICATE_PW("mobile.builder.ios_certificate_pw", "", "iOS certificate password",
                PropertyCategory.MobileBuilder),

        //WINDOWSPHONE
        MOBILE_BUILDER_WINDOWSPHONE_PUBLISHER_ID_TITLE("mobile.builder.windows_phone_publisher_id_title", "",
                "Windows Phone publisher ID title", PropertyCategory.MobileBuilder),

        @PropertyOptions(advance = true)
        MOBILE_BUILDER_PLATFORM_URL("mobile.builder.platform_url",
                "https://build.convertigo.net/cmb/PhoneGapBuilder", "Mobile builder platform URL",
                PropertyCategory.MobileBuilder),

        /** FULL SYNC */
        FULLSYNC_COUCH_URL("fullsync.couch.url", "http://127.0.0.1:5984", "Couch DB URL for FullSync",
                PropertyCategory.FullSync), FULLSYNC_COUCH_USERNAME("fullsync.couch.username", "",
                        "Couch DB username for FullSync",
                        PropertyCategory.FullSync), @PropertyOptions(propertyType = PropertyType.PasswordPlain, ciphered = true)
        FULLSYNC_COUCH_PASSWORD("fullsync.couch.password", "", "Couch DB password for FullSync",
                PropertyCategory.FullSync),

        /** EXTERNAL BROWSER */
        @PropertyOptions(propertyType = PropertyType.Array)
        EBA_LIST("externalbrowser.agent.list", "", "External Browser Agent list",
                PropertyCategory.ExternalBrowser), @PropertyOptions(propertyType = PropertyType.Array)
        EBM_POOL("externalbrowser.manager.pool", "", "External Browser Manager pool",
                PropertyCategory.ExternalBrowser),

        /* End of configuration keys definition */;

        final String key;
        final String defaultValue;
        final String description;
        final PropertyCategory category;

        PropertyName(String key, String defaultValue, String description, PropertyCategory category) {
            this.key = key;
            this.defaultValue = defaultValue;
            this.description = description;
            this.category = category;
        }

        @Override
        public String toString() {
            return key;
        }

        private PropertyOptions getOptions() {
            PropertyOptions options = GenericUtils.getAnnotation(PropertyOptions.class, this);
            if (options == null) {
                options = getClass().getAnnotation(PropertyOptions.class);
            }
            return options;
        }

        public String getKey() {
            return key;
        }

        public String getDefaultValue() {
            return defaultValue;
        }

        public String getDescription() {
            return description;
        }

        public PropertyCategory getCategory() {
            return category;
        }

        public boolean isAdvance() {
            return getOptions().advance();
        }

        public boolean isCiphered() {
            return getOptions().ciphered();
        }

        public PropertyType getType() {
            return getOptions().propertyType();
        }

        public ComboEnum[] getCombo() {
            return getOptions().combo().getEnumConstants();
        }

        public boolean isVisible() {
            PropertyOptions propertyOptions = GenericUtils.getAnnotation(PropertyOptions.class, this);
            if (propertyOptions != null) {
                Visibility visibility = propertyOptions.visibility();
                if (Engine.isCloudMode()) {
                    return visibility == Visibility.VISIBLE;
                } else {
                    return visibility == Visibility.VISIBLE || visibility == Visibility.HIDDEN_CLOUD;
                }
            }
            return true;
        }
    };

    private static Properties properties;
    public static Properties system_properties = null;

    /**
     * Store the servlet path, all paths starting with '.' are relative to this path.
     */
    public static String servletPath;

    public static synchronized String getProperty(PropertyName property) {
        return getProperty(property, true);
    }

    public static boolean checkProperty(PropertyName property, String value) {
        String current_value = getProperty(property);
        value = encodeValue(property.getType(), value);
        return current_value.equals(value);
    }

    public static synchronized long getPropertyAsLong(PropertyName property) {
        try {
            return Long.parseLong(getProperty(property, true));
        } catch (Exception e) {
            return Long.parseLong(property.getDefaultValue());
        }
    }

    public static synchronized boolean getPropertyAsBoolean(PropertyName property) {
        return "true".equals(getProperty(property, true));
    }

    public static synchronized String getOriginalProperty(PropertyName property) {
        return getProperty(property, false);
    }

    public static synchronized String getProperty(PropertyName property, boolean bSubstitute) {
        if (property == null) {
            throw new IllegalArgumentException("Null property key");
        }

        if (properties == null) {
            throw new IllegalStateException("Not initialized EnginePropertiesManager");
        }

        String result = system_properties.getProperty(SYSTEM_PROP_PREFIX + property);
        if (result == null) {
            result = properties.getProperty(property.key);
        }

        if (result == null) {
            result = property.getDefaultValue();
        }

        if (result == null) {
            throw new IllegalArgumentException("Unknown property key: " + property);
        }

        // Substitute parameter value if needed
        if (bSubstitute)
            result = OptionConverter.substVars(result, properties);

        // Migration -> 3.1
        if (property.equals(PropertyName.DOCUMENT_XSLT_ENGINE) && (result != null) && result.equals("xalan"))
            result = "xalan/xsltc";

        return result;
    }

    public static synchronized String[] getOriginalPropertyAsStringArray(PropertyName property) {
        return getPropertyAsStringArray(property, false);
    }

    public static synchronized String[] getPropertyAsStringArray(PropertyName property) {
        return getPropertyAsStringArray(property, true);
    }

    public static String[] getPropertyAsStringArray(PropertyName property, boolean bSubstitute) {
        if (property.getType() != PropertyType.Array) {
            throw new IllegalArgumentException("The requested property is not of type Array: " + property);
        }

        String array = getProperty(property, bSubstitute);
        StringTokenizer st = new StringTokenizer(array, ";", false);
        String[] propertyAsStringArray = new String[st.countTokens()];
        int i = 0;
        while (st.hasMoreTokens()) {
            String item = st.nextToken();
            item = item.replaceAll("\\[\\[pv\\]\\]", ";");
            propertyAsStringArray[i] = item;
            i++;
        }

        return propertyAsStringArray;
    }

    public static void setPropertyFromStringArray(PropertyName property, String[] values) {
        if (property.getType() != PropertyType.Array) {
            throw new IllegalArgumentException("The requested property is not of type Array: " + property);
        }

        String propertyAsString = "";
        for (String item : values) {
            item = item.replaceAll(";", "[[pv]]");
            propertyAsString += item + ";";
        }

        setProperty(property, propertyAsString);
    }

    public static void setProperty(PropertyName property, String value) {
        String exvalue;

        if (system_properties.containsKey(SYSTEM_PROP_PREFIX + property.getKey())) {
            exvalue = (String) system_properties.put(SYSTEM_PROP_PREFIX + property.getKey(), value);
        } else {
            if (properties == null) {
                throw new IllegalStateException("Not initialized EnginePropertiesManager");
            }

            value = encodeValue(property.getType(), value);
            exvalue = (String) properties.put(property.getKey(), value);
        }
        if (!value.equals(exvalue) && Engine.isStarted) {
            if (property == PropertyName.CONVERTIGO_PRODUCT_VERSION_CHECK) {
                if ("false".equals(value)) {
                    Engine.logEngine.warn("The product version check will be ignored!");
                } else {
                    Engine.logEngine.info("The product version check will be done!");
                }
            }
            Engine.theApp.eventManager.dispatchEvent(new PropertyChangeEvent(property, value),
                    PropertyChangeEventListener.class);
        }
    }

    public static <E extends ComboEnum> E getPropertyAsEnum(PropertyName property) {
        if (property.getType() != PropertyType.Combo) {
            throw new IllegalArgumentException("The requested property is not of type Combo: " + property);
        }
        String stringValue = getProperty(property);
        try {
            E value = GenericUtils.cast(
                    property.getOptions().combo().getMethod("valueOf", String.class).invoke(null, stringValue));
            return value;
        } catch (Exception e) {
            throw new RuntimeException("Unable to retrieve the Enum value", e);
        }
    }

    public static void copySystemProperties() {
        if (system_properties == null) {
            Properties sys_prop = System.getProperties();
            system_properties = new Properties();
            for (String name : GenericUtils.<Collection<String>>cast(sys_prop.keySet())) {
                if (name.startsWith("convertigo.")) {
                    system_properties.setProperty(name, sys_prop.getProperty(name));
                }
            }
        }
    }

    static {
        copySystemProperties();
    }

    public static synchronized void initProperties() throws EngineException {
        properties = new Properties();
    }

    public static synchronized void loadProperties() throws EngineException {
        loadProperties(true);
    }

    public static synchronized void loadProperties(boolean configureLog4J) throws EngineException {
        if (properties != null)
            return;

        FileInputStream propsInputStream = null;
        String enginePropertiesFile = Engine.CONFIGURATION_PATH + PROPERTIES_FILE_NAME;
        try {
            EnginePropertiesManager.initProperties();

            System.out.println("Loading Convertigo engine properties from " + enginePropertiesFile);

            try {
                propsInputStream = new FileInputStream(enginePropertiesFile);
                properties.load(propsInputStream);
            } catch (FileNotFoundException e) {
                String message = "Unable to find the Convertigo engine configuration file '" + enginePropertiesFile
                        + "'. Creating a new one...";
                if (Engine.logEngine != null)
                    Engine.logEngine.warn(message);
                else
                    System.out.println(message);
            }

            // Decipher the ciphered properties
            for (PropertyName property : PropertyName.values()) {
                String key = property.getKey();

                // Ciphered property?
                if (property.isCiphered() && properties.containsKey(key)) {
                    String value = properties.getProperty(key);

                    String decipheredValue = Crypto2.decodeFromHexString3(value);
                    if (decipheredValue == null) {
                        String message = "Unable to decode value for property '" + key + "'";
                        if (Engine.logEngine != null)
                            Engine.logEngine.warn(message);
                        else
                            System.out.println(message);
                        continue;
                    }

                    properties.setProperty(key, decipheredValue);
                }
            }

            // Add special properties needed for substitution
            properties.put("user.workspace", Engine.USER_WORKSPACE_PATH);
            System.out.println("  Adding user workspace path: " + Engine.USER_WORKSPACE_PATH);

            Engine.LOG_PATH = Engine.USER_WORKSPACE_PATH + "/logs";

            String logDirectory = Engine.LOG_PATH;
            properties.put("log.directory", logDirectory);
            System.out.println("  Adding log directory: " + logDirectory);

            String webappPath = Engine.WEBAPP_PATH;
            properties.put("convertigo.webapp_path", webappPath);
            System.out.println("  Adding webapp path: " + webappPath);

            System.out.println("Properties loaded!");

            if (configureLog4J) {
                configureLog4J();
            }
        } catch (IOException e) {
            properties = null;
            throw new EngineException(
                    "Unable to load the Convertigo engine configuration file '" + PROPERTIES_FILE_NAME + "'.", e);
        } finally {
            if (propsInputStream != null) {
                try {
                    propsInputStream.close();
                } catch (IOException e) {
                    // Silently ignore
                    System.out.println("Unable to close the configuration properties file");
                    e.printStackTrace();
                }
            }
        }
    }

    public static synchronized void saveProperties() throws IOException, EngineException {
        saveProperties(true);
    }

    public static synchronized void saveProperties(boolean configureLog4J) throws IOException, EngineException {
        OutputStream propsOutputStream = null;
        String enginePropertiesPath = Engine.CONFIGURATION_PATH + PROPERTIES_FILE_NAME;
        File enginePropertiesFile = new File(enginePropertiesPath);
        try {
            if (Engine.logEngine == null)
                System.out.println("Saving Convertigo engine properties to " + enginePropertiesPath);
            else
                Engine.logEngine.debug("Saving Convertigo engine properties to " + enginePropertiesPath);

            try {
                FileUtils.copyFile(enginePropertiesFile, new File(enginePropertiesPath + ".bak"));
            } catch (Exception e) {
            }

            enginePropertiesFile.getParentFile().mkdirs();
            propsOutputStream = new FileOutputStream(enginePropertiesFile);

            saveProperties(propsOutputStream, "Convertigo Engine configuration file");

            if (Engine.logEngine == null) {
                System.out.println("Convertigo engine properties saved!");
            } else {
                Engine.logEngine.debug("Convertigo engine properties saved!");
            }

            if (configureLog4J) {
                configureLog4J();
            }
        } catch (IOException e) {
            //properties = null; // Why ??    Part of fix for Ticket #2072
            throw new EngineException(
                    "Unable to save the Convertigo engine configuration file '" + enginePropertiesPath + "'.", e);
        } finally {
            if (propsOutputStream != null) {
                propsOutputStream.flush();
                propsOutputStream.close();
            }
        }
    }

    public static void saveProperties(OutputStream outputStream, String comments)
            throws IOException, EngineException {
        Properties modifiedProperties = new Properties();
        for (PropertyName property : PropertyName.values()) {
            String propertyValue = getOriginalProperty(property);
            if (!property.getDefaultValue().equals(propertyValue)) {
                if (property.isCiphered()) {
                    propertyValue = Crypto2.encodeToHexString(propertyValue);
                }
                modifiedProperties.put(property.getKey(), propertyValue);
            }
        }
        modifiedProperties.store(outputStream, comments);
    }

    public static String getPropertiesAsString(String title, Properties propertiesToGet) {
        if (propertiesToGet == null) {
            if (properties == null) {
                throw new IllegalStateException("Not initialized EnginePropertiesManager");
            }
            propertiesToGet = properties;
        }

        List<String> vProperties = new ArrayList<String>(propertiesToGet.size());
        for (Object propKey : propertiesToGet.keySet()) {
            String propValue = propertiesToGet.getProperty((String) propKey);
            vProperties.add(propKey + "=" + propValue);
        }

        Collections.sort(vProperties);

        String msg = title + "\n";
        for (String line : vProperties) {
            msg += line + "\n";
        }

        return msg;
    }

    public static void configureLog4J() {
        Properties log4jProperties = new Properties();
        for (PropertyName propertyName : PropertyName.values()) {
            String sPropertyName = propertyName.toString();
            if (sPropertyName.startsWith("log4j."))
                log4jProperties.setProperty(sPropertyName, getProperty(propertyName));
        }

        log4jProperties.put("log.directory", Engine.LOG_PATH);

        LogManager.resetConfiguration();
        PropertyConfigurator.configure(log4jProperties);

        if (Engine.logEngine != null) {
            Engine.logEngine.debug(getPropertiesAsString("Log4J properties:", log4jProperties));
        } else {
            System.out.println(getPropertiesAsString("Log4J properties:", log4jProperties));
        }
    }

    public static void unload() {
        properties = null;
    }

    public static void load(ByteArrayInputStream byteArrayInputStream) throws IOException {
        if (properties == null) {
            throw new IllegalStateException("Not initialized EnginePropertiesManager");
        }
        properties.load(byteArrayInputStream);
    }

    private static String encodeValue(PropertyType propertyType, String value) {
        switch (propertyType) {
        case PasswordHash:
            value = "" + value.hashCode();
            break;
        default:
            break;
        }
        return value;
    }
}