/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.conf;

import static java.util.Arrays.asList;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;

import org.apache.openjpa.audit.AuditLogger;
import org.apache.openjpa.audit.Auditor;
import org.apache.openjpa.datacache.CacheDistributionPolicy;
import org.apache.openjpa.datacache.ConcurrentDataCache;
import org.apache.openjpa.datacache.ConcurrentQueryCache;
import org.apache.openjpa.datacache.DataCacheManager;
import org.apache.openjpa.datacache.DataCacheManagerImpl;
import org.apache.openjpa.datacache.PartitionedDataCache;
import org.apache.openjpa.ee.ManagedRuntime;
import org.apache.openjpa.enhance.RuntimeUnenhancedClassesModes;
import org.apache.openjpa.event.BrokerFactoryEventManager;
import org.apache.openjpa.event.LifecycleEventManager;
import org.apache.openjpa.event.OrphanedKeyAction;
import org.apache.openjpa.event.RemoteCommitEventManager;
import org.apache.openjpa.event.RemoteCommitProvider;
import org.apache.openjpa.instrumentation.InstrumentationManager;
import org.apache.openjpa.instrumentation.InstrumentationManagerImpl;
import org.apache.openjpa.kernel.AutoClear;
import org.apache.openjpa.kernel.BrokerImpl;
import org.apache.openjpa.kernel.ConnectionRetainModes;
import org.apache.openjpa.kernel.FinderCache;
import org.apache.openjpa.kernel.InverseManager;
import org.apache.openjpa.kernel.LockLevels;
import org.apache.openjpa.kernel.LockManager;
import org.apache.openjpa.kernel.PreparedQueryCache;
import org.apache.openjpa.kernel.QueryFlushModes;
import org.apache.openjpa.kernel.RestoreState;
import org.apache.openjpa.kernel.SavepointManager;
import org.apache.openjpa.kernel.Seq;
import org.apache.openjpa.kernel.exps.AggregateListener;
import org.apache.openjpa.kernel.exps.FilterListener;
import org.apache.openjpa.lib.conf.BooleanValue;
import org.apache.openjpa.lib.conf.ClassListValue;
import org.apache.openjpa.lib.conf.ConfigurationImpl;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.conf.IntValue;
import org.apache.openjpa.lib.conf.ObjectValue;
import org.apache.openjpa.lib.conf.PluginListValue;
import org.apache.openjpa.lib.conf.PluginValue;
import org.apache.openjpa.lib.conf.ProductDerivations;
import org.apache.openjpa.lib.conf.StringListValue;
import org.apache.openjpa.lib.conf.StringValue;
import org.apache.openjpa.lib.encryption.EncryptionProvider;
import org.apache.openjpa.lib.instrumentation.InstrumentationLevel;
import org.apache.openjpa.lib.instrumentation.InstrumentationProvider;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.MetaDataFactory;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.util.ClassResolver;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.ProxyManager;
import org.apache.openjpa.util.StoreFacadeTypeRegistry;
import org.apache.openjpa.validation.ValidatingLifecycleEventManager;

/**
 * Implementation of the {@link OpenJPAConfiguration} interface.
 *
 * @see ConfigurationImpl
 * @author Marc Prud'hommeaux
 * @author Abe White
 */
