/*
 * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 1998, 2020 IBM Corporation and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
//      Gordon Yorke - VM managed entity detachment
//     Eduard Bartsch, SAP - Fix for Bug 351186 - ConcurrentModificationException Exception in PropertiesHandler
//     Rick Curtis - Add support for WebSphere Liberty platform.
//     08/11/2014-2.5 Rick Curtis
//       - 440594: Tolerate invalid NamedQuery at EntityManager creation.
//     09/03/2015 - Will Dazey
//       - 456067 : Added support for defining query timeout units
//     09/28/2015 - Will Dazey
//       - 478331 : Added support for defining local or server as the default locale for obtaining timestamps
//     03/11/2016 - Jody Grassel
//       - 489794 : Add support for WebSphere EJBEmbeddable platform.
//     08/29/2016 Jody Grassel
//       - 500441: Eclipselink core has System.getProperty() calls that are not potentially executed under doPriv()
//     04/11/2018 - Will Dazey
//       - 533148 : Add the eclipselink.jpa.sql-call-deferral property
package org.eclipse.persistence.internal.sessions;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.security.AccessController;
import java.security.PrivilegedAction;

import org.eclipse.persistence.annotations.IdValidation;
import org.eclipse.persistence.config.BatchWriting;
import org.eclipse.persistence.config.CacheType;
import org.eclipse.persistence.config.CommitOrderType;
import org.eclipse.persistence.config.EntityManagerProperties;
import org.eclipse.persistence.config.ExclusiveConnectionMode;
import org.eclipse.persistence.config.FlushClearCache;
import org.eclipse.persistence.config.LoggerType;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.config.ReferenceMode;
import org.eclipse.persistence.config.TargetDatabase;
import org.eclipse.persistence.config.TargetServer;
import org.eclipse.persistence.descriptors.DescriptorQueryManager;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedGetSystemProperty;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;

/**
 *
 * The class processes some of EclipseLink properties.
 * The class may be used for any properties, but it only makes sense
 * to use it in the following two cases:
 *      1. There is a set of legal values defined
 *          either requiring translation (like CacheTypeProp);
 *          or not (like LoggingLevelProp).
 *      2. Property is really a prefix from which the property obtained by
 *      appending entity or class name (like DescriptorCustomizerProp -
 *      it corresponds to "eclipselink.descriptor.customizer." prefix that allows to
 *      define properties like "eclipselink.descriptor.customizer.myPackage.MyClass").
 *
 * EclipseLink properties and their values defined in org.eclipse.persistence.config package.
 *
 * To add a new property:
 *   Define a new property in PersistenceUnitProperties;
 *   Add a class containing property's values if required to config package (like CacheType);
 *      Alternatively values may be already defined elsewhere (like in LoggingLevelProp).
 *   Add an inner class to this class extending Prop corresponding to the new property (like CacheTypeProp);
 *      The first constructor parameter is property name; the second is default value;
 *      In constructor
 *          provide 2-dimensional value array in case the values should be translated (like CacheTypeProp);
 *              in case translation is not required provide a single-dimension array (like LoggingLevelProp).
 *   In inner class Prop static initializer addProp an instance of the new prop class (like addProp(new CacheTypeProp())).
 *
 * @see PersistenceUnitProperties
 * @see CacheType
 * @see TargetDatabase
 * @see TargetServer
 *
 *  05/28/2008-1.0M8 Andrei Ilitchev.
 *     - 224964: Provide support for Proxy Authentication through JPA.
 *     Added support for CONNECTION_EXCLUSIVE. Also added BooleanProp to allow simpler way of creating boolean-valued properties:
 *     instead of defining a new class for each new Boolean property just add a new instance of BooleanProp with property name and default:
 *           addProp(new BooleanProp(PersistenceUnitProperties.CONNECTION_EXCLUSIVE, "false"));
 *     Changed the existing boolean-valued properties to use this approach, also
 *     applied the same approach to LOGGING_LEVEL and CATEGORY_LOGGING_LEVEL_
 *     Also introduced a new version of getSessionPropertyValue that takes properties:
 *         public static String getSessionPropertyValue(String name, Map m, AbstractSession session) {
 *     it's convenient for use in EntityManagerImpl: first searches the passed properties then (recursively) properties of the session, then System properties.
 */
public class PropertiesHandler {

