/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2016, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.wildfly.iiop.openjdk;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;

import javax.net.ssl.SSLContext;

import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PersistentResourceDefinition;
import org.jboss.as.controller.PropertiesAttributeDefinition;
import org.jboss.as.controller.registry.Resource;
import org.jboss.as.naming.InitialContext;
import org.jboss.as.naming.service.DefaultNamespaceContextSelectorService;
import org.jboss.as.network.SocketBinding;
import org.jboss.as.server.AbstractDeploymentChainStep;
import org.jboss.as.server.DeploymentProcessorTarget;
import org.jboss.as.server.deployment.Phase;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.metadata.ejb.jboss.IORASContextMetaData;
import org.jboss.metadata.ejb.jboss.IORSASContextMetaData;
import org.jboss.metadata.ejb.jboss.IORSecurityConfigMetaData;
import org.jboss.metadata.ejb.jboss.IORTransportConfigMetaData;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.omg.CORBA.ORB;
import org.omg.PortableServer.IdAssignmentPolicyValue;
import org.omg.PortableServer.LifespanPolicyValue;
import org.omg.PortableServer.POA;
import org.wildfly.iiop.openjdk.csiv2.CSIV2IORToSocketInfo;
import org.wildfly.iiop.openjdk.csiv2.ElytronSASClientInterceptor;
import org.wildfly.iiop.openjdk.deployment.IIOPClearCachesProcessor;
import org.wildfly.iiop.openjdk.deployment.IIOPDependencyProcessor;
import org.wildfly.iiop.openjdk.deployment.IIOPMarkerProcessor;
import org.wildfly.iiop.openjdk.logging.IIOPLogger;
import org.wildfly.iiop.openjdk.naming.jndi.JBossCNCtxFactory;
import org.wildfly.iiop.openjdk.rmi.DelegatingStubFactoryFactory;
import org.wildfly.iiop.openjdk.security.NoSSLSocketFactory;
import org.wildfly.iiop.openjdk.security.LegacySSLSocketFactory;
import org.wildfly.iiop.openjdk.security.SSLSocketFactory;
import org.wildfly.iiop.openjdk.service.CorbaNamingService;
import org.wildfly.iiop.openjdk.service.CorbaORBService;
import org.wildfly.iiop.openjdk.service.CorbaPOAService;
import org.wildfly.iiop.openjdk.service.IORSecConfigMetaDataService;
import org.wildfly.security.auth.client.AuthenticationContext;
import org.wildfly.security.manager.WildFlySecurityManager;

import com.sun.corba.se.impl.orbutil.ORBConstants;
import org.jboss.as.controller.AbstractBoottimeAddStepHandler;

/**
 *

 * This class implements a {@code ModelAddOperationHandler} that installs the IIOP subsystem services:
 *

 *
{@code CorbaORBService}: responsible for configuring and starting the CORBA {@code ORB}.

 *
{@code CorbaPOAService}: responsible for creating and activating CORBA {@code POA}s.

 *
{@code CorbaNamingService}: responsible for creating and starting the CORBA naming service.

 *

 * After the {@code ORB} is created, we create and activate the "RootPOA" and then use this {@code POA} to create the
 * {@code POA}s required by the other services.
 *

 *
 * @author Stefan Guilhen
 * @author Tomasz Adamski
 */
public class IIOPSubsystemAdd extends AbstractBoottimeAddStepHandler {

    public IIOPSubsystemAdd(final Collection<? extends AttributeDefinition> attributes) {
        super(attributes);
    }