public class OpenJPAConfigurationImpl
    extends ConfigurationImpl
    implements OpenJPAConfiguration {

    // private static final Localizer _loc = Localizer.forPackage(OpenJPAConfigurationImpl.class);

    // cached state; some of this is created in getter methods, so make
    // protected in case subclasses want to access without creating
    protected MetaDataRepository metaRepository = null;
    protected RemoteCommitEventManager remoteEventManager = null;

    // openjpa properties
    public ObjectValue classResolverPlugin;
    public BrokerValue brokerPlugin;
    public ObjectValue dataCachePlugin;
    public ObjectValue dataCacheManagerPlugin;
    public ObjectValue auditorPlugin;
    public ObjectValue cacheDistributionPolicyPlugin;
    public IntValue dataCacheTimeout;
    public ObjectValue queryCachePlugin;
    public BooleanValue dynamicDataStructs;
    public ObjectValue managedRuntimePlugin;
    public BooleanValue transactionMode;
    public IntValue connectionRetainMode;
    public IntValue fetchBatchSize;
    public IntValue maxFetchDepth;
    public StringListValue fetchGroups;
    public IntValue flushBeforeQueries;
    public IntValue lockTimeout;
    public IntValue queryTimeout;
    public IntValue readLockLevel;
    public IntValue writeLockLevel;
    public ObjectValue seqPlugin;
    public PluginListValue filterListenerPlugins;
    public PluginListValue aggregateListenerPlugins;
    public BooleanValue retryClassRegistration;
    public ObjectValue proxyManagerPlugin;
    public StringValue connectionUserName;
    public StringValue connectionPassword;
    public PluginValue encryptionProvider;
    public StringValue connectionURL;
    public StringValue connectionDriverName;
    public ObjectValue connectionFactory;
    public StringValue connectionFactoryName;
    public StringValue connectionProperties;
    public StringValue connectionFactoryProperties;
    public BooleanValue connectionFactoryMode;
    public StringValue connection2UserName;
    public StringValue connection2Password;
    public StringValue connection2URL;
    public StringValue connection2DriverName;
    public StringValue connection2Properties;
    public ObjectValue connectionFactory2;
    public StringValue connectionFactory2Name;
    public StringValue connectionFactory2Properties;
    public BooleanValue optimistic;
    public IntValue autoClear;
    public BooleanValue retainState;
    public IntValue restoreState;
    public ObjectValue detachStatePlugin;
    public BooleanValue ignoreChanges;
    public BooleanValue nontransactionalRead;
    public BooleanValue nontransactionalWrite;
    public BooleanValue refreshFromDataCache;
    public BooleanValue multithreaded;
    public StringValue mapping;
    public PluginValue metaFactoryPlugin;
    public MetaDataRepositoryValue metaRepositoryPlugin;
    public ObjectValue lockManagerPlugin;
    public ObjectValue inverseManagerPlugin;
    public ObjectValue savepointManagerPlugin;
    public ObjectValue orphanedKeyPlugin;
    public ObjectValue compatibilityPlugin;
    public ObjectValue callbackPlugin;
    public QueryCompilationCacheValue queryCompilationCachePlugin;
    public IntValue runtimeUnenhancedClasses;
    public CacheMarshallersValue cacheMarshallerPlugins;
    public BooleanValue eagerInitialization;
    public PluginValue preparedQueryCachePlugin;
    public PluginValue finderCachePlugin;
    public ObjectValue specification;
    public StringValue validationMode;
    public ObjectValue validationFactory;
    public ObjectValue validator;
    public ObjectValue lifecycleEventManager;
    public StringValue validationGroupPrePersist;
    public StringValue validationGroupPreUpdate;
    public StringValue validationGroupPreRemove;
    public StringValue dataCacheMode;
    public BooleanValue dynamicEnhancementAgent;
    public ObjectValue instrumentationManager;
    public PluginListValue instrumentationProviders;
    public BooleanValue postLoadOnMerge;
    public BooleanValue optimizeIdCopy;
    public BooleanValue useTcclForSelectNew;
    public ClassListValue typesWithoutEnhancement;

    // JPA Properties
    public IntValue databaseAction;
    public IntValue scriptsAction;
    public IntValue createSource;
    public IntValue dropSource;
    public StringValue createScriptSource;
    public StringValue dropScriptSource;
    public StringValue createScriptTarget;
    public StringValue dropScriptTarget;
    public StringValue loadScriptSource;

    // custom values
    public BrokerFactoryValue brokerFactoryPlugin;
    public RemoteCommitProviderValue remoteProviderPlugin;
    public AutoDetachValue autoDetach;

    private Collection<String> supportedOptions = new HashSet<>(33);
    private final StoreFacadeTypeRegistry _storeFacadeRegistry = new StoreFacadeTypeRegistry();
    private BrokerFactoryEventManager _brokerFactoryEventManager = new BrokerFactoryEventManager(this);
    private Map<String, Object> _peMap; //contains persistence environment-specific info
    private boolean _allowSetLifeCycleEventManager = true;
    /**
     * Default constructor. Attempts to load global properties.
     */
    public OpenJPAConfigurationImpl() {
        this(true);
    }

    /**
     * Constructor.
     *
     * @param loadGlobals whether to attempt to load the global properties
     */
    public OpenJPAConfigurationImpl(boolean loadGlobals) {
        this(true, loadGlobals);
    }

    /**
     * Constructor.
     *
     * @param derivations whether to apply product derivations
     * @param loadGlobals whether to attempt to load the global properties
     */
    public OpenJPAConfigurationImpl(boolean derivations, boolean loadGlobals) {
        super(false);
        String[] aliases;

        classResolverPlugin = addPlugin("ClassResolver", true);
        aliases = new String[] {
                "default", "org.apache.openjpa.util.ClassResolverImpl",
                // deprecated alias
                "spec", "org.apache.openjpa.util.ClassResolverImpl", };
        classResolverPlugin.setAliases(aliases);
        classResolverPlugin.setDefault(aliases[0]);
        classResolverPlugin.setString(aliases[0]);
        classResolverPlugin.setInstantiatingGetter("getClassResolverInstance");

        brokerFactoryPlugin = new BrokerFactoryValue();
        addValue(brokerFactoryPlugin);

        brokerPlugin = new BrokerValue();
        addValue(brokerPlugin);

        dataCacheManagerPlugin = addPlugin("DataCacheManager", true);
        aliases =
            new String[] { "default", DataCacheManagerImpl.class.getName(), };
        dataCacheManagerPlugin.setAliases(aliases);
        dataCacheManagerPlugin.setDefault(aliases[0]);
        dataCacheManagerPlugin.setString(aliases[0]);
        dataCacheManagerPlugin.setInstantiatingGetter("getDataCacheManager");

        cacheDistributionPolicyPlugin = addPlugin("CacheDistributionPolicy", true);
        aliases = new String[] {
                "default",    "org.apache.openjpa.datacache.DefaultCacheDistributionPolicy",
                "type-based", "org.apache.openjpa.datacache.TypeBasedCacheDistributionPolicy"};
        cacheDistributionPolicyPlugin.setAliases(aliases);
        cacheDistributionPolicyPlugin.setDefault(aliases[0]);
        cacheDistributionPolicyPlugin.setString(aliases[0]);
        cacheDistributionPolicyPlugin.setInstantiatingGetter("getCacheDistributionPolicy");

        dataCachePlugin = addPlugin("DataCache", false);
        aliases = new String[] {
            "false", null,
            "true", ConcurrentDataCache.class.getName(),
            "concurrent", ConcurrentDataCache.class.getName(),
            "partitioned", PartitionedDataCache.class.getName(),
        };
        dataCachePlugin.setAliases(aliases);
        dataCachePlugin.setDefault(aliases[0]);
        dataCachePlugin.setString(aliases[0]);

        dataCacheTimeout = addInt("DataCacheTimeout");
        dataCacheTimeout.setDefault("-1");
        dataCacheTimeout.set(-1);
        dataCacheTimeout.setDynamic(true);

        queryCachePlugin = addPlugin("QueryCache", false);
        aliases = new String[] {
            "false", null,
            "true", ConcurrentQueryCache.class.getName(),
            "concurrent", ConcurrentQueryCache.class.getName(),
        };
        queryCachePlugin.setAliases(aliases);
        queryCachePlugin.setDefault(aliases[0]);
        queryCachePlugin.setString(aliases[0]);

        refreshFromDataCache = addBoolean("RefreshFromDataCache");
        refreshFromDataCache.setDefault("false");
        refreshFromDataCache.set(false);
        refreshFromDataCache.setDynamic(true);

        dynamicDataStructs = addBoolean("DynamicDataStructs");
        dynamicDataStructs.setDefault("false");
        dynamicDataStructs.set(false);

        lockManagerPlugin = addPlugin("LockManager", false);
        aliases =
            new String[] {
                "none", "org.apache.openjpa.kernel.NoneLockManager",
                "version", "org.apache.openjpa.kernel.VersionLockManager", };
        lockManagerPlugin.setAliases(aliases);
        lockManagerPlugin.setDefault(aliases[0]);
        lockManagerPlugin.setString(aliases[0]);

        inverseManagerPlugin = addPlugin("InverseManager", false);
        aliases = new String[] {
                "false", null,
                "true",  "org.apache.openjpa.kernel.InverseManager", };
        inverseManagerPlugin.setAliases(aliases);
        inverseManagerPlugin.setDefault(aliases[0]);
        inverseManagerPlugin.setString(aliases[0]);

        savepointManagerPlugin = addPlugin("SavepointManager", true);
        aliases = new String[] {
                "in-mem", "org.apache.openjpa.kernel.InMemorySavepointManager", };
        savepointManagerPlugin.setAliases(aliases);
        savepointManagerPlugin.setDefault(aliases[0]);
        savepointManagerPlugin.setString(aliases[0]);
        savepointManagerPlugin.setInstantiatingGetter("getSavepointManagerInstance");

        orphanedKeyPlugin = addPlugin("OrphanedKeyAction", true);
        aliases = new String[] {
                "log",       "org.apache.openjpa.event.LogOrphanedKeyAction",
                "exception", "org.apache.openjpa.event.ExceptionOrphanedKeyAction",
                "none",      "org.apache.openjpa.event.NoneOrphanedKeyAction", };
        orphanedKeyPlugin.setAliases(aliases);
        orphanedKeyPlugin.setDefault(aliases[0]);
        orphanedKeyPlugin.setString(aliases[0]);
        orphanedKeyPlugin.setInstantiatingGetter("getOrphanedKeyActionInstance");

        remoteProviderPlugin = new RemoteCommitProviderValue();
        addValue(remoteProviderPlugin);

        transactionMode = addBoolean("TransactionMode");
        aliases = new String[] { "local", "false", "managed", "true", };
        transactionMode.setAliases(aliases);
        transactionMode.setDefault(aliases[0]);

        managedRuntimePlugin = addPlugin("ManagedRuntime", true);
        aliases = new String[] {
                "auto",       "org.apache.openjpa.ee.AutomaticManagedRuntime",
                "jndi",       "org.apache.openjpa.ee.JNDIManagedRuntime",
                "invocation", "org.apache.openjpa.ee.InvocationManagedRuntime", };
        managedRuntimePlugin.setAliases(aliases);
        managedRuntimePlugin.setDefault(aliases[0]);
        managedRuntimePlugin.setString(aliases[0]);
        managedRuntimePlugin
            .setInstantiatingGetter("getManagedRuntimeInstance");

        proxyManagerPlugin = addPlugin("ProxyManager", true);
        aliases = new String[] {
                "default", "org.apache.openjpa.util.ProxyManagerImpl" };
        proxyManagerPlugin.setAliases(aliases);
        proxyManagerPlugin.setDefault(aliases[0]);
        proxyManagerPlugin.setString(aliases[0]);
        proxyManagerPlugin.setInstantiatingGetter("getProxyManagerInstance");

        mapping = addString("Mapping");
        metaFactoryPlugin = addPlugin("MetaDataFactory", false);

        metaRepositoryPlugin = (MetaDataRepositoryValue) addValue(new MetaDataRepositoryValue());

        connectionFactory = addObject("ConnectionFactory");
        connectionFactory.setInstantiatingGetter("getConnectionFactory");

        connectionFactory2 = addObject("ConnectionFactory2");
        connectionFactory2.setInstantiatingGetter("getConnectionFactory2");
        // This is done because this plug-in may get initialized very lazily
        // when the runtime needs it for flush or a sequence. To keep it
        // dynamic allows it to be set even when the configuration is frozen
        connectionFactory.setDynamic(true);
        connectionFactory2.setDynamic(true);


        connectionUserName = addString("ConnectionUserName");
        connectionUserName.addEquivalentKey("jakarta.persistence.jdbc.user");

        connectionPassword = addString("ConnectionPassword");
        connectionPassword.addEquivalentKey("jakarta.persistence.jdbc.password");
        connectionPassword.hide();

        encryptionProvider = addPlugin("EncryptionProvider",true);

        connectionURL = addString("ConnectionURL");
        connectionURL.addEquivalentKey("jakarta.persistence.jdbc.url");

        connectionDriverName = addString("ConnectionDriverName");
        connectionDriverName.addEquivalentKey("jakarta.persistence.jdbc.driver");

        connectionFactoryName = addString("ConnectionFactoryName");
        connectionProperties = addString("ConnectionProperties");
        connectionFactoryProperties = addString("ConnectionFactoryProperties");
        connection2UserName = addString("Connection2UserName");
        connection2Password = addString("Connection2Password");
        connection2Password.hide();

        connection2URL = addString("Connection2URL");
        connection2DriverName = addString("Connection2DriverName");
        connection2Properties = addString("Connection2Properties");
        connectionFactory2Properties = addString("ConnectionFactory2Properties");
        connectionFactory2Name = addString("ConnectionFactory2Name");

        connectionFactoryMode = addBoolean("ConnectionFactoryMode");
        aliases = new String[] { "local", "false", "managed", "true", };
        connectionFactoryMode.setAliases(aliases);
        connectionFactoryMode.setDefault(aliases[0]);

        optimistic = addBoolean("Optimistic");
        optimistic.setDefault("true");
        optimistic.set(true);

        postLoadOnMerge = addBoolean("PostLoadOnMerge");
        postLoadOnMerge.setDefault("false");
        postLoadOnMerge.set(false);

        optimizeIdCopy = addBoolean("OptimizeIdCopy");
        optimizeIdCopy.setDefault("false");
        optimizeIdCopy.set(false);

        databaseAction = addInt("jakarta.persistence.schema-generation.database.action");
        aliases = new String[] {
                "none", String.valueOf(SchemaGenerationAction.NONE),
                "create", String.valueOf(SchemaGenerationAction.CREATE),
                "drop-and-create", String.valueOf(SchemaGenerationAction.DROP_AND_CREATE),
                "drop", String.valueOf(SchemaGenerationAction.DROP)
        };
        databaseAction.setAliases(aliases);
        databaseAction.setDefault(aliases[0]);
        databaseAction.setAliasListComprehensive(true);

        scriptsAction = addInt("jakarta.persistence.schema-generation.scripts.action");
        aliases = new String[] {
                "none", String.valueOf(SchemaGenerationAction.NONE),
                "create", String.valueOf(SchemaGenerationAction.CREATE),
                "drop-and-create", String.valueOf(SchemaGenerationAction.DROP_AND_CREATE),
                "drop", String.valueOf(SchemaGenerationAction.DROP)
        };
        scriptsAction.setAliases(aliases);
        scriptsAction.setDefault(aliases[0]);
        scriptsAction.setAliasListComprehensive(true);

        createSource = addInt("jakarta.persistence.schema-generation.create-source");
        aliases = new String[] {
                "none", String.valueOf(SchemaGenerationSource.NONE),
                "metadata", String.valueOf(SchemaGenerationSource.METADATA),
                "script", String.valueOf(SchemaGenerationSource.SCRIPT),
                "metadata-then-script", String.valueOf(SchemaGenerationSource.METADATA_THEN_SCRIPT),
                "script-then-metadata", String.valueOf(SchemaGenerationSource.SCRIPT_THEN_METADATA)
        };
        createSource.setAliases(aliases);
        createSource.setDefault(aliases[0]);
        createSource.setAliasListComprehensive(true);

        dropSource = addInt("jakarta.persistence.schema-generation.drop-source");
        aliases = new String[] {
                "metadata", String.valueOf(SchemaGenerationSource.METADATA),
                "script", String.valueOf(SchemaGenerationSource.SCRIPT),
                "metadata-then-script", String.valueOf(SchemaGenerationSource.METADATA_THEN_SCRIPT),
                "script-then-metadata", String.valueOf(SchemaGenerationSource.SCRIPT_THEN_METADATA)
        };
        dropSource.setAliases(aliases);
        dropSource.setDefault(aliases[0]);
        dropSource.setAliasListComprehensive(true);

        createScriptSource = addString("jakarta.persistence.schema-generation.create-script-source");
        dropScriptSource = addString("jakarta.persistence.schema-generation.drop-script-source");
        createScriptTarget = addString("jakarta.persistence.schema-generation.scripts.create-target");
        dropScriptTarget = addString("jakarta.persistence.schema-generation.scripts.drop-target");
        loadScriptSource = addString("jakarta.persistence.sql-load-script-source");

        autoClear = addInt("AutoClear");
        aliases =
            new String[] { "datastore",
                String.valueOf(AutoClear.CLEAR_DATASTORE), "all",
                String.valueOf(AutoClear.CLEAR_ALL), };
        autoClear.setAliases(aliases);
        autoClear.setDefault(aliases[0]);
        autoClear.set(AutoClear.CLEAR_DATASTORE);
        autoClear.setAliasListComprehensive(true);

        retainState = addBoolean("RetainState");
        retainState.setDefault("true");
        retainState.set(true);

        restoreState = addInt("RestoreState");
        aliases = new String[] {
                "none",      String.valueOf(RestoreState.RESTORE_NONE),
                "false",     String.valueOf(RestoreState.RESTORE_NONE),
                "immutable", String.valueOf(RestoreState.RESTORE_IMMUTABLE),
                // "true" for compat with jdo RestoreValues
                "true",      String.valueOf(RestoreState.RESTORE_IMMUTABLE),
                "all",       String.valueOf(RestoreState.RESTORE_ALL), };
        restoreState.setAliases(aliases);
        restoreState.setDefault(aliases[0]);
        restoreState.set(RestoreState.RESTORE_IMMUTABLE);
        restoreState.setAliasListComprehensive(true);

        autoDetach = new AutoDetachValue();
        addValue(autoDetach);

        detachStatePlugin = addPlugin("DetachState", true);
        aliases = new String[] {
            "loaded",       DetachOptions.Loaded.class.getName(),
            "fgs",          DetachOptions.FetchGroups.class.getName(),
            "fetch-groups", DetachOptions.FetchGroups.class.getName(),
            "all",          DetachOptions.All.class.getName(),
        };
        detachStatePlugin.setAliases(aliases);
        detachStatePlugin.setDefault(aliases[0]);
        detachStatePlugin.setString(aliases[0]);
        detachStatePlugin.setInstantiatingGetter("getDetachStateInstance");

        ignoreChanges = addBoolean("IgnoreChanges");

        nontransactionalRead = addBoolean("NontransactionalRead");
        nontransactionalRead.setDefault("true");
        nontransactionalRead.set(true);

        nontransactionalWrite = addBoolean("NontransactionalWrite");
        multithreaded = addBoolean("Multithreaded");

        fetchBatchSize = addInt("FetchBatchSize");
        fetchBatchSize.setDefault("-1");
        fetchBatchSize.set(-1);
        fetchBatchSize.setDynamic(true);

        maxFetchDepth = addInt("MaxFetchDepth");
        maxFetchDepth.setDefault("-1");
        maxFetchDepth.set(-1);

        fetchGroups = addStringList("FetchGroups");
        fetchGroups.setDefault("default");
        fetchGroups.set(new String[] { "default" });

        flushBeforeQueries = addInt("FlushBeforeQueries");
        aliases = new String[] {
                "true",            String.valueOf(QueryFlushModes.FLUSH_TRUE),
                "false",           String.valueOf(QueryFlushModes.FLUSH_FALSE),
                "with-connection", String.valueOf(QueryFlushModes.FLUSH_WITH_CONNECTION),
        };
        flushBeforeQueries.setAliases(aliases);
        flushBeforeQueries.setDefault(aliases[0]);
        flushBeforeQueries.set(QueryFlushModes.FLUSH_TRUE);
        flushBeforeQueries.setAliasListComprehensive(true);

        lockTimeout = addInt("LockTimeout");
        lockTimeout.addEquivalentKey("jakarta.persistence.lock.timeout");
        lockTimeout.setDefault("-1");
        lockTimeout.setDynamic(true);

        readLockLevel = addInt("ReadLockLevel");
        aliases = new String[] {
                "read", String.valueOf(LockLevels.LOCK_READ),
                "write", String.valueOf(LockLevels.LOCK_WRITE),
                "none", String.valueOf(LockLevels.LOCK_NONE),
            };
        readLockLevel.setAliases(aliases);
        readLockLevel.setDefault(aliases[0]);
        readLockLevel.set(LockLevels.LOCK_READ);
        readLockLevel.setAliasListComprehensive(true);

        writeLockLevel = addInt("WriteLockLevel");
        aliases = new String[] {
                "read", String.valueOf(LockLevels.LOCK_READ),
                "write", String.valueOf(LockLevels.LOCK_WRITE),
                "none", String.valueOf(LockLevels.LOCK_NONE),
            };
        writeLockLevel.setAliases(aliases);
        writeLockLevel.setDefault(aliases[1]);
        writeLockLevel.set(LockLevels.LOCK_WRITE);
        writeLockLevel.setAliasListComprehensive(true);

        seqPlugin = new SeqValue("Sequence");
        seqPlugin.setInstantiatingGetter("getSequenceInstance");
        addValue(seqPlugin);

        connectionRetainMode = addInt("ConnectionRetainMode");
        aliases = new String[] {
                "on-demand",   String.valueOf(ConnectionRetainModes.CONN_RETAIN_DEMAND),
                "transaction", String.valueOf(ConnectionRetainModes.CONN_RETAIN_TRANS),
                "always",      String.valueOf(ConnectionRetainModes.CONN_RETAIN_ALWAYS),
                // deprecated
                "persistence-manager", String.valueOf(ConnectionRetainModes.CONN_RETAIN_ALWAYS),
            };
        connectionRetainMode.setAliases(aliases);
        connectionRetainMode.setDefault(aliases[0]);
        connectionRetainMode.setAliasListComprehensive(true);
        connectionRetainMode.set(ConnectionRetainModes.CONN_RETAIN_DEMAND);

        filterListenerPlugins = addPluginList("FilterListeners");
        filterListenerPlugins.setInstantiatingGetter("getFilterListenerInstances");

        aggregateListenerPlugins = addPluginList("AggregateListeners");
        aggregateListenerPlugins.setInstantiatingGetter("getAggregateListenerInstances");

        retryClassRegistration = addBoolean("RetryClassRegistration");

        compatibilityPlugin = addPlugin("Compatibility", true);
        aliases = new String[] { "default", Compatibility.class.getName() };
        compatibilityPlugin.setAliases(aliases);
        compatibilityPlugin.setDefault(aliases[0]);
        compatibilityPlugin.setString(aliases[0]);
        compatibilityPlugin.setInstantiatingGetter("getCompatibilityInstance");

        callbackPlugin = addPlugin("Callbacks", true);
        aliases = new String[] { "default", CallbackOptions.class.getName() };
        callbackPlugin.setAliases(aliases);
        callbackPlugin.setDefault(aliases[0]);
        callbackPlugin.setString(aliases[0]);
        callbackPlugin.setInstantiatingGetter("getCallbackOptionsInstance");

        queryCompilationCachePlugin = new QueryCompilationCacheValue("QueryCompilationCache");
        queryCompilationCachePlugin.setInstantiatingGetter("getQueryCompilationCacheInstance");
        addValue(queryCompilationCachePlugin);

        runtimeUnenhancedClasses = addInt("RuntimeUnenhancedClasses");
        runtimeUnenhancedClasses.setAliases(new String[] {
            "supported",   String.valueOf(RuntimeUnenhancedClassesModes.SUPPORTED),
            "unsupported", String.valueOf(RuntimeUnenhancedClassesModes.UNSUPPORTED),
            "warn",        String.valueOf(RuntimeUnenhancedClassesModes.WARN),
        });
        runtimeUnenhancedClasses.setDefault("unsupported");
        runtimeUnenhancedClasses.setString("unsupported");
        runtimeUnenhancedClasses.setAliasListComprehensive(true);

        cacheMarshallerPlugins = (CacheMarshallersValue) addValue(new CacheMarshallersValue(this));

        eagerInitialization = addBoolean("InitializeEagerly");

        specification = new SpecificationPlugin(this, "Specification");
        addValue(specification);
        specification.setInstantiatingGetter("getSpecificationInstance");

        queryTimeout = addInt("jakarta.persistence.query.timeout");
        queryTimeout.setDefault("-1");
        queryTimeout.setDynamic(true);

        lifecycleEventManager = addPlugin("LifecycleEventManager", true);
        aliases = new String[] {
            "default", LifecycleEventManager.class.getName(),
            "validating", ValidatingLifecycleEventManager.class.getName(),
        };
        lifecycleEventManager.setAliases(aliases);
        lifecycleEventManager.setDefault(aliases[0]);
        lifecycleEventManager.setString(aliases[0]);
        lifecycleEventManager.setInstantiatingGetter("getLifecycleEventManagerInstance");

        dynamicEnhancementAgent  = addBoolean("DynamicEnhancementAgent");
        dynamicEnhancementAgent.setDefault("true");
        dynamicEnhancementAgent.set(true);

        instrumentationManager = addPlugin("InstrumentationManager", true);
        aliases =
            new String[] { "default", InstrumentationManagerImpl.class.getName(), };
        instrumentationManager.setAliases(aliases);
        instrumentationManager.setDefault(aliases[0]);
        instrumentationManager.setString(aliases[0]);
        instrumentationManager.setInstantiatingGetter("getInstrumentationManager");

        instrumentationProviders = addPluginList("Instrumentation");
        aliases = new String[] { "jmx", "org.apache.openjpa.instrumentation.jmx.JMXProvider" };
        instrumentationProviders.setAliases(aliases);
        instrumentationProviders.setInstantiatingGetter("getInstrumentationInstances");

        auditorPlugin = addPlugin("Auditor", true);
        aliases = new String[] { "default", AuditLogger.class.getName(), };
        auditorPlugin.setAliases(aliases);
        auditorPlugin.setInstantiatingGetter("getAuditorInstance");

        useTcclForSelectNew = addBoolean("UseTCCLinSelectNew");
        useTcclForSelectNew.setDefault("false");
        useTcclForSelectNew.set(false);

        typesWithoutEnhancement = new ClassListValue();

        // initialize supported options that some runtimes may not support
        supportedOptions.add(OPTION_NONTRANS_READ);
        supportedOptions.add(OPTION_OPTIMISTIC);
        supportedOptions.add(OPTION_ID_APPLICATION);
        supportedOptions.add(OPTION_ID_DATASTORE);
        supportedOptions.add(OPTION_TYPE_COLLECTION);
        supportedOptions.add(OPTION_TYPE_MAP);
        supportedOptions.add(OPTION_TYPE_ARRAY);
        supportedOptions.add(OPTION_NULL_CONTAINER);
        supportedOptions.add(OPTION_EMBEDDED_RELATION);
        supportedOptions.add(OPTION_EMBEDDED_COLLECTION_RELATION);
        supportedOptions.add(OPTION_EMBEDDED_MAP_RELATION);
        supportedOptions.add(OPTION_INC_FLUSH);
        supportedOptions.add(OPTION_VALUE_AUTOASSIGN);
        supportedOptions.add(OPTION_VALUE_INCREMENT);
        supportedOptions.add(OPTION_DATASTORE_CONNECTION);
        supportedOptions.add(OPTION_POSTLOAD_ON_MERGE);
        supportedOptions.add(OPTION_USE_TCCL_IN_SELECT_NEW);

        if (derivations)
            ProductDerivations.beforeConfigurationLoad(this);
        if (loadGlobals)
            loadGlobals();
    }

    @Override
    public Collection<String> supportedOptions() {
        return supportedOptions;
    }

    /**
     * Get the name of the Specification only (not the version or other
     * information) or an empty String if not set.
     *
     */
    @Override
    public String getSpecification() {
        Specification spec = getSpecificationInstance();
        return spec == null ? "" : spec.getName();
    }

    @Override