    /**
     * INTERNAL:
     * Gets property value from the map, if none found looks in System properties.
     * Use this to get a value for a non-prefixed property.
     * Could be used on prefixes (like "org.eclipse.persistence.cache-type.") too,
     * but will always return null
     * Throws IllegalArgumentException in case the property value is illegal.
     */
    public static String getPropertyValue(String name, Map m) {
        return getPropertyValue(name, m, true);
    }

    public static String getPropertyValueLogDebug(String name, Map m, AbstractSession session) {
        return getPropertyValueLogDebug(name, m, session, true);
    }

    public static String getPropertyValue(String name, Map m, boolean useSystemAsDefault) {
        return Prop.getPropertyValueToApply(name, m, null, useSystemAsDefault);
    }

    public static String getPropertyValueLogDebug(String name, Map m, AbstractSession session, boolean useSystemAsDefault) {
        return Prop.getPropertyValueToApply(name, m, session, useSystemAsDefault);
    }

    /**
     * INTERNAL:
     * Given property name and value verifies and translates the value.
     * Throws IllegalArgumentException in case the property value is illegal.
     */
    public static String getPropertyValue(String name, String value) {
        return Prop.getPropertyValueToApply(name, value, null);
    }

    public static String getPropertyValueLogDebug(String name, String value, AbstractSession session) {
        return Prop.getPropertyValueToApply(name, value, session);
    }

    /**
     * INTERNAL:
     * Gets property value from the map, if none found looks in System properties.
     * Use this to get a value for a prefixed property:
     * for "org.eclipse.persistence.cache-type.Employee"
     * pass "org.eclipse.persistence.cache-type.", "Employee".
     * Throws IllegalArgumentException in case the property value is illegal.
     */
    public static String getPrefixedPropertyValue(String prefix, String suffix, Map m) {
        return (String)getPrefixValues(prefix, m).get(suffix);
    }

    /**
     * INTERNAL:
     * Gets properties' values from the map, if none found looks in System properties.
     * In the returned map values keyed by suffixes.
     * Use it with prefixes (like "org.eclipse.persistence.cache-type.").
     * Could be used on simple properties (not prefixes, too),
     * but will always return either an empty map or a map containing a single
     * value keyed by an empty String.
     * Throws IllegalArgumentException in case the property value is illegal.
     */
    public static Map getPrefixValues(String prefix, Map m) {
        return Prop.getPrefixValuesToApply(prefix, m, null, true);
    }

    public static Map getPrefixValuesLogDebug(String prefix, Map m, AbstractSession session) {
        return Prop.getPrefixValuesToApply(prefix, m, session, true);
    }

    /**
     * INTERNAL:
     * Returns the default property value that should be applied.
     * Throws IllegalArgumentException in case the name doesn't correspond
     * to any property.
     */
    public static String getDefaultPropertyValue(String name) {
        return Prop.getDefaultPropertyValueToApply(name, null);
    }

    public static String getDefaultPropertyValueLogDebug(String name, AbstractSession session) {
        return Prop.getDefaultPropertyValueToApply(name, session);
    }

    /**
     * INTERNAL:
     * Empty String value indicates that the default property value should be used.
     */
    protected static boolean shouldUseDefault(String value) {
        return value != null &&  value.length() == 0;
    }

    protected static abstract class Prop {
        static HashMap mainMap = new HashMap();
        Object[] valueArray;
        HashMap valueMap;
        String name;
        String defaultValue;
        String defaultValueToApply;
        boolean valueToApplyMayBeNull;
        boolean shouldReturnOriginalValueIfValueToApplyNotFound;