    @Override
    protected void performBoottime(final OperationContext context, final ModelNode operation, final ModelNode model) throws OperationFailedException {

        // This needs to run after all child resources so that they can detect a fresh state
        context.addStep(new OperationStepHandler() {
            @Override
            public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
                final Resource resource = context.readResource(PathAddress.EMPTY_ADDRESS);
                ModelNode node = Resource.Tools.readModel(resource);
                launchServices(context, node);
                // Rollback handled by the parent step
                context.completeStep(OperationContext.RollbackHandler.NOOP_ROLLBACK_HANDLER);
            }
        }, OperationContext.Stage.RUNTIME);
    }

    @Override
    protected void populateModel(final OperationContext context, final ModelNode operation, final Resource resource) throws OperationFailedException {
        super.populateModel(context, operation, resource);
        final ModelNode model = resource.getModel();
        ConfigValidator.validateConfig(context, model);
    }

    protected void launchServices(final OperationContext context, final ModelNode model) throws OperationFailedException {

        IIOPLogger.ROOT_LOGGER.activatingSubsystem();

        // set the ORBUseDynamicStub system property.
        WildFlySecurityManager.setPropertyPrivileged("org.jboss.com.sun.CORBA.ORBUseDynamicStub", "true");
        // we set the same stub factory to both the static and dynamic stub factory. As there is no way to dynamically change
        // the userDynamicStubs's property at runtime it is possible for the ORB class's <clinit> method to be
        // called before this property is set.
        // TODO: investigate a better way to handle this
        com.sun.corba.se.spi.orb.ORB.getPresentationManager().setStubFactoryFactory(true,
                new DelegatingStubFactoryFactory());
        com.sun.corba.se.spi.orb.ORB.getPresentationManager().setStubFactoryFactory(false,
                new DelegatingStubFactoryFactory());

        // setup naming.
        InitialContext.addUrlContextFactory("corbaloc", JBossCNCtxFactory.INSTANCE);
        InitialContext.addUrlContextFactory("corbaname", JBossCNCtxFactory.INSTANCE);
        InitialContext.addUrlContextFactory("IOR", JBossCNCtxFactory.INSTANCE);
        InitialContext.addUrlContextFactory("iiopname", JBossCNCtxFactory.INSTANCE);
        InitialContext.addUrlContextFactory("iiop", JBossCNCtxFactory.INSTANCE);

        context.addStep(new AbstractDeploymentChainStep() {
            public void execute(DeploymentProcessorTarget processorTarget) {
                processorTarget.addDeploymentProcessor(IIOPExtension.SUBSYSTEM_NAME, Phase.DEPENDENCIES,
                        Phase.DEPENDENCIES_IIOP_OPENJDK, new IIOPDependencyProcessor());
                processorTarget.addDeploymentProcessor(IIOPExtension.SUBSYSTEM_NAME, Phase.PARSE, Phase.PARSE_IIOP_OPENJDK,
                        new IIOPMarkerProcessor());
                processorTarget.addDeploymentProcessor(IIOPExtension.SUBSYSTEM_NAME, Phase.INSTALL, Phase.INSTALL_IIOP_CLEAR_CACHES,
                        new IIOPClearCachesProcessor());
            }
        }, OperationContext.Stage.RUNTIME);

        // get the configured ORB properties.
        Properties props = this.getConfigurationProperties(context, model);

        // setup the ORB initializers using the configured properties.
        this.setupInitializers(props);

        // setup the SSL socket factories, if necessary.
        final boolean sslConfigured = this.setupSSLFactories(props);

        // create the service that initializes and starts the CORBA ORB.
        CorbaORBService orbService = new CorbaORBService(props);
        final ServiceBuilder<ORB> builder = context.getServiceTarget().addService(CorbaORBService.SERVICE_NAME, orbService);
        org.jboss.as.server.Services.addServerExecutorDependency(builder, orbService.getExecutorInjector());

        // if a security domain has been specified, add a dependency to the domain service.
        String securityDomain = props.getProperty(Constants.SECURITY_SECURITY_DOMAIN);
        if (securityDomain != null) {
            builder.requires(context.getCapabilityServiceName(Capabilities.LEGACY_SECURITY_DOMAIN_CAPABILITY, securityDomain, null));
            builder.requires(DefaultNamespaceContextSelectorService.SERVICE_NAME);
        }

        // add dependencies to the ssl context services if needed.
        final String serverSSLContextName = props.getProperty(Constants.SERVER_SSL_CONTEXT);
        if (serverSSLContextName != null) {
            ServiceName serverContextServiceName = context.getCapabilityServiceName(Capabilities.SSL_CONTEXT_CAPABILITY, serverSSLContextName, SSLContext.class);
            builder.requires(serverContextServiceName);
        }
        final String clientSSLContextName = props.getProperty(Constants.CLIENT_SSL_CONTEXT);
        if (clientSSLContextName != null) {
            ServiceName clientContextServiceName = context.getCapabilityServiceName(Capabilities.SSL_CONTEXT_CAPABILITY, clientSSLContextName, SSLContext.class);
            builder.requires(clientContextServiceName);
        }

        // if an authentication context has ben specified, add a dependency to its service.
        final String authContext = props.getProperty(Constants.ORB_INIT_AUTH_CONTEXT);
        if (authContext != null) {
            ServiceName authContextServiceName = context.getCapabilityServiceName(Capabilities.AUTH_CONTEXT_CAPABILITY, authContext, AuthenticationContext.class);
            builder.requires(authContextServiceName);
        }

        final boolean serverRequiresSsl = IIOPRootDefinition.SERVER_REQUIRES_SSL.resolveModelAttribute(context, model).asBoolean();

        // inject the socket bindings that specify IIOP and IIOP/SSL ports.
        String socketBinding = props.getProperty(Constants.ORB_SOCKET_BINDING);
        if (socketBinding != null) {
            if (!serverRequiresSsl) {
                builder.addDependency(SocketBinding.JBOSS_BINDING_NAME.append(socketBinding), SocketBinding.class,
                        orbService.getIIOPSocketBindingInjector());
            } else {
                IIOPLogger.ROOT_LOGGER.wontUseCleartextSocket();
            }
        }

        String sslSocketBinding = props.getProperty(Constants.ORB_SSL_SOCKET_BINDING);
        if(sslSocketBinding != null) {
            builder.addDependency(SocketBinding.JBOSS_BINDING_NAME.append(sslSocketBinding), SocketBinding.class,
                    orbService.getIIOPSSLSocketBindingInjector());
        }

        // create the IOR security config metadata service.
        final IORSecurityConfigMetaData securityConfigMetaData = this.createIORSecurityConfigMetaData(context,
                model, sslConfigured, serverRequiresSsl);
        final IORSecConfigMetaDataService securityConfigMetaDataService = new IORSecConfigMetaDataService(securityConfigMetaData);
        context.getServiceTarget()
                .addService(IORSecConfigMetaDataService.SERVICE_NAME, securityConfigMetaDataService)
                .setInitialMode(ServiceController.Mode.ACTIVE).install();

        builder.requires(IORSecConfigMetaDataService.SERVICE_NAME);

        // set the initial mode and install the service.
        builder.setInitialMode(ServiceController.Mode.ACTIVE).install();

        // create the service the initializes the Root POA.
        CorbaPOAService rootPOAService = new CorbaPOAService("RootPOA", "poa", serverRequiresSsl);
        context.getServiceTarget().addService(CorbaPOAService.ROOT_SERVICE_NAME, rootPOAService)
                .addDependency(CorbaORBService.SERVICE_NAME, ORB.class, rootPOAService.getORBInjector())
                .setInitialMode(ServiceController.Mode.ACTIVE).install();

        // create the service the initializes the interface repository POA.
        final CorbaPOAService irPOAService = new CorbaPOAService("IRPOA", "irpoa", serverRequiresSsl, IdAssignmentPolicyValue.USER_ID, null, null,
                LifespanPolicyValue.PERSISTENT, null, null, null);
        context.getServiceTarget()
                .addService(CorbaPOAService.INTERFACE_REPOSITORY_SERVICE_NAME, irPOAService)
                .addDependency(CorbaPOAService.ROOT_SERVICE_NAME, POA.class, irPOAService.getParentPOAInjector())
                .setInitialMode(ServiceController.Mode.ACTIVE).install();

        // create the service that initializes the naming service POA.
        final CorbaPOAService namingPOAService = new CorbaPOAService("Naming", null, serverRequiresSsl, IdAssignmentPolicyValue.USER_ID, null,
                null, LifespanPolicyValue.PERSISTENT, null, null, null);
        context.getServiceTarget()
                .addService(CorbaPOAService.SERVICE_NAME.append("namingpoa"), namingPOAService)
                .addDependency(CorbaPOAService.ROOT_SERVICE_NAME, POA.class, namingPOAService.getParentPOAInjector())
                .setInitialMode(ServiceController.Mode.ACTIVE).install();

        // create the CORBA naming service.
        final CorbaNamingService namingService = new CorbaNamingService(props);
        context
                .getServiceTarget()
                .addService(CorbaNamingService.SERVICE_NAME, namingService)
                .addDependency(CorbaORBService.SERVICE_NAME, ORB.class, namingService.getORBInjector())
                .addDependency(CorbaPOAService.ROOT_SERVICE_NAME, POA.class, namingService.getRootPOAInjector())
                .addDependency(CorbaPOAService.SERVICE_NAME.append("namingpoa"), POA.class,
                        namingService.getNamingPOAInjector())
                .setInitialMode(ServiceController.Mode.ACTIVE).install();

        configureClientSecurity(props);
    }

    /**
     *

     * Obtains the subsystem configuration properties from the specified {@code ModelNode}, using default values for undefined
     * properties. If the property has a IIOP equivalent, it is translated into its IIOP counterpart before being added to
     * the returned {@code Properties} object.
     *

     *
     * @param model the {@code ModelNode} that contains the subsystem configuration properties.
     * @return a {@code Properties} instance containing all configured subsystem properties.
     * @throws OperationFailedException if an error occurs while resolving the properties.
     */
    protected Properties getConfigurationProperties(OperationContext context, ModelNode model) throws OperationFailedException {
        Properties props = new Properties();

        getResourceProperties(props, IIOPRootDefinition.INSTANCE, context, model);

        // check if the node contains a list of generic properties.
        ModelNode configNode = model.get(Constants.CONFIGURATION);
        if (configNode.hasDefined(Constants.PROPERTIES)) {
            for (Property property : configNode.get(Constants.PROPERTIES).get(Constants.PROPERTY)
                    .asPropertyList()) {
                String name = property.getName();
                String value = property.getValue().get(Constants.PROPERTY_VALUE).asString();
                props.setProperty(name, value);
            }
        }
        return props;
    }

    private void getResourceProperties(final Properties properties, PersistentResourceDefinition resource,
            OperationContext context, ModelNode model) throws OperationFailedException {
        for (AttributeDefinition attrDefinition : resource.getAttributes()) {
            if(attrDefinition instanceof PropertiesAttributeDefinition){
                PropertiesAttributeDefinition pad=(PropertiesAttributeDefinition)attrDefinition;
                ModelNode resolvedModelAttribute = attrDefinition.resolveModelAttribute(context, model);
                if(resolvedModelAttribute.isDefined()) {
                    for (final Property prop : resolvedModelAttribute.asPropertyList()) {
                        properties.setProperty(prop.getName(), prop.getValue().asString());
                    }
                }
                continue;
            }
            ModelNode resolvedModelAttribute = attrDefinition.resolveModelAttribute(context, model);
            //FIXME
            if (resolvedModelAttribute.isDefined()) {
                String name = attrDefinition.getName();
                String value = resolvedModelAttribute.asString();

                String openjdkProperty = PropertiesMap.PROPS_MAP.get(name);
                if (openjdkProperty != null) {
                    name = openjdkProperty;
                }
                properties.setProperty(name, value);
            }
        }
    }

    /**
     *

     * Sets up the ORB initializers according to what has been configured in the subsystem.
     *

     *
     * @param props the subsystem configuration properties.
     */
    private void setupInitializers(Properties props) {
        List<String> orbInitializers = new ArrayList<String>();

        // check which groups of initializers are to be installed.
        String installSecurity = (String) props.remove(Constants.ORB_INIT_SECURITY);
        if (installSecurity.equalsIgnoreCase(Constants.CLIENT)) {
            orbInitializers.addAll(Arrays.asList(IIOPInitializer.SECURITY_CLIENT.getInitializerClasses()));
        } else if (installSecurity.equalsIgnoreCase(Constants.IDENTITY)) {
            orbInitializers.addAll(Arrays.asList(IIOPInitializer.SECURITY_IDENTITY.getInitializerClasses()));
        } else if (installSecurity.equalsIgnoreCase(Constants.ELYTRON)) {
            final String authContext = props.getProperty(Constants.ORB_INIT_AUTH_CONTEXT);
            ElytronSASClientInterceptor.setAuthenticationContextName(authContext);
            orbInitializers.addAll(Arrays.asList(IIOPInitializer.SECURITY_ELYTRON.getInitializerClasses()));
        }

        String installTransaction = (String) props.remove(Constants.ORB_INIT_TRANSACTIONS);
        if (installTransaction.equalsIgnoreCase(Constants.FULL)) {
            orbInitializers.addAll(Arrays.asList(IIOPInitializer.TRANSACTIONS.getInitializerClasses()));
        } else if (installTransaction.equalsIgnoreCase(Constants.SPEC)) {
            orbInitializers.addAll(Arrays.asList(IIOPInitializer.SPEC_TRANSACTIONS.getInitializerClasses()));
        }

        // add the standard opendk initializer plus all configured initializers.
        for (String initializerClass : orbInitializers) {
            props.setProperty(Constants.ORB_INITIALIZER_PREFIX + initializerClass, "");
        }
    }

    /**
     *

     * Sets up the SSL domain socket factories if SSL support has been enabled.
     *

     *
     * @param props the subsystem configuration properties.
     * @return true if ssl has been configured
     * @throws OperationFailedException if the SSL setup has not been done correctly (SSL support has been turned on but no
     *         security domain has been specified).
     */
    private boolean setupSSLFactories(final Properties props) throws OperationFailedException {
        final boolean supportSSL = "true".equalsIgnoreCase(props.getProperty(Constants.SECURITY_SUPPORT_SSL));

        final boolean sslConfigured;
        if (supportSSL) {
            // if the config is using Elytron supplied SSL contexts, install the SSLSocketFactory.
            final String serverSSLContextName = props.getProperty(Constants.SERVER_SSL_CONTEXT);
            final String clientSSLContextName = props.getProperty(Constants.CLIENT_SSL_CONTEXT);
            if (serverSSLContextName != null && clientSSLContextName != null) {
                SSLSocketFactory.setServerSSLContextName(serverSSLContextName);
                SSLSocketFactory.setClientSSLContextName(clientSSLContextName);
                props.setProperty(ORBConstants.SOCKET_FACTORY_CLASS_PROPERTY, SSLSocketFactory.class.getName());
            }
            else {
                // if the config only has a legacy JSSE domain reference, install the LegacySSLSocketFactory.
                final String securityDomain = props.getProperty(Constants.SECURITY_SECURITY_DOMAIN);
                LegacySSLSocketFactory.setSecurityDomain(securityDomain);
                props.setProperty(ORBConstants.SOCKET_FACTORY_CLASS_PROPERTY, LegacySSLSocketFactory.class.getName());
            }
            sslConfigured = true;
        } else {
            props.setProperty(ORBConstants.SOCKET_FACTORY_CLASS_PROPERTY, NoSSLSocketFactory.class.getName());
            sslConfigured = false;
        }

        return sslConfigured;
    }

    private IORSecurityConfigMetaData createIORSecurityConfigMetaData(final OperationContext context, final ModelNode resourceModel, final boolean sslConfigured, final boolean serverRequiresSsl)
            throws OperationFailedException {
        final IORSecurityConfigMetaData securityConfigMetaData = new IORSecurityConfigMetaData();

        final IORSASContextMetaData sasContextMetaData = new IORSASContextMetaData();
        sasContextMetaData.setCallerPropagation(IIOPRootDefinition.CALLER_PROPAGATION.resolveModelAttribute(context, resourceModel).asString());
        securityConfigMetaData.setSasContext(sasContextMetaData);

        final IORASContextMetaData asContextMetaData = new IORASContextMetaData();
        asContextMetaData.setAuthMethod(IIOPRootDefinition.AUTH_METHOD.resolveModelAttribute(context, resourceModel).asString());
        if (resourceModel.hasDefined(IIOPRootDefinition.REALM.getName())) {
            asContextMetaData.setRealm(IIOPRootDefinition.REALM.resolveModelAttribute(context, resourceModel).asString());
        }
        asContextMetaData.setRequired(IIOPRootDefinition.REQUIRED.resolveModelAttribute(context, resourceModel).asBoolean());
        securityConfigMetaData.setAsContext(asContextMetaData);

        final IORTransportConfigMetaData transportConfigMetaData = new IORTransportConfigMetaData();
        final ModelNode integrityNode = IIOPRootDefinition.INTEGRITY.resolveModelAttribute(context, resourceModel);
        if(integrityNode.isDefined()){
            transportConfigMetaData.setIntegrity(integrityNode.asString());
        } else {
            transportConfigMetaData.setIntegrity(sslConfigured ? (serverRequiresSsl ? Constants.IOR_REQUIRED : Constants.IOR_SUPPORTED) : Constants.NONE);
        }

        final ModelNode confidentialityNode = IIOPRootDefinition.CONFIDENTIALITY.resolveModelAttribute(context, resourceModel);
        if(confidentialityNode.isDefined()){
            transportConfigMetaData.setConfidentiality(confidentialityNode.asString());
        } else {
            transportConfigMetaData.setConfidentiality(sslConfigured ? (serverRequiresSsl ? Constants.IOR_REQUIRED: Constants.IOR_SUPPORTED) : Constants.IOR_NONE);
        }

        final ModelNode establishTrustInTargetNode = IIOPRootDefinition.TRUST_IN_TARGET.resolveModelAttribute(context, resourceModel);
        if (establishTrustInTargetNode.isDefined()) {
            transportConfigMetaData.setEstablishTrustInTarget(confidentialityNode.asString());
        } else {
            transportConfigMetaData.setEstablishTrustInTarget(sslConfigured ? Constants.IOR_SUPPORTED : Constants.NONE);
        }

        final ModelNode establishTrustInClientNode = IIOPRootDefinition.TRUST_IN_CLIENT.resolveModelAttribute(context, resourceModel);
        if(establishTrustInClientNode.isDefined()){
            transportConfigMetaData.setEstablishTrustInClient(establishTrustInClientNode.asString());
        } else {
            transportConfigMetaData.setEstablishTrustInClient(sslConfigured ? (serverRequiresSsl ? Constants.IOR_REQUIRED : Constants.IOR_SUPPORTED) : Constants.NONE);
        }

        transportConfigMetaData.setDetectMisordering(Constants.IOR_SUPPORTED);
        transportConfigMetaData.setDetectReplay(Constants.IOR_SUPPORTED);

        securityConfigMetaData.setTransportConfig(transportConfigMetaData);
        return securityConfigMetaData;
    }

    private void configureClientSecurity(final Properties props) {
        final boolean clientRequiresSSL = Boolean.getBoolean(props.getProperty(Constants.SECURITY_CLIENT_REQUIRES_SSL));
        CSIV2IORToSocketInfo.setClientRequiresSSL(clientRequiresSSL);
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.security.service;

import java.lang.reflect.Constructor;
import java.security.Policy;
import java.util.Properties;
import java.util.Set;

import javax.security.jacc.PolicyContext;

import org.jboss.as.naming.ServiceBasedNamingStore;
import org.jboss.as.naming.ValueManagedReferenceFactory;
import org.jboss.as.naming.deployment.ContextNames;
import org.jboss.as.naming.service.BinderService;
import org.jboss.as.security.SecurityExtension;
import org.jboss.as.security.logging.SecurityLogger;
import org.jboss.as.security.plugins.ModuleClassLoaderLocator;
import org.jboss.as.server.moduleservice.ServiceModuleLoader;
import org.jboss.modules.ModuleLoadException;
import org.jboss.msc.inject.Injector;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.jboss.msc.value.InjectedValue;
import org.jboss.msc.value.Values;
import org.jboss.security.SecurityConstants;
import org.jboss.security.auth.callback.CallbackHandlerPolicyContextHandler;
import org.jboss.security.jacc.SubjectPolicyContextHandler;
import org.jboss.security.plugins.ClassLoaderLocatorFactory;
import org.jboss.security.plugins.JBossPolicyRegistration;
import org.wildfly.security.manager.WildFlySecurityManager;

/**
 * Bootstrap service for the security container
 *
 * @author Marcus Moyses
 * @author Anil Saldhana
 */
public class SecurityBootstrapService implements Service<Void> {

    static final String JACC_MODULE = "org.jboss.as.security.jacc-module";

    public static final ServiceName SERVICE_NAME = SecurityExtension.JBOSS_SECURITY.append("bootstrap");

    private static final SecurityLogger log = SecurityLogger.ROOT_LOGGER;

    private final InjectedValue<ServiceModuleLoader> moduleLoaderValue = new InjectedValue<ServiceModuleLoader>();

    protected volatile Properties securityProperty;

    private Policy oldPolicy;

    /**
     * This is static because we want to keep using the same one after reload
     *
     * see https://issues.jboss.org/browse/WFLY-10066
     */
    private static volatile Policy jaccPolicy;

    private final boolean initializeJacc;

    private static final String JACC_POLICY_PROVIDER = "javax.security.jacc.policy.provider";

    private static final String POLICY_REGISTRATION = "policyRegistration";

    public SecurityBootstrapService(boolean initializeJacc) {
        this.initializeJacc = initializeJacc;
    }

    /** {@inheritDoc} */
    @Override
    public void start(StartContext context) throws StartException {
        log.debugf("Starting SecurityBootstrapService");
        //Print out the current version of PicketBox
        SecurityLogger.ROOT_LOGGER.currentVersion(org.picketbox.Version.VERSION);
        initializeJacc();
        setupPolicyRegistration(context);
    }

    private void initializeJacc() throws StartException {
        if (!initializeJacc) {
            SecurityLogger.ROOT_LOGGER.debugf("Legacy subsystem configured to not initialize JACC. If you want JACC support, make sure you have it properly configured in Elytron subsystem.");
            return;
        }
        SecurityLogger.ROOT_LOGGER.debugf("Initializing JACC from legacy subsystem.");
        try {
            // Get the current Policy impl
            oldPolicy = Policy.getPolicy();
            if(jaccPolicy == null) {
                String module = WildFlySecurityManager.getPropertyPrivileged(JACC_MODULE, null);
                String provider = WildFlySecurityManager.getPropertyPrivileged(JACC_POLICY_PROVIDER, "org.jboss.security.jacc.DelegatingPolicy");
                Class<?> providerClass = loadClass(module, provider);
                try {
                    // Look for a ctor(Policy) signature
                    Class<?>[] ctorSig = {Policy.class};
                    Constructor<?> ctor = providerClass.getConstructor(ctorSig);
                    Object[] ctorArgs = {oldPolicy};
                    jaccPolicy = (Policy) ctor.newInstance(ctorArgs);
                } catch (NoSuchMethodException e) {
                    log.debugf("Provider does not support ctor(Policy)");
                    try {
                        jaccPolicy = (Policy) providerClass.newInstance();
                    } catch (Exception e1) {
                        throw SecurityLogger.ROOT_LOGGER.unableToStartException("SecurityBootstrapService", e1);
                    }
                } catch (Exception e) {
                    throw SecurityLogger.ROOT_LOGGER.unableToStartException("SecurityBootstrapService", e);
                }
            }

            // Install the JACC policy provider
            Policy.setPolicy(jaccPolicy);

            // Have the policy load/update itself
            jaccPolicy.refresh();

            // Register the default active Subject PolicyContextHandler
            SubjectPolicyContextHandler handler = new SubjectPolicyContextHandler();
            PolicyContext.registerHandler(SecurityConstants.SUBJECT_CONTEXT_KEY, handler, true);

            // Register the JAAS CallbackHandler JACC PolicyContextHandlers
            CallbackHandlerPolicyContextHandler chandler = new CallbackHandlerPolicyContextHandler();
            PolicyContext.registerHandler(SecurityConstants.CALLBACK_HANDLER_KEY, chandler, true);

            //Register a module classloader locator
            ClassLoaderLocatorFactory.set(new ModuleClassLoaderLocator(moduleLoaderValue.getValue()));
        } catch (Exception e) {
            throw SecurityLogger.ROOT_LOGGER.unableToStartException("SecurityBootstrapService", e);
        }
    }

    private void setupPolicyRegistration(final StartContext context) {
        ServiceTarget target = context.getChildTarget();
        final BinderService binderService = new BinderService(POLICY_REGISTRATION);
        binderService.getManagedObjectInjector().inject(new ValueManagedReferenceFactory(
                Values.immediateValue(new JBossPolicyRegistration())));
        target.addService(ContextNames.buildServiceName(ContextNames.JAVA_CONTEXT_SERVICE_NAME, POLICY_REGISTRATION), binderService)
                .addDependency(ContextNames.JAVA_CONTEXT_SERVICE_NAME, ServiceBasedNamingStore.class, binderService.getNamingStoreInjector())
                .install();
    }

    private Class<?> loadClass(final String module, final String className) throws ClassNotFoundException, ModuleLoadException {
        if (module != null) {
            return SecurityActions.getModuleClassLoader(module).loadClass(className);
        }

        return SecurityActions.loadClass(className);
    }

    /** {@inheritDoc} */
    @SuppressWarnings("rawtypes")
    @Override
    public void stop(StopContext context) {
        // remove handlers
        Set handlerKeys = PolicyContext.getHandlerKeys();
        handlerKeys.remove(SecurityConstants.CALLBACK_HANDLER_KEY);
        handlerKeys.remove(SecurityConstants.SUBJECT_CONTEXT_KEY);

        // Install the policy provider that existed on startup
        if (initializeJacc && jaccPolicy != null)
            Policy.setPolicy(oldPolicy);
    }

    /** {@inheritDoc} */
    @Override
    public Void getValue() throws IllegalStateException {
        return null;
    }

    public Injector<ServiceModuleLoader> getServiceModuleLoaderInjectedValue() {
        return moduleLoaderValue;
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.clustering.controller.transform;

import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.transform.OperationResultTransformer;
import org.jboss.as.controller.transform.OperationTransformer;
import org.jboss.as.controller.transform.TransformationContext;
import org.jboss.dmr.ModelNode;

/**
 * @author Paul Ferraro
 */
public class SimpleRemoveOperationTransformer implements OperationTransformer {

    private final PathAddressTransformer addressTransformer;

    public SimpleRemoveOperationTransformer(PathAddressTransformer addressTransformer) {
        this.addressTransformer = addressTransformer;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TransformedOperation transformOperation(TransformationContext context, PathAddress address, ModelNode operation) {
        ModelNode legacyOperation = Util.createRemoveOperation(this.addressTransformer.transform(address));
        return new TransformedOperation(legacyOperation, OperationResultTransformer.ORIGINAL_RESULT);
    }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.as.weld;

import org.jboss.modules.Module;
import org.jboss.weld.resources.spi.ResourceLoader;
import org.jboss.weld.resources.spi.ResourceLoadingException;

import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A {@link ResourceLoader} that can load classes from a {@link Module}
 * <p>
 * Thread Safety: This class is thread safe, and does not require a happens before even between construction and usage
 *
 * @author Stuart Douglas
 *
 */
public class WeldModuleResourceLoader implements ResourceLoader {

    private final Module module;

    /**
     * Additional classes that have been added to the bean archive by the container or by a portable extension
     */
    private final Map<String, Class<?>> classes;

    public WeldModuleResourceLoader(Module module) {
        this.module = module;
        this.classes = new ConcurrentHashMap<String, Class<?>>();
    }

    /**
     * If the class name is found in additionalClasses then return it.
     *
     * Otherwise the class will be loaded from the module ClassLoader
     */
    @Override
    public Class<?> classForName(String name) {
        try {
            if (classes.containsKey(name)) {
                return classes.get(name);
            }
            final Class<?> clazz = module.getClassLoader().loadClass(name);
            classes.put(name, clazz);
            return clazz;
        } catch (ClassNotFoundException | LinkageError e) {
            throw new ResourceLoadingException(e);
        }
    }

    public void addAdditionalClass(Class<?> clazz) {
        this.classes.put(clazz.getName(), clazz);
    }

    /**
     * Loads a resource from the module class loader
     */
    @Override
    public URL getResource(String name) {
        try {
            return module.getClassLoader().getResource(name);
        } catch (Exception e) {
            throw new ResourceLoadingException(e);
        }
    }

    /**
     * Loads resources from the module class loader
     */
    @Override
    public Collection<URL> getResources(String name) {
        try {
            final HashSet<URL> resources = new HashSet<URL>();
            Enumeration<URL> urls = module.getClassLoader().getResources(name);
            while (urls.hasMoreElements()) {
                resources.add(urls.nextElement());
            }
            return resources;
        } catch (Exception e) {
            throw new ResourceLoadingException(e);
        }

    }

    @Override
    public void cleanup() {
        // nop
    }

}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2017, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.wildfly.extension.undertow.deployment;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.jboss.vfs.VirtualFile;
import io.undertow.server.handlers.resource.PathResourceManager;
import io.undertow.server.handlers.resource.Resource;
import io.undertow.server.handlers.resource.ResourceChangeListener;
import io.undertow.server.handlers.resource.ResourceManager;
import io.undertow.util.CanonicalPathUtils;

/**
 * Resource manager that deals with overlays
 *
 * @author Stuart Douglas
 */
public class ServletResourceManager implements ResourceManager {

    public static final int TRANSFER_MIN_SIZE = 1024 * 1024;
    private final PathResourceManager deploymentResourceManager;
    private final Collection<VirtualFile> overlays;
    private final ResourceManager[] externalOverlays;
    private final boolean explodedDeployment;

    public ServletResourceManager(final VirtualFile resourcesRoot, final Collection<VirtualFile> overlays,
                                  boolean explodedDeployment, boolean followSymlink, boolean disableFileWatchService,
                                  List<String> externalOverlays) throws IOException {
        this.explodedDeployment = explodedDeployment;
        Path physicalFile = resourcesRoot.getPhysicalFile().toPath().toRealPath();
        deploymentResourceManager = new PathResourceManager(physicalFile, TRANSFER_MIN_SIZE, true,
                followSymlink, !disableFileWatchService);
        this.overlays = overlays;
        if(externalOverlays == null) {
            this.externalOverlays = new ResourceManager[0];
        } else {
            this.externalOverlays = new ResourceManager[externalOverlays.size()];
            for (int i = 0; i < externalOverlays.size(); ++i) {
                String path = externalOverlays.get(i);
                PathResourceManager pr = new PathResourceManager(Paths.get(path).toRealPath(), TRANSFER_MIN_SIZE,
                        true, followSymlink, !disableFileWatchService);
                this.externalOverlays[i] = pr;
            }
        }
    }

    @Override
    public Resource getResource(final String path) throws IOException {
        Resource res = deploymentResourceManager.getResource(path);
        if (res != null) {
            return new ServletResource(this, res);
        }
        String p = path;
        if (p.startsWith("/")) {
            p = p.substring(1);
        }
        if (overlays != null) {
            String canonical = CanonicalPathUtils.canonicalize(p); //we don't need to do this for other resources, as the underlying RM will handle it
            for (VirtualFile overlay : overlays) {
                VirtualFile child = overlay.getChild(canonical);
                if (child.exists()) {
                    try {
                        //we make sure the child is actually a child of the parent
                        //CanonicalPathUtils should make sure this cannot happen
                        //but just to be safe we do it anyway
                        child.getPathNameRelativeTo(overlay);
                        return new ServletResource(this, new VirtualFileResource(overlay.getPhysicalFile(), child, canonical));
                    } catch (IllegalArgumentException ignore) {

                    }
                }
            }
        }
        for (int i = 0; i < externalOverlays.length; ++i) {
            ResourceManager manager = externalOverlays[i];
            res = manager.getResource(path);
            if(res != null) {
                return res;
            }
        }
        return null;
    }

    @Override
    public boolean isResourceChangeListenerSupported() {
        return true;
    }

    @Override
    public void registerResourceChangeListener(ResourceChangeListener listener) {
        if(explodedDeployment && deploymentResourceManager.isResourceChangeListenerSupported()) {
            deploymentResourceManager.registerResourceChangeListener(listener);
        }
        for(ResourceManager external : externalOverlays) {
            if(external.isResourceChangeListenerSupported()) {
                external.registerResourceChangeListener(listener);
            }
        }
    }

    @Override
    public void removeResourceChangeListener(ResourceChangeListener listener) {
        if(deploymentResourceManager.isResourceChangeListenerSupported()) {
            deploymentResourceManager.removeResourceChangeListener(listener);
        }
        for(ResourceManager external : externalOverlays) {
            if(external.isResourceChangeListenerSupported()) {
                external.removeResourceChangeListener(listener);
            }
        }
    }

    @Override
    public void close() throws IOException {
        deploymentResourceManager.close();
    }

    /**
     * Lists all children of a particular path, taking overlays into account
     *
     * @param path The path
     * @return The list of children
     */
    public List<Resource> list(String path) {
        try {
            final List<Resource> ret = new ArrayList<>();

            Resource res = deploymentResourceManager.getResource(path);
            if (res != null) {
                for (Resource child : res.list()) {
                    ret.add(new ServletResource(this, child));
                }
            }
            String p = path;
            if (p.startsWith("/")) {
                p = p.substring(1);
            }
            if (overlays != null) {
                for (VirtualFile overlay : overlays) {
                    VirtualFile child = overlay.getChild(p);
                    if (child.exists()) {
                        VirtualFileResource vfsResource = new VirtualFileResource(overlay.getPhysicalFile(), child, path);
                        for (Resource c : vfsResource.list()) {
                            ret.add(new ServletResource(this, c));
                        }
                    }
                }
            }
            return ret;
        } catch (IOException e) {
            throw new RuntimeException(e); //this method really should have thrown IOException
        }
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.connector.deployers.ra.processors;

import org.jboss.as.connector.metadata.xmldescriptors.ConnectorXmlDescriptor;
import org.jboss.as.connector.subsystems.resourceadapters.ResourceAdaptersSubsystemService;
import org.jboss.as.connector.util.ConnectorServices;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.module.ModuleDependency;
import org.jboss.as.server.deployment.module.ModuleSpecification;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoader;

public class RarDependencyProcessor implements DeploymentUnitProcessor {

    private static ModuleIdentifier JMS_ID = ModuleIdentifier.create("javax.jms.api");
    private static ModuleIdentifier IRON_JACAMAR_ID = ModuleIdentifier.create("org.jboss.ironjacamar.api");
    private static ModuleIdentifier IRON_JACAMAR_IMPL_ID = ModuleIdentifier.create("org.jboss.ironjacamar.impl");
    private static ModuleIdentifier VALIDATION_ID = ModuleIdentifier.create("javax.validation.api");
    private static ModuleIdentifier HIBERNATE_VALIDATOR_ID = ModuleIdentifier.create("org.hibernate.validator");
    private static ModuleIdentifier RESOURCE_API_ID = ModuleIdentifier.create("javax.resource.api");

    private final boolean appclient;

    public RarDependencyProcessor(final boolean appclient) {
        this.appclient = appclient;
    }

    /**
     * Add dependencies for modules required for ra deployments
     *
     * @param phaseContext the deployment unit context
     * @throws DeploymentUnitProcessingException
     *
     */
    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {

        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        final ModuleLoader moduleLoader = Module.getBootModuleLoader();
        final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);

        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, RESOURCE_API_ID, false, false, false, false));
        if (phaseContext.getDeploymentUnit().getAttachment(ConnectorXmlDescriptor.ATTACHMENT_KEY) == null) {
            return;  // Skip non ra deployments
        }

        //if a module depends on a rar it also needs a dep on all the rar's "local dependencies"
        moduleSpecification.setLocalDependenciesTransitive(true);
        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, JMS_ID, false, false, false, false));
        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, VALIDATION_ID, false, false, false, false));
        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, IRON_JACAMAR_ID, false, false, false, false));
        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, IRON_JACAMAR_IMPL_ID, false, true, false, false));
        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, HIBERNATE_VALIDATOR_ID, false, false, true, false));
        if (! appclient)
            phaseContext.addDeploymentDependency(ConnectorServices.RESOURCEADAPTERS_SUBSYSTEM_SERVICE, ResourceAdaptersSubsystemService.ATTACHMENT_KEY);
    }

    public void undeploy(final DeploymentUnit context) {
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.jpa.container;

import static org.jboss.as.jpa.messages.JpaLogger.ROOT_LOGGER;

import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManager;

/**
 * Close the non tx invocations on transaction scoped entity manager
 *
 * @author Scott Marlow
 */
public class NonTxEmCloser {

    /**
     * Each thread will have its own list of SB invocations in progress.
     * Key = scoped persistence unit name
     */
    public static final ThreadLocalStack<Map<String, EntityManager>> nonTxStack = new ThreadLocalStack<Map<String, EntityManager>>();

    /**
     * entered new session bean invocation, start new collection for tracking transactional entity managers created
     * without a JTA transaction.
     */
    public static void pushCall() {
        nonTxStack.push(null);          // to conserve memory/cpu cycles, push a null placeholder that will only get replaced
        // with a Map if we actually need it (in add() below).
    }

    /**
     * current session bean invocation is ending, close any transactional entity managers created without a JTA
     * transaction.
     */
    public static void popCall() {
        Map<String, EntityManager> emStack = nonTxStack.pop();
        if (emStack != null) {
            for (EntityManager entityManager : emStack.values()) {
                try {
                    if (entityManager.isOpen()) {
                        entityManager.close();
                    }
                } catch (RuntimeException safeToIgnore) {
                    if (ROOT_LOGGER.isTraceEnabled()) {
                        ROOT_LOGGER.trace("Could not close (non-transactional) container managed entity manager." +
                            "  This shouldn't impact application functionality (only read " +
                            "operations occur in non-transactional mode)", safeToIgnore);
                    }
                }
            }
        }
    }

    /**
     * Return the transactional entity manager for the specified scoped persistence unit name
     *
     * @param puScopedName
     * @return
     */
    public static EntityManager get(String puScopedName) {
        Map<String, EntityManager> map = nonTxStack.peek();
        if (map != null) {
            return map.get(puScopedName);
        }
        return null;
    }

    public static void add(String puScopedName, EntityManager entityManager) {
        Map<String, EntityManager> map = nonTxStack.peek();
        if (map == null && !nonTxStack.isEmpty()) {
            // replace null with a collection to hold the entity managers.
            map = new HashMap<String, EntityManager>();
            nonTxStack.pop();
            nonTxStack.push(map);    // replace top of stack (currently null) with new collection
        }
        if (map != null) {
            map.put(puScopedName, entityManager);
        }
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2016, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.wildfly.extension.messaging.activemq.deployment.injection;

import javax.jms.JMSContext;
import javax.transaction.Synchronization;
import javax.transaction.TransactionScoped;
import javax.transaction.TransactionSynchronizationRegistry;

import static org.wildfly.extension.messaging.activemq.logging.MessagingLogger.ROOT_LOGGER;

/**
 * Injection of JMSContext in the @TransactionScoped scope.
 *
 * @author Jeff Mesnil (c) 2016 Red Hat inc.
 */
@TransactionScoped
class TransactedJMSContext extends AbstractJMSContext {

    /**
     * Closing of transaction scoped JMSContext is executed through Synchronization listener.
     * This method registers listener, which takes care of closing JMSContext.
     *
     * @param transactionSynchronizationRegistry
     * @param contextInstance
     */
    void registerCleanUpListener(TransactionSynchronizationRegistry transactionSynchronizationRegistry, JMSContext contextInstance) {
        //to avoid registration of more listeners for one context, flag in transaction is used.
        Object alreadyRegistered = transactionSynchronizationRegistry.getResource(contextInstance);
        if (alreadyRegistered == null) {
            transactionSynchronizationRegistry.registerInterposedSynchronization(new AfterCompletionSynchronization(contextInstance));
            transactionSynchronizationRegistry.putResource(contextInstance, AfterCompletionSynchronization.class.getName());
        }
    }

    /**
     * Synchronization task, which executes "cleanup" on JMSContext in AfterCompletion phase. BeforeCompletion does nothing.
     */
    private class AfterCompletionSynchronization implements Synchronization {
        final JMSContext context;

        public AfterCompletionSynchronization(JMSContext context) {
            this.context = context;
        }

        @Override
        public void beforeCompletion() {
            //intentionally blank
        }

        @Override
        public void afterCompletion(int status) {
            ROOT_LOGGER.debugf("Clean up JMSContext created from %s", TransactedJMSContext.this);
            context.close();
        }
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.ejb3.subsystem;

import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.parsing.ParseUtils;
import org.jboss.as.threads.Namespace;
import org.jboss.as.threads.ThreadsParser;
import org.jboss.dmr.ModelNode;
import org.jboss.staxmapper.XMLElementReader;
import org.jboss.staxmapper.XMLExtendedStreamReader;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;

import java.util.Collections;
import java.util.EnumSet;
import java.util.List;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import static org.jboss.as.controller.parsing.ParseUtils.missingRequired;
import static org.jboss.as.controller.parsing.ParseUtils.readStringAttributeElement;
import static org.jboss.as.controller.parsing.ParseUtils.requireNoAttributes;
import static org.jboss.as.controller.parsing.ParseUtils.requireNoContent;
import static org.jboss.as.controller.parsing.ParseUtils.requireNoNamespaceAttribute;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedAttribute;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.ASYNC;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.CACHE;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.CLUSTER_PASSIVATION_STORE;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.DEFAULT_DATA_STORE;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.FILE_DATA_STORE;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.FILE_PASSIVATION_STORE;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.IIOP;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.PATH;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.RELATIVE_TO;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.REMOTE;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.SERVICE;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.STRICT_MAX_BEAN_INSTANCE_POOL;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.THREAD_POOL;
import static org.jboss.as.ejb3.subsystem.EJB3SubsystemModel.TIMER_SERVICE;

/**
 * @author Jaikiran Pai
 */
public class EJB3Subsystem12Parser implements XMLElementReader<List<ModelNode>> {

    protected static final PathAddress SUBSYSTEM_PATH = PathAddress.pathAddress(PathElement.pathElement(SUBSYSTEM, EJB3Extension.SUBSYSTEM_NAME));

    protected EJB3Subsystem12Parser() {
    }

    protected void readAttributes(final XMLExtendedStreamReader reader) throws XMLStreamException {
        for (int i = 0; i < reader.getAttributeCount(); i++) {
            ParseUtils.requireNoNamespaceAttribute(reader, i);
            throw ParseUtils.unexpectedAttribute(reader, i);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void readElement(final XMLExtendedStreamReader reader, final List<ModelNode> operations) throws XMLStreamException {
        final ModelNode ejb3SubsystemAddOperation = Util.createAddOperation(SUBSYSTEM_PATH);
        operations.add(ejb3SubsystemAddOperation);
        readAttributes(reader);
        // elements
        final EnumSet<EJB3SubsystemXMLElement> encountered = EnumSet.noneOf(EJB3SubsystemXMLElement.class);
        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            if (EJB3SubsystemNamespace.forUri(reader.getNamespaceURI()) != getExpectedNamespace()) {
                throw unexpectedElement(reader);
            }
            final EJB3SubsystemXMLElement element = EJB3SubsystemXMLElement.forName(reader.getLocalName());
            if (!encountered.add(element)) {
                throw unexpectedElement(reader);
            }
            readElement(reader, element, operations, ejb3SubsystemAddOperation);
        }
    }

    protected void readElement(final XMLExtendedStreamReader reader, final EJB3SubsystemXMLElement element, final List<ModelNode> operations, final ModelNode ejb3SubsystemAddOperation) throws XMLStreamException {
        switch (element) {
            case CACHES: {
                this.parseCaches(reader, operations);
                break;
            }
            case PASSIVATION_STORES: {
                this.parsePassivationStores(reader, operations);
                break;
            }
            case MDB: {
                // read <mdb>
                this.parseMDB(reader, operations, ejb3SubsystemAddOperation);
                break;
            }
            case ENTITY_BEAN: {
                // read <entity-bean>
                this.parseEntityBean(reader, operations, ejb3SubsystemAddOperation);
                break;
            }
            case POOLS: {
                // read <pools>
                this.parsePools(reader, operations);
                break;
            }
            case REMOTE: {
                // read <remote>
                parseRemote(reader, operations);
                break;
            }
            case ASYNC: {
                // read <remote>
                parseAsync(reader, operations);
                break;
            }
            case SESSION_BEAN: {
                // read <session-bean>
                this.parseSessionBean(reader, operations, ejb3SubsystemAddOperation);
                break;
            }
            case TIMER_SERVICE: {
                parseTimerService(reader, operations);
                break;
            }
            case THREAD_POOLS: {
                parseThreadPools(reader, operations);
                break;
            }
            case IIOP: {
                parseIIOP(reader, operations);
                break;
            }
            case IN_VM_REMOTE_INTERFACE_INVOCATION:
                parseInVMRemoteInterfaceInvocation(reader, ejb3SubsystemAddOperation);
                break;
            default: {
                throw unexpectedElement(reader);
            }
        }
    }

    protected void parseRemote(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {
        final int count = reader.getAttributeCount();
        ModelNode operation = Util.createAddOperation(SUBSYSTEM_PATH.append(SERVICE, REMOTE));
        final EnumSet<EJB3SubsystemXMLAttribute> required = EnumSet.of(EJB3SubsystemXMLAttribute.CONNECTOR_REF, EJB3SubsystemXMLAttribute.THREAD_POOL_NAME);
        for (int i = 0; i < count; i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            final EJB3SubsystemXMLAttribute attribute = EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i));
            required.remove(attribute);
            switch (attribute) {
                case CONNECTOR_REF:
                    EJB3RemoteResourceDefinition.CONNECTOR_REF.parseAndSetParameter(value, operation, reader);
                    break;
                case THREAD_POOL_NAME:
                    EJB3RemoteResourceDefinition.THREAD_POOL_NAME.parseAndSetParameter(value, operation, reader);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (!required.isEmpty()) {
            throw missingRequired(reader, required);
        }
        operation.get(EJB3SubsystemModel.EXECUTE_IN_WORKER).set(new ModelNode(false));
        requireNoContent(reader);
        operations.add(operation);
    }

    private void parseAsync(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {
        final int count = reader.getAttributeCount();
        //String threadPoolName = null;
        ModelNode operation = Util.createAddOperation(SUBSYSTEM_PATH.append(SERVICE, ASYNC));
        final EnumSet<EJB3SubsystemXMLAttribute> required = EnumSet.of(EJB3SubsystemXMLAttribute.THREAD_POOL_NAME);
        for (int i = 0; i < count; i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            final EJB3SubsystemXMLAttribute attribute = EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i));
            required.remove(attribute);
            switch (attribute) {
                case THREAD_POOL_NAME:
                    EJB3AsyncResourceDefinition.THREAD_POOL_NAME.parseAndSetParameter(value, operation, reader);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (!required.isEmpty()) {
            throw missingRequired(reader, required);
        }

        requireNoContent(reader);
        operations.add(operation);
    }

    private void parseIIOP(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {
        ModelNode operation = Util.createAddOperation(SUBSYSTEM_PATH.append(SERVICE, IIOP));
        final int count = reader.getAttributeCount();
        final EnumSet<EJB3SubsystemXMLAttribute> required = EnumSet.of(EJB3SubsystemXMLAttribute.ENABLE_BY_DEFAULT, EJB3SubsystemXMLAttribute.USE_QUALIFIED_NAME);
        for (int i = 0; i < count; i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            final EJB3SubsystemXMLAttribute attribute = EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i));
            required.remove(attribute);
            switch (attribute) {
                case ENABLE_BY_DEFAULT:
                    EJB3IIOPResourceDefinition.ENABLE_BY_DEFAULT.parseAndSetParameter(value,operation,reader);
                    break;
                case USE_QUALIFIED_NAME:
                    EJB3IIOPResourceDefinition.USE_QUALIFIED_NAME.parseAndSetParameter(value,operation,reader);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (!required.isEmpty()) {
            throw missingRequired(reader, required);
        }
        requireNoContent(reader);
        operations.add(operation);
    }

    protected void parseMDB(final XMLExtendedStreamReader reader, List<ModelNode> operations, final ModelNode ejb3SubsystemAddOperation) throws XMLStreamException {
        // no attributes expected
        requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            switch (EJB3SubsystemXMLElement.forName(reader.getLocalName())) {
                case BEAN_INSTANCE_POOL_REF: {
                    final String poolName = readStringAttributeElement(reader, EJB3SubsystemXMLAttribute.POOL_NAME.getLocalName());
                    EJB3SubsystemRootResourceDefinition.DEFAULT_MDB_INSTANCE_POOL.parseAndSetParameter(poolName, ejb3SubsystemAddOperation, reader);
                    break;
                }
                case RESOURCE_ADAPTER_REF: {
                    final String resourceAdapterName = readStringAttributeElement(reader, EJB3SubsystemXMLAttribute.RESOURCE_ADAPTER_NAME.getLocalName());
                    EJB3SubsystemRootResourceDefinition.DEFAULT_RESOURCE_ADAPTER_NAME.parseAndSetParameter(resourceAdapterName, ejb3SubsystemAddOperation, reader);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    private void parseEntityBean(final XMLExtendedStreamReader reader, List<ModelNode> operations, final ModelNode ejb3SubsystemAddOperation) throws XMLStreamException {
        // no attributes expected
        requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            switch (EJB3SubsystemXMLElement.forName(reader.getLocalName())) {
                case BEAN_INSTANCE_POOL_REF: {
                    final String poolName = readStringAttributeElement(reader, EJB3SubsystemXMLAttribute.POOL_NAME.getLocalName());
                    EJB3SubsystemRootResourceDefinition.DEFAULT_ENTITY_BEAN_INSTANCE_POOL.parseAndSetParameter(poolName, ejb3SubsystemAddOperation, reader);
                    break;
                }
                case OPTIMISTIC_LOCKING: {
                    final String enabled = readStringAttributeElement(reader, EJB3SubsystemXMLAttribute.ENABLED.getLocalName());
                    EJB3SubsystemRootResourceDefinition.DEFAULT_ENTITY_BEAN_OPTIMISTIC_LOCKING.parseAndSetParameter(enabled, ejb3SubsystemAddOperation, reader);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }

    }

    private void parseSessionBean(final XMLExtendedStreamReader reader, final List<ModelNode> operations, ModelNode ejb3SubsystemAddOperation) throws XMLStreamException {
        // no attributes expected
        requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            switch (EJB3SubsystemXMLElement.forName(reader.getLocalName())) {
                case STATELESS: {
                    this.parseStatelessBean(reader, operations, ejb3SubsystemAddOperation);
                    break;
                }
                case STATEFUL: {
                    this.parseStatefulBean(reader, operations, ejb3SubsystemAddOperation);
                    break;
                }
                case SINGLETON: {
                    this.parseSingletonBean(reader, operations, ejb3SubsystemAddOperation);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    private void parseStatelessBean(final XMLExtendedStreamReader reader, final List<ModelNode> operations, ModelNode ejb3SubsystemAddOperation) throws XMLStreamException {
        // no attributes expected
        requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            switch (EJB3SubsystemXMLElement.forName(reader.getLocalName())) {
                case BEAN_INSTANCE_POOL_REF: {
                    final String poolName = readStringAttributeElement(reader, EJB3SubsystemXMLAttribute.POOL_NAME.getLocalName());
                    EJB3SubsystemRootResourceDefinition.DEFAULT_SLSB_INSTANCE_POOL.parseAndSetParameter(poolName, ejb3SubsystemAddOperation, reader);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    protected void parseStatefulBean(final XMLExtendedStreamReader reader, final List<ModelNode> operations, final ModelNode ejb3SubsystemAddOperation) throws XMLStreamException {
        final int count = reader.getAttributeCount();
        final EnumSet<EJB3SubsystemXMLAttribute> missingRequiredAttributes = EnumSet.of(EJB3SubsystemXMLAttribute.DEFAULT_ACCESS_TIMEOUT, EJB3SubsystemXMLAttribute.CACHE_REF);
        for (int i = 0; i < count; i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            final EJB3SubsystemXMLAttribute attribute = EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i));
            switch (attribute) {
                case DEFAULT_ACCESS_TIMEOUT: {
                    EJB3SubsystemRootResourceDefinition.DEFAULT_STATEFUL_BEAN_ACCESS_TIMEOUT.parseAndSetParameter(value, ejb3SubsystemAddOperation, reader);
                    break;
                }
                case CACHE_REF: {
                    EJB3SubsystemRootResourceDefinition.DEFAULT_SFSB_CACHE.parseAndSetParameter(value, ejb3SubsystemAddOperation, reader);
                    break;
                }
                case CLUSTERED_CACHE_REF: {
                    EJB3SubsystemRootResourceDefinition.DEFAULT_CLUSTERED_SFSB_CACHE.parseAndSetParameter(value, ejb3SubsystemAddOperation, reader);
                    break;
                }
                default: {
                    throw unexpectedAttribute(reader, i);
                }
            }
            // found the mandatory attribute
            missingRequiredAttributes.remove(attribute);
        }
        requireNoContent(reader);
        if (!missingRequiredAttributes.isEmpty()) {
            throw missingRequired(reader, missingRequiredAttributes);
        }
    }

    private void parseSingletonBean(final XMLExtendedStreamReader reader, final List<ModelNode> operations, final ModelNode ejb3SubsystemAddOperation) throws XMLStreamException {
        final int count = reader.getAttributeCount();
        final EnumSet<EJB3SubsystemXMLAttribute> missingRequiredAttributes = EnumSet.of(EJB3SubsystemXMLAttribute.DEFAULT_ACCESS_TIMEOUT);
        for (int i = 0; i < count; i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            final EJB3SubsystemXMLAttribute attribute = EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i));
            switch (attribute) {
                case DEFAULT_ACCESS_TIMEOUT:
                    EJB3SubsystemRootResourceDefinition.DEFAULT_SINGLETON_BEAN_ACCESS_TIMEOUT.parseAndSetParameter(value, ejb3SubsystemAddOperation, reader);
                    // found the mandatory attribute
                    missingRequiredAttributes.remove(EJB3SubsystemXMLAttribute.DEFAULT_ACCESS_TIMEOUT);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        requireNoContent(reader);
        if (!missingRequiredAttributes.isEmpty()) {
            throw missingRequired(reader, missingRequiredAttributes);
        }
    }

    private void parsePools(final XMLExtendedStreamReader reader, final List<ModelNode> operations) throws XMLStreamException {
        // no attributes expected
        requireNoAttributes(reader);
        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            switch (EJB3SubsystemXMLElement.forName(reader.getLocalName())) {
                case BEAN_INSTANCE_POOLS: {
                    this.parseBeanInstancePools(reader, operations);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    private void parseBeanInstancePools(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {
        // no attributes expected
        requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            switch (EJB3SubsystemXMLElement.forName(reader.getLocalName())) {
                case STRICT_MAX_POOL: {
                    this.parseStrictMaxPool(reader, operations);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    void parseStrictMaxPool(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {
        final int count = reader.getAttributeCount();
        String poolName = null;
        final ModelNode operation = Util.createAddOperation();
        for (int i = 0; i < count; i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            final EJB3SubsystemXMLAttribute attribute = EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i));
            switch (attribute) {
                case NAME:
                    poolName = value;
                    break;
                case MAX_POOL_SIZE:
                    StrictMaxPoolResourceDefinition.MAX_POOL_SIZE.parseAndSetParameter(value, operation, reader);
                    break;
                case INSTANCE_ACQUISITION_TIMEOUT:
                    StrictMaxPoolResourceDefinition.INSTANCE_ACQUISITION_TIMEOUT.parseAndSetParameter(value, operation, reader);
                    break;
                case INSTANCE_ACQUISITION_TIMEOUT_UNIT:
                    StrictMaxPoolResourceDefinition.INSTANCE_ACQUISITION_TIMEOUT_UNIT.parseAndSetParameter(value, operation, reader);
                    break;

                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        requireNoContent(reader);
        if (poolName == null) {
            throw missingRequired(reader, Collections.singleton(EJB3SubsystemXMLAttribute.NAME.getLocalName()));
        }
        // create and add the operation
        // create /subsystem=ejb3/strict-max-bean-instance-pool=name:add(...)
        final PathAddress address = this.getEJB3SubsystemAddress().append(STRICT_MAX_BEAN_INSTANCE_POOL, poolName);
        operation.get(OP_ADDR).set(address.toModelNode());
        operations.add(operation);
    }

    private void parseCaches(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {
        // no attributes expected
        requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            switch (EJB3SubsystemXMLElement.forName(reader.getLocalName())) {
                case CACHE: {
                    this.parseCache(reader, operations);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    private void parseCache(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {
        String name = null;
        ModelNode operation = Util.createAddOperation();
        //Set<String> aliases = new LinkedHashSet<String>();
        for (int i = 0; i < reader.getAttributeCount(); i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            switch (EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i))) {
                case NAME: {
                    name = value;
                    break;
                }
                case PASSIVATION_STORE_REF: {
                    CacheFactoryResourceDefinition.PASSIVATION_STORE.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case ALIASES: {
                    for (String alias : reader.getListAttributeValue(i)) {
                        CacheFactoryResourceDefinition.ALIASES.parseAndAddParameterElement(alias, operation, reader);
                    }
                    break;
                }
                default: {
                    throw unexpectedAttribute(reader, i);
                }
            }
        }
        requireNoContent(reader);
        if (name == null) {
            throw missingRequired(reader, Collections.singleton(EJB3SubsystemXMLAttribute.NAME.getLocalName()));
        }
        final PathAddress address = this.getEJB3SubsystemAddress().append(PathElement.pathElement(CACHE, name));
        operation.get(OP_ADDR).set(address.toModelNode());
        operations.add(operation);
    }

    @SuppressWarnings("deprecation")
    protected void parsePassivationStores(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {
        // no attributes expected
        requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            switch (EJB3SubsystemXMLElement.forName(reader.getLocalName())) {
                case FILE_PASSIVATION_STORE: {
                    this.parseFilePassivationStore(reader, operations);
                    break;
                }
                case CLUSTER_PASSIVATION_STORE: {
                    this.parseClusterPassivationStore(reader, operations);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    @SuppressWarnings("deprecation")
    protected void parseFilePassivationStore(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {
        String name = null;
        ModelNode operation = Util.createAddOperation();
        for (int i = 0; i < reader.getAttributeCount(); i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            switch (EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i))) {
                case NAME: {
                    name = value;
                    break;
                }
                case MAX_SIZE: {
                    FilePassivationStoreResourceDefinition.MAX_SIZE.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case IDLE_TIMEOUT: {
                    LegacyPassivationStoreResourceDefinition.IDLE_TIMEOUT.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case IDLE_TIMEOUT_UNIT: {
                    LegacyPassivationStoreResourceDefinition.IDLE_TIMEOUT_UNIT.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case RELATIVE_TO: {
                    FilePassivationStoreResourceDefinition.RELATIVE_TO.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case GROUPS_PATH: {
                    FilePassivationStoreResourceDefinition.GROUPS_PATH.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case SESSIONS_PATH: {
                    FilePassivationStoreResourceDefinition.SESSIONS_PATH.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case SUBDIRECTORY_COUNT: {
                    FilePassivationStoreResourceDefinition.SUBDIRECTORY_COUNT.parseAndSetParameter(value, operation, reader);
                    break;
                }
                default: {
                    throw unexpectedAttribute(reader, i);
                }
            }
        }
        requireNoContent(reader);
        if (name == null) {
            throw missingRequired(reader, Collections.singleton(EJB3SubsystemXMLAttribute.NAME.getLocalName()));
        }
        // create and add the operation
        operation.get(OP_ADDR).set(SUBSYSTEM_PATH.append(FILE_PASSIVATION_STORE, name).toModelNode());
        operations.add(operation);
    }

    @SuppressWarnings("deprecation")
    protected void parseClusterPassivationStore(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {
        String name = null;
        ModelNode operation = Util.createAddOperation();
        for (int i = 0; i < reader.getAttributeCount(); i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            switch (EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i))) {
                case NAME: {
                    name = value;
                    break;
                }
                case MAX_SIZE: {
                    ClusterPassivationStoreResourceDefinition.MAX_SIZE.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case IDLE_TIMEOUT: {
                    LegacyPassivationStoreResourceDefinition.IDLE_TIMEOUT.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case IDLE_TIMEOUT_UNIT: {
                    LegacyPassivationStoreResourceDefinition.IDLE_TIMEOUT_UNIT.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case CACHE_CONTAINER: {
                    ClusterPassivationStoreResourceDefinition.CACHE_CONTAINER.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case BEAN_CACHE: {
                    ClusterPassivationStoreResourceDefinition.BEAN_CACHE.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case CLIENT_MAPPINGS_CACHE: {
                    ClusterPassivationStoreResourceDefinition.CLIENT_MAPPINGS_CACHE.parseAndSetParameter(value, operation, reader);
                    break;
                }
                case PASSIVATE_EVENTS_ON_REPLICATE: {
                    ClusterPassivationStoreResourceDefinition.PASSIVATE_EVENTS_ON_REPLICATE.parseAndSetParameter(value, operation, reader);
                    break;
                }
                default: {
                    throw unexpectedAttribute(reader, i);
                }
            }
        }
        requireNoContent(reader);
        if (name == null) {
            throw missingRequired(reader, Collections.singleton(EJB3SubsystemXMLAttribute.NAME.getLocalName()));
        }
        // create and add the operation
        operation.get(OP_ADDR).set(SUBSYSTEM_PATH.append(CLUSTER_PASSIVATION_STORE, name).toModelNode());
        operations.add(operation);
    }

    protected void parseTimerService(final XMLExtendedStreamReader reader, List<ModelNode> operations) throws XMLStreamException {

        ModelNode fileDataStoreAdd = null;
        final ModelNode address = new ModelNode();
        address.add(SUBSYSTEM, EJB3Extension.SUBSYSTEM_NAME);
        address.add(SERVICE, TIMER_SERVICE);
        final ModelNode timerServiceAdd = new ModelNode();
        timerServiceAdd.get(OP).set(ADD);
        timerServiceAdd.get(OP_ADDR).set(address);

        ModelNode dataStorePath = null;
        ModelNode dataStorePathRelativeTo = null;

        final int attCount = reader.getAttributeCount();
        final EnumSet<EJB3SubsystemXMLAttribute> required = EnumSet.of(EJB3SubsystemXMLAttribute.THREAD_POOL_NAME);
        for (int i = 0; i < attCount; i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            final EJB3SubsystemXMLAttribute attribute = EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i));
            required.remove(attribute);
            switch (attribute) {
                case THREAD_POOL_NAME:
                    TimerServiceResourceDefinition.THREAD_POOL_NAME.parseAndSetParameter(value, timerServiceAdd, reader);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (!required.isEmpty()) {
            throw missingRequired(reader, required);
        }

        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            switch (EJB3SubsystemXMLElement.forName(reader.getLocalName())) {
                case DATA_STORE: {
                    final int count = reader.getAttributeCount();
                    for (int i = 0; i < count; i++) {
                        requireNoNamespaceAttribute(reader, i);
                        final String value = reader.getAttributeValue(i);
                        final EJB3SubsystemXMLAttribute attribute = EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i));
                        switch (attribute) {
                            case PATH:
                                if (dataStorePath != null) {
                                    throw unexpectedAttribute(reader, i);
                                }
                                dataStorePath = FileDataStoreResourceDefinition.PATH.parse(value, reader);
                                break;
                            case RELATIVE_TO:
                                if (dataStorePathRelativeTo != null) {
                                    throw unexpectedAttribute(reader, i);
                                }
                                dataStorePathRelativeTo = FileDataStoreResourceDefinition.RELATIVE_TO.parse(value, reader);
                                break;
                            default:
                                throw unexpectedAttribute(reader, i);
                        }
                    }
                    if (dataStorePath == null) {
                        throw missingRequired(reader, Collections.singleton(EJB3SubsystemXMLAttribute.PATH));
                    }
                    timerServiceAdd.get(DEFAULT_DATA_STORE).set("default-file-store");
                    fileDataStoreAdd = new ModelNode();
                    final ModelNode fileDataAddress = address.clone();
                    fileDataAddress.add(FILE_DATA_STORE, "default-file-store");
                    fileDataStoreAdd.get(OP).set(ADD);
                    fileDataStoreAdd.get(OP_ADDR).set(fileDataAddress);
                    fileDataStoreAdd.get(PATH).set(dataStorePath);
                    if (dataStorePathRelativeTo != null) {
                        fileDataStoreAdd.get(RELATIVE_TO).set(dataStorePathRelativeTo);
                    }
                    requireNoContent(reader);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
        operations.add(timerServiceAdd);
        if(fileDataStoreAdd != null) {
            operations.add(fileDataStoreAdd);
        }
    }

    private void parseThreadPools(final XMLExtendedStreamReader reader, final List<ModelNode> operations) throws XMLStreamException {
        // no attributes expected
        requireNoAttributes(reader);

        final ModelNode parentAddress = SUBSYSTEM_PATH.toModelNode();
        while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
            EJB3SubsystemNamespace readerNS = EJB3SubsystemNamespace.forUri(reader.getNamespaceURI());
            switch (EJB3SubsystemXMLElement.forName(reader.getLocalName())) {
                case THREAD_POOL: {
                    ThreadsParser.getInstance().parseUnboundedQueueThreadPool(reader, readerNS.getUriString(),
                            Namespace.THREADS_1_1, parentAddress, operations, THREAD_POOL, null);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    protected EJB3SubsystemNamespace getExpectedNamespace() {
        return EJB3SubsystemNamespace.EJB3_1_2;
    }

    PathAddress getEJB3SubsystemAddress() {
        return SUBSYSTEM_PATH;
    }

    private void parseInVMRemoteInterfaceInvocation(final XMLExtendedStreamReader reader, final ModelNode ejb3SubsystemAddOperation) throws XMLStreamException {
        final int count = reader.getAttributeCount();
        final EnumSet<EJB3SubsystemXMLAttribute> missingRequiredAttributes = EnumSet.of(EJB3SubsystemXMLAttribute.PASS_BY_VALUE);
        //String passByValue = null;
        for (int i = 0; i < count; i++) {
            requireNoNamespaceAttribute(reader, i);
            final String value = reader.getAttributeValue(i);
            final EJB3SubsystemXMLAttribute attribute = EJB3SubsystemXMLAttribute.forName(reader.getAttributeLocalName(i));
            switch (attribute) {
                case PASS_BY_VALUE:
                    EJB3SubsystemRootResourceDefinition.PASS_BY_VALUE.parseAndSetParameter(value, ejb3SubsystemAddOperation, reader);
                    // found the mandatory attribute
                    missingRequiredAttributes.remove(EJB3SubsystemXMLAttribute.PASS_BY_VALUE);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        requireNoContent(reader);
        if (!missingRequiredAttributes.isEmpty()) {
            throw missingRequired(reader, missingRequiredAttributes);
        }
    }

}