        static {
            addProp(new LoggerTypeProp());
            addProp(new LoggingLevelProp(PersistenceUnitProperties.LOGGING_LEVEL, Level.INFO.getName()));
            addProp(new LoggingLevelProp(PersistenceUnitProperties.CATEGORY_LOGGING_LEVEL_, Level.INFO.getName()));
            addProp(new TargetDatabaseProp());
            addProp(new TargetServerProp());
            addProp(new CacheSizeProp());
            addProp(new CacheTypeProp());
            addProp(new BooleanProp(PersistenceUnitProperties.CACHE_SHARED_, "false"));
            addProp(new DescriptorCustomizerProp());
            addProp(new BatchWritingProp());
            addProp(new FlushClearCacheProp());
            addProp(new ReferenceModeProp());
            addProp(new FlushModeProp());
            addProp(new BooleanProp(PersistenceUnitProperties.PERSISTENCE_CONTEXT_CLOSE_ON_COMMIT, "false"));
            addProp(new BooleanProp(PersistenceUnitProperties.PERSISTENCE_CONTEXT_PERSIST_ON_COMMIT, "true"));
            addProp(new BooleanProp(PersistenceUnitProperties.PERSISTENCE_CONTEXT_COMMIT_WITHOUT_PERSIST_RULES, "false"));
            addProp(new BooleanProp(PersistenceUnitProperties.VALIDATE_EXISTENCE, "false"));
            addProp(new BooleanProp(PersistenceUnitProperties.ORDER_UPDATES, "true"));
            addProp(new CommitOrderProp());
            addProp(new BooleanProp(PersistenceUnitProperties.JOIN_EXISTING_TRANSACTION, "false"));
            addProp(new BooleanProp(PersistenceUnitProperties.COMPOSITE_UNIT, "false"));
            addProp(new BooleanProp(PersistenceUnitProperties.COMPOSITE_UNIT_MEMBER, "false"));
            addProp(new ExclusiveConnectionModeProp());
            addProp(new BooleanProp(PersistenceUnitProperties.EXCLUSIVE_CONNECTION_IS_LAZY, "true"));
            addProp(new IdValidationProp());
            addProp(new ConnectionPoolProp());
            addProp(new BooleanProp(PersistenceUnitProperties.JDBC_RESULT_SET_ACCESS_OPTIMIZATION, Boolean.toString(ObjectLevelReadQuery.isResultSetAccessOptimizedQueryDefault)));
            addProp(new BooleanProp(PersistenceUnitProperties.JPQL_TOLERATE, "false"));
            addProp(new BooleanProp(PersistenceUnitProperties.MULTITENANT_SHARED_CACHE, "false"));
            addProp(new BooleanProp(PersistenceUnitProperties.MULTITENANT_SHARED_EMF, "true"));
            //Enhancement
            addProp(new QueryTimeoutUnitProp());
            addProp(new PessimisticLockTimeoutUnitProp());
            addProp(new BooleanProp(PersistenceUnitProperties.USE_LOCAL_TIMESTAMP, "false"));
            addProp(new BooleanProp(PersistenceUnitProperties.SQL_CALL_DEFERRAL, "true"));
            addProp(new BooleanProp(PersistenceUnitProperties.NAMING_INTO_INDEXED, "false"));
        }

        Prop(String name) {
            this.name = name;
        }

        Prop(String name, String defaultValue) {
            this(name);
            this.defaultValue = defaultValue;
        }

        static String getPropertyValueFromMap(String name, Map m, boolean useSystemAsDefault) {
            String value = (String)m.get(name);
            if (value == null && useSystemAsDefault) {
                if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
                    value = AccessController.doPrivileged(new PrivilegedGetSystemProperty(name));
                } else {
                    value = System.getProperty(name);
                }
            }

            return value;
        }

        // Collect all entries corresponding to the prefix name.
        // Note that entries from Map m override those from System properties.
        static Map getPrefixValuesFromMap(String name, Map m, boolean useSystemAsDefault) {
            Map mapOut = new HashMap();

            if(useSystemAsDefault) {
                Map.Entry[] entries = (Map.Entry[]) AccessController.doPrivileged(new PrivilegedAction() {
                    public Object run() {
                        return System.getProperties().entrySet().toArray(new Map.Entry[] {});
                        }
                    }
                );

                for (Map.Entry entry:entries) {
                    String str = (String)entry.getKey();
                    if(str.startsWith(name)) {
                        String entityName = str.substring(name.length(), str.length());
                        mapOut.put(entityName, entry.getValue());
                    }
                }
            }

            Iterator it = m.entrySet().iterator();
            while(it.hasNext()) {
                Map.Entry entry = (Map.Entry)it.next();
                String str = (String)entry.getKey();
                if(str.startsWith(name)) {
                    String entityName = str.substring(name.length(), str.length());
                    mapOut.put(entityName, entry.getValue());
                }
            }

            return mapOut;
        }

        static String getPropertyValue(String name, boolean shouldUseDefault, Map m, AbstractSession session, boolean useSystemAsDefault) {
            Prop prop = (Prop)mainMap.get(name);
            if(prop == null) {
                // it's not our property
                return null;
            }
            String value = getPropertyValueFromMap(name, m, useSystemAsDefault);
            if(value == null) {
                return null;
            }
            return prop.getValueToApply(value, shouldUseDefault, session);
        }

        static String getPropertyValueToApply(String name, Map m, AbstractSession session, boolean useSystemAsDefault) {
            Prop prop = (Prop)mainMap.get(name);
            if(prop == null) {
                throw new IllegalArgumentException(name);
            }
            String value = getPropertyValueFromMap(name, m, useSystemAsDefault);
            if(value == null) {
                return null;
            }
            return prop.getValueToApply(value, shouldUseDefault(value), session);
        }

        static String getPropertyValueToApply(String name, String value, AbstractSession session) {
            Prop prop = (Prop)mainMap.get(name);
            if(prop == null) {
                throw new IllegalArgumentException(name);
            }
            return prop.getValueToApply(value, shouldUseDefault(value), session);
        }

        static Map<String, Object> getPrefixValuesToApply(String prefix, Map m, AbstractSession session, boolean useSystemAsDefault) {
            Prop prop = (Prop)mainMap.get(prefix);
            // maps suffixes to property values
            Map mapIn = getPrefixValuesFromMap(prefix, m, useSystemAsDefault);
            if(mapIn.isEmpty()) {
                return mapIn;
            }

            HashMap mapOut = new HashMap(mapIn.size());
            Iterator it = mapIn.entrySet().iterator();
            while(it.hasNext()) {
                Map.Entry entry = (Map.Entry)it.next();
                String suffix = (String)entry.getKey();
                Object value = entry.getValue();
                if ((prop != null) && (value instanceof String)) {
                    value = prop.getValueToApply((String)value, shouldUseDefault((String)value), suffix, session);
                }
                mapOut.put(suffix, value);
            }
            // maps suffixes to valuesToApply
            return mapOut;
        }

        static String getDefaultPropertyValueToApply(String name, AbstractSession session) {
            Prop prop = (Prop)mainMap.get(name);
            if(prop == null) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("ejb30-default-for-unknown-property", new Object[]{name}));
            }
            prop.logDefault(session);
            return prop.defaultValueToApply;
        }

        String getValueToApply(String value, boolean shouldUseDefault, AbstractSession session) {
            return getValueToApply(value, shouldUseDefault, null, session);
        }

        // suffix is used only for properties-prefixes (like CacheType)
        String getValueToApply(String value, boolean shouldUseDefault, String suffix, AbstractSession session) {
            if(shouldUseDefault) {
                logDefault(session, suffix);
                return defaultValueToApply;
            }
            String valueToApply = value;
            if(valueMap != null) {
                String key = getUpperCaseString(value);
                valueToApply = (String)valueMap.get(key);
                if(valueToApply == null) {
                    boolean notFound = true;
                    if(valueToApplyMayBeNull) {
                        notFound = !valueMap.containsKey(key);
                    }
                    if(notFound) {
                        if(shouldReturnOriginalValueIfValueToApplyNotFound) {
                            valueToApply = value;
                        } else {
                            String propertyName = name;
                            if(suffix != null) {
                                propertyName = propertyName + suffix;
                            }
                            throw new IllegalArgumentException(ExceptionLocalization.buildMessage("ejb30-illegal-property-value", new Object[]{propertyName, getPrintValue(value)}));
                        }
                    }
                }
            }
            String logValue = PersistenceUnitProperties.getOverriddenLogStringForProperty(name);
            if (logValue != null){
                log(session, logValue, logValue, suffix);
            } else {
                log(session, value, valueToApply, suffix);
            }
            return valueToApply;
        }

        static String getUpperCaseString(String value) {
            if(value != null) {
                return value.toUpperCase();
            } else {
                return null;
            }
        }
        static String getPrintValue(String value) {
            if(value != null) {
                return value;
            } else {
                return "null";
            }
        }
        void initialize() {
            if(valueArray != null) {
                valueMap = new HashMap(valueArray.length);
                if(valueArray instanceof Object[][]) {
                    Object[][] valueArray2 = (Object[][])valueArray;
                    for(int i=0; i<valueArray2.length; i++) {
                        valueMap.put(getUpperCaseString((String)valueArray2[i][0]), valueArray2[i][1]);
                        if(valueArray2[i][1] == null) {
                            valueToApplyMayBeNull = true;
                        }
                    }
                } else {
                    for(int i=0; i<valueArray.length; i++) {
                        valueMap.put(getUpperCaseString((String)valueArray[i]), valueArray[i]);
                        if(valueArray[i] == null) {
                            valueToApplyMayBeNull = true;
                        }
                    }
                }
                defaultValueToApply = (String)valueMap.get(getUpperCaseString(defaultValue));
            } else {
                defaultValueToApply = defaultValue;
            }
        }

        void logDefault(AbstractSession session) {
            logDefault(session, null);
        }

        void logDefault(AbstractSession session, String suffix) {
            if (session != null) {
                if (session.shouldLog(SessionLog.FINEST, SessionLog.PROPERTIES)) {
                    String propertyName = name;
                    if (suffix != null) {
                        propertyName = propertyName + suffix;
                    }
                    if (defaultValue != defaultValueToApply) {
                        session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "handler_property_value_default", new Object[]{propertyName, defaultValue, defaultValueToApply});
                    } else {
                        session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "property_value_default", new Object[]{propertyName, defaultValue});
                    }
                }
            }
        }

        void log(AbstractSession session, String value, String valueToApply, String suffix) {
            if (session != null) {
                if (session.shouldLog(SessionLog.FINEST, SessionLog.PROPERTIES)) {
                    String propertyName = name;
                    if(suffix != null) {
                        propertyName = propertyName + suffix;
                    }
                    if(value != valueToApply) {
                        session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "handler_property_value_specified", new Object[]{propertyName, value, valueToApply});
                    } else {
                        session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "property_value_specified", new Object[]{propertyName, value});
                    }
                }
            }
        }

        static void addProp(Prop prop) {
            prop.initialize();
            mainMap.put(prop.name, prop);
        }
    }

    protected static class LoggerTypeProp extends Prop {
        LoggerTypeProp() {
            super(PersistenceUnitProperties.LOGGING_LOGGER, LoggerType.DEFAULT);
            this.shouldReturnOriginalValueIfValueToApplyNotFound = true;
            String pcg = "org.eclipse.persistence.logging.";
            valueArray = new Object[][] {
                {LoggerType.DefaultLogger, pcg + "DefaultSessionLog"},
                {LoggerType.JavaLogger, pcg + "JavaLog"}
            };
        }
    }

    protected static class ConnectionPoolProp extends Prop {
        ConnectionPoolProp() {
            super(PersistenceUnitProperties.CONNECTION_POOL);
        }
    }

    protected static class LoggingLevelProp extends Prop {
        LoggingLevelProp(String name, String defaultValue) {
            super(name, defaultValue);
            valueArray = new Object[] {
                Level.OFF.getName(),
                Level.SEVERE.getName(),
                Level.OFF.getName(),
                Level.WARNING.getName(),
                Level.INFO.getName(),
                Level.CONFIG.getName(),
                Level.FINE.getName(),
                Level.FINER.getName(),
                Level.FINEST.getName(),
                Level.ALL.getName()
            };
        }
    }

    protected static class ReferenceModeProp extends Prop {
        ReferenceModeProp() {
            super(EntityManagerProperties.PERSISTENCE_CONTEXT_REFERENCE_MODE, ReferenceMode.HARD.toString());
            valueArray = new Object[] {
                ReferenceMode.HARD.toString(),
                ReferenceMode.WEAK.toString(),
                ReferenceMode.FORCE_WEAK.toString()
            };
        }
    }

    protected static class FlushModeProp extends Prop {
        FlushModeProp() {
            //XXX - using javax.persistence.FlushModeType.* directly causes
            //runtime dependency of this class on JPA API which is wrong
            //and leads to transitive dependency from MOXy to JPA API
            super(EntityManagerProperties.PERSISTENCE_CONTEXT_FLUSH_MODE,
                    /* FlushModeType.AUTO.toString() */ "AUTO");
            valueArray = new Object[] {
                    /* FlushModeType.AUTO.toString() */ "AUTO",
                    /* FlushModeType.COMMIT.toString() */"COMMIT"
            };
        }
    }

    protected static class CommitOrderProp extends Prop {
        CommitOrderProp() {
            super(EntityManagerProperties.PERSISTENCE_CONTEXT_COMMIT_ORDER, CommitOrderType.None.toString());
            valueArray = new Object[] {
                    CommitOrderType.Id.toString(),
                    CommitOrderType.Changes.toString(),
                    CommitOrderType.None.toString()
            };
        }
    }

    protected static class TargetDatabaseProp extends Prop {
        TargetDatabaseProp() {
            super(PersistenceUnitProperties.TARGET_DATABASE, TargetDatabase.DEFAULT);
            this.shouldReturnOriginalValueIfValueToApplyNotFound = true;
            String pcg = "org.eclipse.persistence.platform.database.";
            valueArray = new Object[][] {
                {TargetDatabase.Auto, pcg + "DatabasePlatform"},
                {TargetDatabase.Oracle, pcg + "OraclePlatform"},
                {TargetDatabase.Oracle8, pcg + "oracle.Oracle8Platform"},
                {TargetDatabase.Oracle9, pcg + "oracle.Oracle9Platform"},
                {TargetDatabase.Oracle10, pcg + "oracle.Oracle10Platform"},
                {TargetDatabase.Oracle11, pcg + "oracle.Oracle11Platform"},
                {TargetDatabase.Attunity, pcg + "AttunityPlatform"},
                {TargetDatabase.Cloudscape, pcg + "CloudscapePlatform"},
                {TargetDatabase.Database, pcg + "DatabasePlatform"},
                {TargetDatabase.DB2Mainframe, pcg + "DB2MainframePlatform"},
                {TargetDatabase.DB2, pcg + "DB2Platform"},
                {TargetDatabase.DBase, pcg + "DBasePlatform"},
                {TargetDatabase.Derby, pcg + "DerbyPlatform"},
                {TargetDatabase.HANA,  pcg + "HANAPlatform"},
                {TargetDatabase.HSQL, pcg + "HSQLPlatform"},
                {TargetDatabase.Informix, pcg + "InformixPlatform"},
                {TargetDatabase.JavaDB, pcg + "JavaDBPlatform"},
                {TargetDatabase.MySQL, pcg + "MySQLPlatform"},
                {TargetDatabase.MaxDB, pcg + "MaxDBPlatform"},
                {TargetDatabase.MySQL4, pcg + "MySQLPlatform"}, // 211249: keep backwards compatibility
                {TargetDatabase.PointBase,  pcg + "PointBasePlatform"},
                {TargetDatabase.PostgreSQL,  pcg + "PostgreSQLPlatform"},
                {TargetDatabase.SQLAnywhere,  pcg + "SQLAnywherePlatform"},
                {TargetDatabase.SQLServer,  pcg + "SQLServerPlatform"},
                {TargetDatabase.Sybase,  pcg + "SybasePlatform"},
                {TargetDatabase.Symfoware,  pcg + "SymfowarePlatform"},
                {TargetDatabase.TimesTen,  pcg + "TimesTenPlatform"}
            };
        }
    }

    protected static class TargetServerProp extends Prop {
        TargetServerProp() {
            super(PersistenceUnitProperties.TARGET_SERVER, TargetServer.DEFAULT);
            this.shouldReturnOriginalValueIfValueToApplyNotFound = true;
            String pcg = "org.eclipse.persistence.platform.server.";
            valueArray = new Object[][] {
                {TargetServer.None, pcg + "NoServerPlatform"},
                {TargetServer.OC4J, pcg + "oc4j.Oc4jPlatform"},
                {TargetServer.SunAS9, pcg + "sunas.SunAS9ServerPlatform"},
                {TargetServer.Glassfish, pcg + "glassfish.GlassfishPlatform"},
                {TargetServer.WebSphere, pcg + "was.WebSpherePlatform"},
                {TargetServer.WebSphere_6_1, pcg + "was.WebSphere_6_1_Platform"},
                {TargetServer.WebSphere_7, pcg + "was.WebSphere_7_Platform"},
                {TargetServer.WebSphere_EJBEmbeddable, pcg + "was.WebSphere_EJBEmbeddable_Platform"},
                {TargetServer.WebSphere_Liberty, pcg + "was.WebSphere_Liberty_Platform"},
                {TargetServer.WebLogic, pcg + "wls.WebLogicPlatform"},
                {TargetServer.WebLogic_9, pcg + "wls.WebLogic_9_Platform"},
                {TargetServer.WebLogic_10, pcg + "wls.WebLogic_10_Platform"},
                {TargetServer.WebLogic_12, pcg + "wls.WebLogic_12_Platform"},
                {TargetServer.JBoss, pcg + "jboss.JBossPlatform"},
                {TargetServer.SAPNetWeaver_7_1, pcg + "sap.SAPNetWeaver_7_1_Platform"},            };
        }
    }

    protected static class CacheSizeProp extends Prop {
        CacheSizeProp() {
            super(PersistenceUnitProperties.CACHE_SIZE_, Integer.toString(1000));
        }
    }

    protected static class CacheTypeProp extends Prop {
        CacheTypeProp() {
            super(PersistenceUnitProperties.CACHE_TYPE_, CacheType.DEFAULT);
            String pcg = "org.eclipse.persistence.internal.identitymaps.";
            valueArray = new Object[][] {
                {CacheType.Weak, pcg + "WeakIdentityMap"},
                {CacheType.Soft, pcg + "SoftIdentityMap"},
                {CacheType.SoftWeak, pcg + "SoftCacheWeakIdentityMap"},
                {CacheType.HardWeak, pcg + "HardCacheWeakIdentityMap"},
                {CacheType.Full, pcg + "FullIdentityMap"},
                {CacheType.NONE, pcg + "NoIdentityMap"}
            };
        }
    }

    protected static class BooleanProp extends Prop {
        BooleanProp(String name, String defaultValue) {
            super(name, defaultValue);
            valueArray = new Object[] {
                "true",
                "false"
            };
        }
    }

    protected static class DescriptorCustomizerProp extends Prop {
        DescriptorCustomizerProp() {
            super(PersistenceUnitProperties.DESCRIPTOR_CUSTOMIZER_);
        }
    }

    protected static class BatchWritingProp extends Prop {
        BatchWritingProp() {
            super(PersistenceUnitProperties.BATCH_WRITING, BatchWriting.DEFAULT);
            this.shouldReturnOriginalValueIfValueToApplyNotFound = true;
            valueArray = new Object[] {
                BatchWriting.None,
                BatchWriting.JDBC,
                BatchWriting.Buffered,
                BatchWriting.OracleJDBC
            };
        }
    }

    protected static class FlushClearCacheProp extends Prop {
        FlushClearCacheProp() {
            super(PersistenceUnitProperties.FLUSH_CLEAR_CACHE, FlushClearCache.DEFAULT);
            valueArray = new Object[] {
                FlushClearCache.Merge,
                FlushClearCache.Drop,
                FlushClearCache.DropInvalidate
            };
        }
    }

    protected static class ExclusiveConnectionModeProp extends Prop {
        ExclusiveConnectionModeProp() {
            super(PersistenceUnitProperties.EXCLUSIVE_CONNECTION_MODE, ExclusiveConnectionMode.DEFAULT);
            valueArray = new Object[] {
                ExclusiveConnectionMode.Transactional,
                ExclusiveConnectionMode.Isolated,
                ExclusiveConnectionMode.Always
            };
        }
    }

    protected static class IdValidationProp extends Prop {
        IdValidationProp() {
            super(PersistenceUnitProperties.ID_VALIDATION, IdValidation.ZERO.toString());
            valueArray = new Object[] {
                IdValidation.NULL.toString(),
                IdValidation.ZERO.toString(),
                IdValidation.NEGATIVE.toString(),
                IdValidation.NONE.toString()
            };
        }
    }

    protected static class QueryTimeoutUnitProp extends Prop {
        QueryTimeoutUnitProp() {
            super(PersistenceUnitProperties.QUERY_TIMEOUT_UNIT, DescriptorQueryManager.DefaultTimeoutUnit.toString());
            valueArray = new Object[] {
                TimeUnit.MILLISECONDS.toString(),
                TimeUnit.SECONDS.toString(),
                TimeUnit.MINUTES.toString()
            };
        }
    }

    protected static class PessimisticLockTimeoutUnitProp extends Prop {
        PessimisticLockTimeoutUnitProp() {
            super(PersistenceUnitProperties.PESSIMISTIC_LOCK_TIMEOUT_UNIT, DescriptorQueryManager.DefaultTimeoutUnit.toString());
            valueArray = new Object[] {
                TimeUnit.MILLISECONDS.toString(),
                TimeUnit.SECONDS.toString(),
                TimeUnit.MINUTES.toString()
            };
        }
    }
}
