/*
 * 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.ee.metadata;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * Metadata for configurations contained in jboss-ejb-client.xml descriptor
 *
 * @author Jaikiran Pai
 * @author Tomasz Adamski
 */
public class EJBClientDescriptorMetaData {

    private Boolean excludeLocalReceiver;
    private Boolean localReceiverPassByValue;
    private long invocationTimeout;
    private String deploymentNodeSelector;
    private String profile;

    private Map<String, RemotingReceiverConfiguration> remotingReceiverConfigurations = new HashMap<String, RemotingReceiverConfiguration>();
    private Set<ClusterConfig> clusterConfigs = new HashSet<ClusterConfig>();

    /**
     * Adds an outbound connection reference used by a remoting receiver in the client context represented
     * by this {@link EJBClientDescriptorMetaData}
     *
     * @param outboundConnectionRef The name of the outbound connection. Cannot be null or empty string.
     */
    public RemotingReceiverConfiguration addRemotingReceiverConnectionRef(final String outboundConnectionRef) {
        if (outboundConnectionRef == null || outboundConnectionRef.trim().isEmpty()) {
            throw new IllegalArgumentException("Cannot add a remoting receiver which references a null/empty outbound connection");
        }
        final RemotingReceiverConfiguration remotingReceiverConfiguration = new RemotingReceiverConfiguration(outboundConnectionRef);
        this.remotingReceiverConfigurations.put(outboundConnectionRef, remotingReceiverConfiguration);
        return remotingReceiverConfiguration;
    }

    /**
     * Returns a collection of outbound connection references that are used by the remoting receivers
     * configured in the client context of this {@link EJBClientDescriptorMetaData}
     *
     * @return
     */
    public Collection<RemotingReceiverConfiguration> getRemotingReceiverConfigurations() {
        return this.remotingReceiverConfigurations.values();
    }

    /**
     * Set the pass-by-value semantics for the local receiver belonging to the EJB client context
     * represented by this metadata
     *
     * @param passByValue True if pass-by-value. False otherwise.
     */
    public void setLocalReceiverPassByValue(final Boolean passByValue) {
        this.localReceiverPassByValue = passByValue;
    }

    /**
     * If pass-by-value semantics for the local EJB receiver has been explicitly set, then returns that value.
     * Else returns null.
     *
     * @return
     */
    public Boolean isLocalReceiverPassByValue() {
        return this.localReceiverPassByValue;
    }

    /**
     * Exclude/include the local receiver in the EJB client context represented by this metadata.
     *
     * @param excludeLocalReceiver True if local receiver has to be excluded in the EJB client context. False otherwise.
     */
    public void setExcludeLocalReceiver(final Boolean excludeLocalReceiver) {
        this.excludeLocalReceiver = excludeLocalReceiver;
    }

    /**
     * Returns true if the local receiver is disabled in the EJB client context represented by this metadata.
     * Else returns false.
     *
     * @return
     */
    public Boolean isLocalReceiverExcluded() {
        return this.excludeLocalReceiver;
    }

    public Collection<ClusterConfig> getClusterConfigs() {
        return Collections.unmodifiableSet(this.clusterConfigs);
    }

    public ClusterConfig newClusterConfig(final String clusterName) {
        final ClusterConfig clusterConfig = new ClusterConfig(clusterName);
        this.clusterConfigs.add(clusterConfig);
        return clusterConfig;
    }

    public long getInvocationTimeout() {
        return this.invocationTimeout;
    }

    public void setInvocationTimeout(final long invocationTimeout) {
        this.invocationTimeout = invocationTimeout;
    }

    public String getDeploymentNodeSelector() {
        return this.deploymentNodeSelector;
    }

    public void setDeploymentNodeSelector(final String selector) {
        this.deploymentNodeSelector = selector;
    }

    public String getProfile() {
        return this.profile;
    }

    public void setProfile(String profile) {
        this.profile = profile;
    }

    public class ClusterConfig extends CommonConnectionConfig {

        private final String clusterName;
        private final Set<ClusterNodeConfig> nodes = new HashSet<ClusterNodeConfig>();
        private long maxAllowedConnectedNodes;
        private String nodeSelector;

        ClusterConfig(final String clusterName) {
            this.clusterName = clusterName;
        }

        public ClusterNodeConfig newClusterNode(final String nodeName) {
            final ClusterNodeConfig node = new ClusterNodeConfig(nodeName);
            this.nodes.add(node);
            return node;
        }

        public Collection<ClusterNodeConfig> getClusterNodeConfigs() {
            return Collections.unmodifiableSet(this.nodes);
        }

        public String getClusterName() {
            return this.clusterName;
        }

        public long getMaxAllowedConnectedNodes() {
            return this.maxAllowedConnectedNodes;
        }

        public void setMaxAllowedConnectedNodes(final long maxAllowedConnectedNodes) {
            this.maxAllowedConnectedNodes = maxAllowedConnectedNodes;
        }

        public void setNodeSelector(final String nodeSelector) {
            this.nodeSelector = nodeSelector;
        }

        public String getNodeSelector() {
            return this.nodeSelector;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ClusterConfig that = (ClusterConfig) o;

            if (!clusterName.equals(that.clusterName)) return false;

            return true;
        }

        @Override
        public int hashCode() {
            return clusterName.hashCode();
        }
    }

    public class ClusterNodeConfig extends CommonConnectionConfig {

        private final String nodeName;

        ClusterNodeConfig(final String nodeName) {
            this.nodeName = nodeName;
        }

        public String getNodeName() {
            return this.nodeName;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ClusterNodeConfig that = (ClusterNodeConfig) o;

            if (!nodeName.equals(that.nodeName)) return false;

            return true;
        }

        @Override
        public int hashCode() {
            return nodeName.hashCode();
        }
    }

    private class CommonConnectionConfig {
        private Properties connectionOptions;
        private Properties channelCreationOptions;
        private long connectTimeout;
        private String userName;
        private String securityRealm;

        public void setConnectionOptions(final Properties connectionOptions) {
            this.connectionOptions = connectionOptions;
        }

        public void setChannelCreationOptions(final Properties channelCreationOptions) {
            this.channelCreationOptions = channelCreationOptions;
        }

        public long getConnectTimeout() {
            return this.connectTimeout;
        }

        public void setConnectTimeout(final long timeout) {
            this.connectTimeout = timeout;
        }

        public Properties getConnectionOptions() {
            return this.connectionOptions;
        }

        public Properties getChannelCreationOptions() {
            return this.channelCreationOptions;
        }

        public void setUserName(final String userName) {
            this.userName = userName;
        }

        public String getUserName() {
            return this.userName;
        }

        public void setSecurityRealm(final String realm) {
            this.securityRealm = realm;
        }

        public String getSecurityRealm() {
            return this.securityRealm;
        }
    }

    public class RemotingReceiverConfiguration {
        private final String outboundConnectionRef;

        private Properties channelCreationOptions;
        private long connectionTimeout;

        RemotingReceiverConfiguration(final String outboundConnectionRef) {
            this.outboundConnectionRef = outboundConnectionRef;
        }

        /**
         * Sets the channel creation options for this remoting receiver configuration
         *
         * @param channelCreationOpts The channel creation options
         */
        public void setChannelCreationOptions(final Properties channelCreationOpts) {
            this.channelCreationOptions = channelCreationOpts;
        }

        public Properties getChannelCreationOptions() {
            return this.channelCreationOptions;
        }

        public void setConnectionTimeout(final long timeout) {
            this.connectionTimeout = timeout;
        }

        public long getConnectionTimeout() {
            return this.connectionTimeout;
        }

        public String getOutboundConnectionRef() {
            return this.outboundConnectionRef;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            RemotingReceiverConfiguration that = (RemotingReceiverConfiguration) o;

            if (outboundConnectionRef != null ? !outboundConnectionRef.equals(that.outboundConnectionRef) : that.outboundConnectionRef != null)
                return false;

            return true;
        }

        @Override
        public int hashCode() {
            return outboundConnectionRef != null ? outboundConnectionRef.hashCode() : 0;
        }
    }
}


/*
 * 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.naming;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;

import org.jboss.as.naming.context.ModularReference;
import org.jboss.as.naming.logging.NamingLogger;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceNotFoundException;
import org.jboss.msc.service.ServiceRegistry;
import org.jboss.msc.service.StabilityMonitor;

/**
 * Abstract object factory that allows for the creation of service references. Object factories that subclass
 * {@link ServiceReferenceObjectFactory} can get access to the value of the service described by the reference.
 *

 * If the factory state is no {@link State#UP} then the factory will block. If the state is {@link State#START_FAILED} or
 * {@link State#REMOVED} (or the state transactions to one of these states while blocking) an exception is thrown.
 *
 * @author Stuart Douglas
 * @author Richard Opalka
 */
public class ServiceReferenceObjectFactory implements ServiceAwareObjectFactory {

    private volatile ServiceRegistry serviceRegistry;

    /**
     * Create a reference to a sub class of {@link ServiceReferenceObjectFactory} that injects the value of the given service.
     */
    public static Reference createReference(final ServiceName service, Class<? extends ServiceReferenceObjectFactory> factory) {
        return ModularReference.create(Context.class, new ServiceNameRefAdr("srof", service), factory);
    }

    @Override
    public void injectServiceRegistry(ServiceRegistry registry) {
        this.serviceRegistry = registry;
    }

    @SuppressWarnings("unchecked")
    @Override
    public final Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        final Reference reference = (Reference) obj;
        final ServiceNameRefAdr nameAdr = (ServiceNameRefAdr) reference.get("srof");
        if (nameAdr == null) {
            throw NamingLogger.ROOT_LOGGER.invalidContextReference("srof");
        }
        final ServiceName serviceName = (ServiceName)nameAdr.getContent();
        final ServiceController<?> controller;
        try {
            controller = serviceRegistry.getRequiredService(serviceName);
        } catch (ServiceNotFoundException e) {
            throw NamingLogger.ROOT_LOGGER.cannotResolveService(serviceName);
        }

        final StabilityMonitor monitor = new StabilityMonitor();
        monitor.addController(controller);
        try {
            monitor.awaitStability();
        } catch (InterruptedException e) {
            throw NamingLogger.ROOT_LOGGER.threadInterrupt(serviceName);
        } finally {
            monitor.removeController(controller);
        }
        switch (controller.getState()) {
            case UP:
                return getObjectInstance(controller.getValue(), obj, name, nameCtx, environment);
            case START_FAILED:
                throw NamingLogger.ROOT_LOGGER.cannotResolveService(serviceName, getClass().getName(), "START_FAILED");
            case REMOVED:
                throw NamingLogger.ROOT_LOGGER.cannotResolveService(serviceName, getClass().getName(), "START_FAILED");
        }
        // we should never get here, as the listener should not notify unless the state was one of the above
        throw NamingLogger.ROOT_LOGGER.cannotResolveServiceBug(serviceName, getClass().getName(), controller.getState().toString());
    }

    /**
     * Handles the service reference. The parameters are the same as
     * {@link javax.naming.spi.ObjectFactory#getObjectInstance(Object, Name, Context, Hashtable)}, but with the addition of the service value as
     * the first parameter.
     */
    public Object getObjectInstance(Object serviceValue, Object obj, Name name, Context nameCtx,
                                             Hashtable<?, ?> environment) throws Exception {
        return serviceValue;
    }

    private static final class ServiceNameRefAdr extends RefAddr {
        private static final long serialVersionUID = 3677121114687908679L;

        private final ServiceName serviceName;

        private ServiceNameRefAdr(String s, ServiceName serviceName) {
            super(s);
            this.serviceName = serviceName;
        }

        public Object getContent() {
            return serviceName;
        }
    }
}


/*
 * 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.naming.context;

import javax.naming.Context;

import org.jboss.as.naming.util.ThreadLocalStack;

/**
 * Selects a naming context based on the provided identifier (eg. comp).  Maintains a thread local used to managed the current selector.
 * The current selector will be used by instances of {@code org.jboss.as.naming.contexts.NamespaceObjectFactory} to determine
 * which context to return.
 *
 * @author John E. Bailey
 */
public abstract class NamespaceContextSelector {
    /* Thread local maintaining the current context selector */
    private static ThreadLocalStack<NamespaceContextSelector> currentSelector = new ThreadLocalStack<NamespaceContextSelector>();

    private static NamespaceContextSelector defaultSelector;

    /**
     * Set the current context selector for the current thread.
     *
     * @param selector The current selector
     */
    public static void pushCurrentSelector(final NamespaceContextSelector selector) {
        currentSelector.push(selector);
    }

    /**
     * Pops the current selector for the thread, replacing it with the previous selector
     *
     * @return selector The current selector
     */
    public static NamespaceContextSelector popCurrentSelector() {
        return currentSelector.pop();
    }

    /**
     * Get the current context selector for the current thread.
     *
     * @return The current context selector.
     */
    public static NamespaceContextSelector getCurrentSelector() {
        NamespaceContextSelector selector = currentSelector.peek();
        if(selector != null) {
            return selector;
        }
        return defaultSelector;
    }

    /**
     * Get the context for a given identifier (eg. comp -> java:comp).  Implementers of this method can use any means to
     * determine which context to return.
     *
     * @param identifier The context identifier
     * @return The context for this identifier
     */
    public abstract Context getContext(final String identifier);

    public static void setDefault(NamespaceContextSelector selector) {
        defaultSelector = selector;
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat Middleware LLC, 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.pojo.descriptor;

import org.jboss.as.pojo.BeanState;
import org.jboss.msc.service.ServiceName;

import java.io.Serializable;
import java.util.List;
import java.util.Set;

/**
 * The legacy bean meta data.
 *
 * @author Ales Justin
 */
public class BeanMetaDataConfig extends AbstractConfigVisitorNode implements Serializable {
    private static final long serialVersionUID = 1L;

    /** Name prefix of all POJO-style beans. */
    private static final ServiceName JBOSS_POJO = ServiceName.JBOSS.append("pojo");

    /**
     * Get MC bean name.
     *
     * @param name the original bean name
     * @param state the state
     * @return bean service name
     */
    public static ServiceName toBeanName(String name, BeanState state) {
        if (state == null)
            state = BeanState.INSTALLED;

        return JBOSS_POJO.append(name).append(state.name());
    }

    /**
     * To instances name.
     *
     * @param clazz the class
     * @param state the bean state
     * @return unique instance name
     */
    public static ServiceName toInstancesName(Class<?> clazz, BeanState state) {
        String clName;
        ClassLoader classLoader = clazz.getClassLoader();
        if (classLoader != null)
            clName = classLoader.toString();
        else
            clName = "SystemClassLoader";

        if (state == null)
            state = BeanState.INSTALLED;

        return JBOSS_POJO.append(clName, clazz.getName(), state.name());
    }

    private String name;
    private String beanClass;
    private Set<String> aliases;
    private ModeConfig mode;
    private ModuleConfig module;
    private ConstructorConfig constructor;
    private Set<PropertyConfig> properties;
    private LifecycleConfig create;
    private LifecycleConfig start;
    private LifecycleConfig stop;
    private LifecycleConfig destroy;
    private List<InstallConfig> installs;
    private List<InstallConfig> uninstalls;
    private List<CallbackConfig> incallbacks;
    private List<CallbackConfig> uncallbacks;
    private Set<DependsConfig> depends;

    @Override
    public void visit(ConfigVisitor visitor) {
        if (module == null)
            module = new ModuleConfig();
        super.visit(visitor);
    }

    protected void addChildren(ConfigVisitor visitor, List<ConfigVisitorNode> nodes) {
        BeanState state = visitor.getState();
        if (state == BeanState.NOT_INSTALLED)
            nodes.add(module);
        if (constructor != null && state == BeanState.DESCRIBED)
            nodes.add(constructor);
        if (properties != null && state == BeanState.INSTANTIATED)
            nodes.addAll(properties);
        if (create != null && state == BeanState.CONFIGURED)
            nodes.add(create);
        if (destroy != null && state == BeanState.CONFIGURED)
            nodes.add(destroy);
        if (start != null && state == BeanState.CREATE)
            nodes.add(start);
        if (stop != null && state == BeanState.CREATE)
            nodes.add(stop);
        if (installs != null)
            nodes.addAll(installs);
        if (uninstalls != null)
            nodes.addAll(uninstalls);
        if (incallbacks != null)
            nodes.addAll(incallbacks);
        if (uncallbacks != null)
            nodes.addAll(uncallbacks);
        if (depends != null)
            nodes.addAll(depends);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBeanClass() {
        return beanClass;
    }

    public void setBeanClass(String beanClass) {
        this.beanClass = beanClass;
    }

    public Set<String> getAliases() {
        return aliases;
    }

    public void setAliases(Set<String> aliases) {
        this.aliases = aliases;
    }

    public ModeConfig getMode() {
        if (mode == null)
            mode = ModeConfig.PASSIVE;
        return mode;
    }

    public void setMode(ModeConfig mode) {
        this.mode = mode;
    }

    public ModuleConfig getModule() {
        return module;
    }

    public void setModule(ModuleConfig module) {
        this.module = module;
    }

    public ConstructorConfig getConstructor() {
        return constructor;
    }

    public void setConstructor(ConstructorConfig constructor) {
        this.constructor = constructor;
    }

    public Set<PropertyConfig> getProperties() {
        return properties;
    }

    public void setProperties(Set<PropertyConfig> properties) {
        this.properties = properties;
    }

    public LifecycleConfig getCreate() {
        return create;
    }

    public void setCreate(LifecycleConfig create) {
        this.create = create;
    }

    public LifecycleConfig getStart() {
        return start;
    }

    public void setStart(LifecycleConfig start) {
        this.start = start;
    }

    public LifecycleConfig getStop() {
        return stop;
    }

    public void setStop(LifecycleConfig stop) {
        this.stop = stop;
    }

    public LifecycleConfig getDestroy() {
        return destroy;
    }

    public void setDestroy(LifecycleConfig destroy) {
        this.destroy = destroy;
    }

    public List<InstallConfig> getInstalls() {
        return installs;
    }

    public void setInstalls(List<InstallConfig> installs) {
        this.installs = installs;
    }

    public List<InstallConfig> getUninstalls() {
        return uninstalls;
    }

    public void setUninstalls(List<InstallConfig> uninstalls) {
        this.uninstalls = uninstalls;
    }

    public List<CallbackConfig> getIncallbacks() {
        return incallbacks;
    }

    public void setIncallbacks(List<CallbackConfig> incallbacks) {
        this.incallbacks = incallbacks;
    }

    public List<CallbackConfig> getUncallbacks() {
        return uncallbacks;
    }

    public void setUncallbacks(List<CallbackConfig> uncallbacks) {
        this.uncallbacks = uncallbacks;
    }

    public Set<DependsConfig> getDepends() {
        return depends;
    }

    public void setDepends(Set<DependsConfig> depends) {
        this.depends = depends;
    }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013, 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.util;

import java.lang.annotation.Inherited;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;

/**
 * Utilities for working with Jandex indices.
 *
 * @author Jozef Hartinger
 *
 */
public class Indices {

    public static final DotName INHERITED_NAME = DotName.createSimple(Inherited.class.getName());

    public static final Function<ClassInfo, String> CLASS_INFO_TO_FQCN = new Function<ClassInfo, String>() {
        @Override
        public String apply(ClassInfo input) {
            return input.name().toString();
        }
    };

    public static final Predicate<ClassInfo> ANNOTATION_PREDICATE = new Predicate<ClassInfo>() {
        @Override
        public boolean test(ClassInfo input) {
            return isAnnotation(input);
        }
    };

    private Indices() {
    }

    private static final int ANNOTATION = 0x00002000;

    public static boolean isAnnotation(ClassInfo clazz) {
        return (clazz.flags() & ANNOTATION) != 0;
    }

    /**
     * Determines a list of classes the given annotation instances are defined on. If an annotation instance is not defined on a
     * class (e.g. on a member) this annotation instance is not reflected anyhow in the resulting list.
     */
    public static List<ClassInfo> getAnnotatedClasses(List<AnnotationInstance> instances) {
        List<ClassInfo> result = new ArrayList<ClassInfo>();
        for (AnnotationInstance instance : instances) {
            AnnotationTarget target = instance.target();
            if (target instanceof ClassInfo) {
                result.add((ClassInfo) target);
            }
        }
        return result;
    }
}


/*
 * 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.ee.component;

import java.util.List;

/**
 * Allows a deployment to get old of class descriptions from all sub deployments it has access to.
 *
 * This maintains a list of all {@link EEModuleDescription}s that this sub deployment has access to,
 * in the same order they appear in the dependencies list.
 *
 * @author Stuart Douglas
 */
public final class EEApplicationClasses {

    //TODO: should we build a map of the available classes
    private final List<EEModuleDescription> availableModules;

    public EEApplicationClasses(final List<EEModuleDescription> availableModules) {
        this.availableModules = availableModules;
    }

    /**
     * Look for a class description in all available modules.
     * @param name The class to lookup
     * @return
     */
    public EEModuleClassDescription getClassByName(String name) {
        for(EEModuleDescription module : availableModules) {
            final EEModuleClassDescription desc = module.getClassDescription(name);
            if(desc != null) {
                return desc;
            }
        }
        return null;
    }
}


/*
 * 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.deployment.processors;

import static org.jboss.as.ejb3.deployment.processors.AbstractDeploymentUnitProcessor.getEjbJarDescription;

import java.lang.reflect.Modifier;
import java.util.List;

import javax.ejb.Singleton;
import javax.ejb.Stateful;
import javax.ejb.Stateless;

import org.jboss.as.ee.metadata.MetadataCompleteMarker;
import org.jboss.as.ee.structure.EJBAnnotationPropertyReplacement;
import org.jboss.as.ejb3.component.session.SessionBeanComponentDescription;
import org.jboss.as.ejb3.component.singleton.SingletonComponentDescription;
import org.jboss.as.ejb3.component.stateful.StatefulComponentDescription;
import org.jboss.as.ejb3.component.stateless.StatelessComponentDescription;
import org.jboss.as.ejb3.deployment.EjbJarDescription;
import org.jboss.as.ejb3.logging.EjbLogger;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.EjbDeploymentMarker;
import org.jboss.as.server.deployment.annotation.CompositeIndex;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.metadata.ejb.spec.EjbType;
import org.jboss.metadata.ejb.spec.EnterpriseBeanMetaData;
import org.jboss.metadata.ejb.spec.GenericBeanMetaData;
import org.jboss.metadata.ejb.spec.SessionBean32MetaData;
import org.jboss.metadata.ejb.spec.SessionBeanMetaData;
import org.jboss.metadata.ejb.spec.SessionType;
import org.jboss.metadata.property.PropertyReplacer;
import org.jboss.msc.service.ServiceName;

/**
 * User: jpai
 */
public class SessionBeanComponentDescriptionFactory extends EJBComponentDescriptionFactory {

    private static final DotName STATELESS_ANNOTATION = DotName.createSimple(Stateless.class.getName());
    private static final DotName STATEFUL_ANNOTATION = DotName.createSimple(Stateful.class.getName());
    private static final DotName SINGLETON_ANNOTATION = DotName.createSimple(Singleton.class.getName());
    private final boolean defaultSlsbPoolAvailable;

    public SessionBeanComponentDescriptionFactory(final boolean appclient, final boolean defaultSlsbPoolAvailable) {
        super(appclient);
        this.defaultSlsbPoolAvailable = defaultSlsbPoolAvailable;
    }

    /**
     * Process annotations and merge any available metadata at the same time.
     */
    @Override
    protected void processAnnotations(final DeploymentUnit deploymentUnit, final CompositeIndex compositeIndex) throws DeploymentUnitProcessingException {

        if (MetadataCompleteMarker.isMetadataComplete(deploymentUnit)) {
            return;
        }

        // Find and process any @Stateless bean annotations
        final List<AnnotationInstance> slsbAnnotations = compositeIndex.getAnnotations(STATELESS_ANNOTATION);
        if (!slsbAnnotations.isEmpty()) {
            processSessionBeans(deploymentUnit, slsbAnnotations, SessionBeanComponentDescription.SessionBeanType.STATELESS);
        }

        // Find and process any @Stateful bean annotations
        final List<AnnotationInstance> sfsbAnnotations = compositeIndex.getAnnotations(STATEFUL_ANNOTATION);
        if (!sfsbAnnotations.isEmpty()) {
            processSessionBeans(deploymentUnit, sfsbAnnotations, SessionBeanComponentDescription.SessionBeanType.STATEFUL);
        }

        // Find and process any @Singleton bean annotations
        final List<AnnotationInstance> sbAnnotations = compositeIndex.getAnnotations(SINGLETON_ANNOTATION);
        if (!sbAnnotations.isEmpty()) {
            processSessionBeans(deploymentUnit, sbAnnotations, SessionBeanComponentDescription.SessionBeanType.SINGLETON);
        }
    }

    @Override
    protected void processBeanMetaData(final DeploymentUnit deploymentUnit, final EnterpriseBeanMetaData enterpriseBeanMetaData) throws DeploymentUnitProcessingException {
        if (enterpriseBeanMetaData.isSession()) {
            assert enterpriseBeanMetaData instanceof SessionBeanMetaData : enterpriseBeanMetaData + " is not a SessionBeanMetaData";
            processSessionBeanMetaData(deploymentUnit, (SessionBeanMetaData) enterpriseBeanMetaData);
        }
    }

    private void processSessionBeans(final DeploymentUnit deploymentUnit, final List<AnnotationInstance> sessionBeanAnnotations, final SessionBeanComponentDescription.SessionBeanType annotatedSessionBeanType) {

        final EjbJarDescription ejbJarDescription = getEjbJarDescription(deploymentUnit);
        final ServiceName deploymentUnitServiceName = deploymentUnit.getServiceName();
        PropertyReplacer propertyReplacer = EJBAnnotationPropertyReplacement.propertyReplacer(deploymentUnit);

        // process these session bean annotations and create component descriptions out of it
        for (final AnnotationInstance sessionBeanAnnotation : sessionBeanAnnotations) {
            final AnnotationTarget target = sessionBeanAnnotation.target();
            if (!(target instanceof ClassInfo)) {
                // Let's just WARN and move on. No need to throw an error
                EjbLogger.DEPLOYMENT_LOGGER.warn(EjbLogger.ROOT_LOGGER.annotationOnlyAllowedOnClass(sessionBeanAnnotation.name().toString(), target).getMessage());
                continue;
            }
            final ClassInfo sessionBeanClassInfo = (ClassInfo) target;
            // skip if it's not a valid class for session bean
            if (!assertSessionBeanClassValidity(sessionBeanClassInfo)) {
                continue;
            }
            final String ejbName = sessionBeanClassInfo.name().local();
            final AnnotationValue nameValue = sessionBeanAnnotation.value("name");
            final String beanName = (nameValue == null || nameValue.asString().isEmpty()) ? ejbName : propertyReplacer.replaceProperties(nameValue.asString());
            final SessionBeanMetaData beanMetaData = getEnterpriseBeanMetaData(deploymentUnit, beanName, SessionBeanMetaData.class);
            final SessionBeanComponentDescription.SessionBeanType sessionBeanType;
            final String beanClassName;
            if (beanMetaData != null) {
                beanClassName = override(sessionBeanClassInfo.name().toString(), beanMetaData.getEjbClass());
                sessionBeanType = override(annotatedSessionBeanType, descriptionOf(((SessionBeanMetaData) beanMetaData).getSessionType()));
            } else {
                beanClassName = sessionBeanClassInfo.name().toString();
                sessionBeanType = annotatedSessionBeanType;
            }

            final SessionBeanComponentDescription sessionBeanDescription;
            switch (sessionBeanType) {
                case STATELESS:
                    sessionBeanDescription = new StatelessComponentDescription(beanName, beanClassName, ejbJarDescription, deploymentUnitServiceName, beanMetaData, defaultSlsbPoolAvailable);
                    break;
                case STATEFUL:
                    sessionBeanDescription = new StatefulComponentDescription(beanName, beanClassName, ejbJarDescription, deploymentUnitServiceName, beanMetaData);
                    // If passivation is disabled for the SFSB, either via annotation or via DD, then setup the component
                    // description appropriately
                    final boolean passivationCapableAnnotationValue = sessionBeanAnnotation.value("passivationCapable") == null ? true : sessionBeanAnnotation.value("passivationCapable").asBoolean();
                    final Boolean passivationCapableDeploymentDescriptorValue;
                    if ((beanMetaData instanceof SessionBean32MetaData)) {
                        passivationCapableDeploymentDescriptorValue = ((SessionBean32MetaData) beanMetaData).isPassivationCapable();
                    } else {
                        passivationCapableDeploymentDescriptorValue = null;
                    }
                    final boolean passivationApplicable = override(passivationCapableDeploymentDescriptorValue, passivationCapableAnnotationValue);
                    ((StatefulComponentDescription) sessionBeanDescription).setPassivationApplicable(passivationApplicable);
                    break;
                case SINGLETON:
                    sessionBeanDescription = new SingletonComponentDescription(beanName, beanClassName, ejbJarDescription, deploymentUnitServiceName, beanMetaData);
                    break;
                default:
                    throw EjbLogger.ROOT_LOGGER.unknownSessionBeanType(sessionBeanType.name());
            }

            addComponent(deploymentUnit, sessionBeanDescription);
        }

        EjbDeploymentMarker.mark(deploymentUnit);
    }

    private static SessionBeanComponentDescription.SessionBeanType descriptionOf(final SessionType sessionType) {
        if (sessionType == null)
            return null;
        switch (sessionType) {
            case Stateless:
                return SessionBeanComponentDescription.SessionBeanType.STATELESS;
            case Stateful:
                return SessionBeanComponentDescription.SessionBeanType.STATEFUL;
            case Singleton:
                return SessionBeanComponentDescription.SessionBeanType.SINGLETON;
            default:
                throw EjbLogger.ROOT_LOGGER.unknownSessionBeanType(sessionType.name());
        }
    }

    /**
     * Returns true if the passed sessionBeanClass meets the requirements set by the EJB3 spec about
     * bean implementation classes. The passed sessionBeanClass must not be an interface and must be public
     * and not final and not abstract. If it passes these requirements then this method returns true. Else it returns false.
     *
     * @param sessionBeanClass The session bean class
     * @return
     */
    private static boolean assertSessionBeanClassValidity(final ClassInfo sessionBeanClass) {
        final short flags = sessionBeanClass.flags();
        final String className = sessionBeanClass.name().toString();
        // must *not* be an interface
        if (Modifier.isInterface(flags)) {
            EjbLogger.DEPLOYMENT_LOGGER.sessionBeanClassCannotBeAnInterface(className);
            return false;
        }
        // bean class must be public, must *not* be abstract or final
        if (!Modifier.isPublic(flags) || Modifier.isAbstract(flags) || Modifier.isFinal(flags)) {
            EjbLogger.DEPLOYMENT_LOGGER.sessionBeanClassMustBePublicNonAbstractNonFinal(className);
            return false;
        }
        // valid class
        return true;
    }

    private void processSessionBeanMetaData(final DeploymentUnit deploymentUnit, final SessionBeanMetaData sessionBean) throws DeploymentUnitProcessingException {
        final EjbJarDescription ejbJarDescription = getEjbJarDescription(deploymentUnit);
        final CompositeIndex compositeIndex = deploymentUnit.getAttachment(org.jboss.as.server.deployment.Attachments.COMPOSITE_ANNOTATION_INDEX);

        final String beanName = sessionBean.getName();
        SessionType sessionType = sessionBean.getSessionType();

        if (sessionType == null && sessionBean instanceof GenericBeanMetaData) {
            final GenericBeanMetaData bean = (GenericBeanMetaData) sessionBean;
            if (bean.getEjbType() == EjbType.SESSION) {
                sessionType = determineSessionType(sessionBean.getEjbClass(), compositeIndex);
                if (sessionType == null) {
                    throw EjbLogger.ROOT_LOGGER.sessionTypeNotSpecified(beanName);
                }
            } else {
                //it is not a session bean, so we ignore it
                return;
            }
        } else if (sessionType == null) {
            sessionType = determineSessionType(sessionBean.getEjbClass(), compositeIndex);
            if (sessionType == null) {
                throw EjbLogger.ROOT_LOGGER.sessionTypeNotSpecified(beanName);
            }
        }

        final String beanClassName = sessionBean.getEjbClass();
        final SessionBeanComponentDescription sessionBeanDescription;
        switch (sessionType) {
            case Stateless:
                sessionBeanDescription = new StatelessComponentDescription(beanName, beanClassName, ejbJarDescription, deploymentUnit.getServiceName(), sessionBean, defaultSlsbPoolAvailable);
                break;
            case Stateful:
                sessionBeanDescription = new StatefulComponentDescription(beanName, beanClassName, ejbJarDescription, deploymentUnit.getServiceName(), sessionBean);
                if (sessionBean instanceof SessionBean32MetaData && ((SessionBean32MetaData) sessionBean).isPassivationCapable() != null) {
                    ((StatefulComponentDescription) sessionBeanDescription).setPassivationApplicable(((SessionBean32MetaData) sessionBean).isPassivationCapable());
                }
                break;
            case Singleton:
                sessionBeanDescription = new SingletonComponentDescription(beanName, beanClassName, ejbJarDescription, deploymentUnit.getServiceName(), sessionBean);
                break;
            default:
                throw EjbLogger.ROOT_LOGGER.unknownSessionBeanType(sessionType.name());
        }
        addComponent(deploymentUnit, sessionBeanDescription);
    }

    private SessionType determineSessionType(final String ejbClass, final CompositeIndex compositeIndex) {
        if(ejbClass == null) {
            return null;
        }
        final ClassInfo info = compositeIndex.getClassByName(DotName.createSimple(ejbClass));
        if (info == null) {
            return null;
        }
        if(info.annotations().get(STATEFUL_ANNOTATION) != null) {
            return SessionType.Stateful;
        } else if(info.annotations().get(STATELESS_ANNOTATION) != null) {
            return SessionType.Stateless;
        } else if(info.annotations().get(SINGLETON_ANNOTATION) != null) {
            return SessionType.Singleton;
        }
        return null;
    }

}


/*
 * 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.deployment.processors;

import java.util.Collection;

import javax.ejb.EJBContext;
import javax.ejb.EntityContext;
import javax.ejb.MessageDrivenContext;
import javax.ejb.SessionContext;

import org.jboss.as.ee.component.Attachments;
import org.jboss.as.ee.component.BindingConfiguration;
import org.jboss.as.ee.component.ComponentDescription;
import org.jboss.as.ee.component.ComponentNamingMode;
import org.jboss.as.ee.component.EEModuleDescription;
import org.jboss.as.ee.component.InjectionSource;
import org.jboss.as.ee.component.deployers.EEResourceReferenceProcessorRegistry;
import org.jboss.as.ejb3.component.EJBComponentDescription;
import org.jboss.as.ejb3.context.CurrentInvocationContext;
import org.jboss.as.ejb3.context.EjbContextResourceReferenceProcessor;
import org.jboss.as.naming.ContextListManagedReferenceFactory;
import org.jboss.as.naming.ManagedReference;
import org.jboss.as.naming.ManagedReferenceFactory;
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.annotation.CompositeIndex;
import org.jboss.msc.inject.Injector;
import org.jboss.msc.service.ServiceBuilder;

/**
 * Deployment processor responsible for detecting EJB components and adding a {@link BindingConfiguration} for the
 * java:comp/EJBContext entry.
 *
 * @author John Bailey
 */
public class EjbContextJndiBindingProcessor implements DeploymentUnitProcessor {

    /**
     * {@inheritDoc} *
     */
    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        final EEResourceReferenceProcessorRegistry registry = deploymentUnit.getAttachment(Attachments.RESOURCE_REFERENCE_PROCESSOR_REGISTRY);

        //setup ejb context jndi handlers
        registry.registerResourceReferenceProcessor(new EjbContextResourceReferenceProcessor(EJBContext.class));
        registry.registerResourceReferenceProcessor(new EjbContextResourceReferenceProcessor(SessionContext.class));
        registry.registerResourceReferenceProcessor(new EjbContextResourceReferenceProcessor(EntityContext.class));
        registry.registerResourceReferenceProcessor(new EjbContextResourceReferenceProcessor(MessageDrivenContext.class));

        final EEModuleDescription eeModuleDescription = deploymentUnit.getAttachment(Attachments.EE_MODULE_DESCRIPTION);
        final Collection<ComponentDescription> componentConfigurations = eeModuleDescription.getComponentDescriptions();
        if (componentConfigurations == null || componentConfigurations.isEmpty()) {
            return;
        }

        for (ComponentDescription componentConfiguration : componentConfigurations) {
            final CompositeIndex index = deploymentUnit.getAttachment(org.jboss.as.server.deployment.Attachments.COMPOSITE_ANNOTATION_INDEX);
            if (index != null) {
                processComponentConfig(componentConfiguration);
            }
        }
    }

    @Override
    public void undeploy(final DeploymentUnit context) {

    }

    protected void processComponentConfig(final ComponentDescription componentDescription) throws DeploymentUnitProcessingException {
        if (!(componentDescription instanceof EJBComponentDescription)) {
            return;  // Only process EJBs
        }
        // if the EJB is packaged in a .war, then we need to bind the java:comp/EJBContext only once for the entire module
        if (componentDescription.getNamingMode() != ComponentNamingMode.CREATE) {
            // get the module description
            final EEModuleDescription moduleDescription = componentDescription.getModuleDescription();
            // the java:module/EJBContext binding configuration
            // Note that we bind to java:module/EJBContext since it's a .war. End users can still lookup java:comp/EJBContext
            // and that will internally get translated to  java:module/EJBContext for .war, since java:comp == java:module in
            // a web ENC. So binding to java:module/EJBContext is OK.
            final BindingConfiguration ejbContextBinding = new BindingConfiguration("java:module/EJBContext", directEjbContextReferenceSource);
            moduleDescription.getBindingConfigurations().add(ejbContextBinding);
        } else { // EJB packaged outside of a .war. So process normally.
            // add the binding configuration to the component description
            final BindingConfiguration ejbContextBinding = new BindingConfiguration("java:comp/EJBContext", directEjbContextReferenceSource);
            componentDescription.getBindingConfigurations().add(ejbContextBinding);
        }
    }

    private static final ManagedReference ejbContextManagedReference = new ManagedReference() {
        public void release() {
        }

        public Object getInstance() {
            return CurrentInvocationContext.getEjbContext();
        }
    };

    private static final ManagedReferenceFactory ejbContextManagedReferenceFactory = new ContextListManagedReferenceFactory() {

        public ManagedReference getReference() {
            return ejbContextManagedReference;
        }

        @Override
        public String getInstanceClassName() {
            return EJBContext.class.getName();
        }
    };

    private static final InjectionSource directEjbContextReferenceSource = new InjectionSource() {
        public void getResourceValue(final ResolutionContext resolutionContext, final ServiceBuilder<?> serviceBuilder, final DeploymentPhaseContext phaseContext, final Injector<ManagedReferenceFactory> injector) throws DeploymentUnitProcessingException {
            injector.inject(ejbContextManagedReferenceFactory);
        }

        public boolean equals(Object o) {
            return o != null && o.getClass() == this.getClass();
        }

        public int hashCode() {
            return 1;
        }
    };
}


/*
 * 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.security.deployment;

import org.jboss.as.ee.structure.Attachments;
import org.jboss.as.security.service.EarJaccService;
import org.jboss.as.security.service.JaccService;
import org.jboss.as.server.deployment.AttachmentKey;
import org.jboss.metadata.ear.spec.EarMetaData;

/**
 * Handles ear deployments
 *
 * @author Marcus Moyses
 */
public class EarSecurityDeployer extends AbstractSecurityDeployer<EarMetaData> {

    /**
     * {@inheritDoc}
     */
    @Override
    protected JaccService<EarMetaData> createService(String contextId, EarMetaData metaData, Boolean standalone) {
        return new EarJaccService(contextId, metaData, standalone);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected AttachmentKey<EarMetaData> getMetaDataType() {
        return Attachments.EAR_METADATA;
    }

}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011-2012, 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.processor;

import org.jboss.as.controller.capability.CapabilityServiceSupport;
import org.jboss.as.ee.structure.DeploymentType;
import org.jboss.as.ee.structure.DeploymentTypeMarker;
import org.jboss.as.ee.structure.SpecDescriptorPropertyReplacement;
import org.jboss.as.jpa.config.Configuration;
import org.jboss.as.jpa.config.PersistenceUnitMetadataHolder;
import org.jboss.as.jpa.config.PersistenceUnitsInApplication;
import org.jboss.as.jpa.messages.JpaLogger;
import org.jboss.as.jpa.puparser.PersistenceUnitXmlParser;
import org.jboss.as.jpa.util.JPAServiceNames;
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.DeploymentUtils;
import org.jboss.as.server.deployment.JPADeploymentMarker;
import org.jboss.as.server.deployment.SubDeploymentMarker;
import org.jboss.as.server.deployment.module.ResourceRoot;
import org.jboss.metadata.parser.util.NoopXMLResolver;
import org.jboss.vfs.VirtualFile;
import org.jipijapa.plugin.spi.PersistenceUnitMetadata;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

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

/**
 * Handle parsing of Persistence unit persistence.xml files
 *

 * The jar file/directory whose META-INF directory contains the persistence.xml file is termed the root of the persistence
 * unit.
 * root of a persistence unit must be one of the following:
 * EJB-JAR file
 * the WEB-INF/classes directory of a WAR file
 * jar file in the WEB-INF/lib directory of a WAR file
 * jar file in the EAR library directory
 * application client jar file
 *
 * @author Scott Marlow
 */
public class PersistenceUnitParseProcessor implements DeploymentUnitProcessor {

    private static final String WEB_PERSISTENCE_XML = "WEB-INF/classes/META-INF/persistence.xml";
    private static final String META_INF_PERSISTENCE_XML = "META-INF/persistence.xml";
    private static final String JAR_FILE_EXTENSION = ".jar";
    private static final String LIB_FOLDER = "lib";

    private final boolean appClientContainerMode;

    public PersistenceUnitParseProcessor(boolean appclient) {
        appClientContainerMode = appclient;
    }

    @Override
    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {

        handleWarDeployment(phaseContext);
        handleEarDeployment(phaseContext);
        handleJarDeployment(phaseContext);

        CapabilityServiceSupport capabilitySupport = phaseContext.getDeploymentUnit().getAttachment(Attachments.CAPABILITY_SERVICE_SUPPORT);
        phaseContext.addDeploymentDependency(capabilitySupport.getCapabilityServiceName(JPAServiceNames.LOCAL_TRANSACTION_PROVIDER_CAPABILITY), JpaAttachments.LOCAL_TRANSACTION_PROVIDER);
        phaseContext.addDeploymentDependency(capabilitySupport.getCapabilityServiceName(JPAServiceNames.TRANSACTION_SYNCHRONIZATION_REGISTRY_CAPABILITY), JpaAttachments.TRANSACTION_SYNCHRONIZATION_REGISTRY);
    }

    @Override
    public void undeploy(DeploymentUnit context) {
    }

    private void handleJarDeployment(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        if (!isEarDeployment(deploymentUnit) && !isWarDeployment(deploymentUnit) &&
                (!appClientContainerMode || DeploymentTypeMarker.isType(DeploymentType.APPLICATION_CLIENT, deploymentUnit)) ) {

            // handle META-INF/persistence.xml
            // ordered list of PUs
            List<PersistenceUnitMetadataHolder> listPUHolders = new ArrayList<PersistenceUnitMetadataHolder>(1);
            // handle META-INF/persistence.xml
            final ResourceRoot deploymentRoot = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
            VirtualFile persistence_xml = deploymentRoot.getRoot().getChild(META_INF_PERSISTENCE_XML);
            parse(persistence_xml, listPUHolders, deploymentUnit);
            PersistenceUnitMetadataHolder holder = normalize(listPUHolders);
            // save the persistent unit definitions
            // deploymentUnit.putAttachment(PersistenceUnitMetadataHolder.PERSISTENCE_UNITS, holder);
            deploymentRoot.putAttachment(PersistenceUnitMetadataHolder.PERSISTENCE_UNITS, holder);
            markDU(holder, deploymentUnit);
            ROOT_LOGGER.tracef("parsed persistence unit definitions for jar %s", deploymentRoot.getRootName());

            incrementPersistenceUnitCount(deploymentUnit, holder.getPersistenceUnits().size());
            addApplicationDependenciesOnProvider( deploymentUnit, holder);
        }
    }

    private void handleWarDeployment(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        if (!appClientContainerMode && isWarDeployment(deploymentUnit)) {

            int puCount;
            // ordered list of PUs
            List<PersistenceUnitMetadataHolder> listPUHolders = new ArrayList<PersistenceUnitMetadataHolder>(1);

            // handle WEB-INF/classes/META-INF/persistence.xml
            final ResourceRoot deploymentRoot = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);

            VirtualFile persistence_xml = deploymentRoot.getRoot().getChild(WEB_PERSISTENCE_XML);
            parse(persistence_xml, listPUHolders, deploymentUnit);
            PersistenceUnitMetadataHolder holder = normalize(listPUHolders);
            deploymentRoot.putAttachment(PersistenceUnitMetadataHolder.PERSISTENCE_UNITS, holder);
            addApplicationDependenciesOnProvider( deploymentUnit, holder);
            markDU(holder, deploymentUnit);
            puCount = holder.getPersistenceUnits().size();

            // look for persistence.xml in jar files in the META-INF/persistence.xml directory (these are not currently
            // handled as subdeployments)
            List<ResourceRoot> resourceRoots = deploymentUnit.getAttachmentList(Attachments.RESOURCE_ROOTS);
            for (ResourceRoot resourceRoot : resourceRoots) {
                if (resourceRoot.getRoot().getName().toLowerCase(Locale.ENGLISH).endsWith(JAR_FILE_EXTENSION)) {
                    listPUHolders = new ArrayList<PersistenceUnitMetadataHolder>(1);
                    persistence_xml = resourceRoot.getRoot().getChild(META_INF_PERSISTENCE_XML);
                    parse(persistence_xml, listPUHolders, deploymentUnit);
                    holder = normalize(listPUHolders);
                    resourceRoot.putAttachment(PersistenceUnitMetadataHolder.PERSISTENCE_UNITS, holder);
                    addApplicationDependenciesOnProvider( deploymentUnit, holder);
                    markDU(holder, deploymentUnit);
                    puCount += holder.getPersistenceUnits().size();
                }
            }
            ROOT_LOGGER.tracef("parsed persistence unit definitions for war %s", deploymentRoot.getRootName());

            incrementPersistenceUnitCount(deploymentUnit, puCount);
        }
    }

    private void handleEarDeployment(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        if (isEarDeployment(deploymentUnit)) {

            int puCount = 0;
            // ordered list of PUs
            List<PersistenceUnitMetadataHolder> listPUHolders = new ArrayList<PersistenceUnitMetadataHolder>(1);
            // handle META-INF/persistence.xml
            final ResourceRoot deploymentRoot = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
            VirtualFile persistence_xml = deploymentRoot.getRoot().getChild(META_INF_PERSISTENCE_XML);
            parse(persistence_xml, listPUHolders, deploymentUnit);
            PersistenceUnitMetadataHolder holder = normalize(listPUHolders);
            deploymentRoot.putAttachment(PersistenceUnitMetadataHolder.PERSISTENCE_UNITS, holder);
            addApplicationDependenciesOnProvider( deploymentUnit, holder);
            markDU(holder, deploymentUnit);
            puCount = holder.getPersistenceUnits().size();
            // Parsing persistence.xml in EJB jar/war files is handled as subdeployments.
            // We need to handle jars in the EAR/lib folder here
            List<ResourceRoot> resourceRoots = deploymentUnit.getAttachmentList(Attachments.RESOURCE_ROOTS);
            for (ResourceRoot resourceRoot : resourceRoots) {
                // look at lib/*.jar files that aren't subdeployments (subdeployments are passed
                // to deploy(DeploymentPhaseContext)).
                if (!SubDeploymentMarker.isSubDeployment(resourceRoot) &&
                    resourceRoot.getRoot().getName().toLowerCase(Locale.ENGLISH).endsWith(JAR_FILE_EXTENSION) &&
                    resourceRoot.getRoot().getParent().getName().equals(LIB_FOLDER)) {
                    listPUHolders = new ArrayList<PersistenceUnitMetadataHolder>(1);
                    persistence_xml = resourceRoot.getRoot().getChild(META_INF_PERSISTENCE_XML);
                    parse(persistence_xml, listPUHolders, deploymentUnit);
                    holder = normalize(listPUHolders);
                    resourceRoot.putAttachment(PersistenceUnitMetadataHolder.PERSISTENCE_UNITS, holder);
                    addApplicationDependenciesOnProvider( deploymentUnit, holder);
                    markDU(holder, deploymentUnit);
                    puCount += holder.getPersistenceUnits().size();
                }
            }
            ROOT_LOGGER.tracef("parsed persistence unit definitions for ear %s", deploymentRoot.getRootName());
            incrementPersistenceUnitCount(deploymentUnit, puCount);
        }
    }

    private void parse(
            final VirtualFile persistence_xml,
            final List<PersistenceUnitMetadataHolder> listPUHolders,
            final DeploymentUnit deploymentUnit)
        throws DeploymentUnitProcessingException {

        ROOT_LOGGER.tracef("parse checking if %s exists, result = %b",persistence_xml.toString(), persistence_xml.exists());
        if (persistence_xml.exists() && persistence_xml.isFile()) {
            InputStream is = null;
            try {
                is = persistence_xml.openStream();
                final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
                inputFactory.setXMLResolver(NoopXMLResolver.create());
                XMLStreamReader xmlReader = inputFactory.createXMLStreamReader(is);
                PersistenceUnitMetadataHolder puHolder = PersistenceUnitXmlParser.parse(xmlReader, SpecDescriptorPropertyReplacement.propertyReplacer(deploymentUnit));

                postParseSteps(persistence_xml, puHolder, deploymentUnit);
                listPUHolders.add(puHolder);
            } catch (Exception e) {
                throw new DeploymentUnitProcessingException(JpaLogger.ROOT_LOGGER.failedToParse(persistence_xml), e);
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
    }

    /**
     * Some of this might need to move to the install phase
     *
     * @param persistence_xml
     * @param puHolder
     */
    private void postParseSteps(
        final VirtualFile persistence_xml,
        final PersistenceUnitMetadataHolder puHolder,
        final DeploymentUnit deploymentUnit ) {

        for (PersistenceUnitMetadata pu : puHolder.getPersistenceUnits()) {
            // set URLs
            List<URL> jarfilesUrls = new ArrayList<URL>();
            if (pu.getJarFiles() != null) {
                for (String jar : pu.getJarFiles()) {
                    jarfilesUrls.add(getRelativeURL(persistence_xml, jar));
                }
            }
            pu.setJarFileUrls(jarfilesUrls);
            URL url = getPersistenceUnitURL(persistence_xml);
            pu.setPersistenceUnitRootUrl(url);
            String scopedPersistenceUnitName;

            /**
             * WFLY-5478 allow custom scoped persistence unit name hint in persistence unit definition.
             * Specified scoped persistence unit name needs to be unique across application server deployments.
             * Application is responsible for picking a unique name.
             * Currently, a non-unique name will result in a DuplicateServiceException deployment failure:
             *   org.jboss.msc.service.DuplicateServiceException: Service jboss.persistenceunit.my2lccustom#test_pu.__FIRST_PHASE__ is already registered
             */
            scopedPersistenceUnitName = Configuration.getScopedPersistenceUnitName(pu);
            if (scopedPersistenceUnitName == null) {
                scopedPersistenceUnitName = createBeanName(deploymentUnit, pu.getPersistenceUnitName());
            } else {
                ROOT_LOGGER.tracef("persistence unit '%s' specified a custom scoped persistence unit name hint " +
                        "(jboss.as.jpa.scopedname=%s).  The specified name *must* be unique across all application server deployments.",
                        pu.getPersistenceUnitName(),
                        scopedPersistenceUnitName);
                if (scopedPersistenceUnitName.indexOf('/') != -1) {
                    throw JpaLogger.ROOT_LOGGER.invalidScopedName(scopedPersistenceUnitName, '/');
                }
            }

            pu.setScopedPersistenceUnitName(scopedPersistenceUnitName);
        }
    }

    private static URL getRelativeURL(VirtualFile persistence_xml, String jar) {
        try {
            return new URL(jar);
        } catch (MalformedURLException e) {
            try {
                VirtualFile deploymentUnitFile = persistence_xml;
                //we need the parent 3 units up, 1 is META-INF, 2nd is the actual jar, 3rd is the jar files parent
                VirtualFile parent = deploymentUnitFile.getParent().getParent().getParent();
                VirtualFile baseDir = (parent != null ? parent : deploymentUnitFile);
                VirtualFile jarFile = baseDir.getChild(jar);
                if (jarFile == null)
                    throw JpaLogger.ROOT_LOGGER.childNotFound(jar, baseDir);
                return jarFile.toURL();
            } catch (Exception e1) {
                throw JpaLogger.ROOT_LOGGER.relativePathNotFound(e1, jar);
            }
        }
    }

    private URL getPersistenceUnitURL(VirtualFile persistence_xml) {
        try {
            VirtualFile metaData = persistence_xml;// di.getMetaDataFile("persistence.xml");
            return metaData.getParent().getParent().toURL();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Eliminate duplicate PU definitions from clustering the deployment (first definition will win)
     *

     * JPA 8.2  A persistence unit must have a name. Only one persistence unit of any given name must be defined
     * within a single EJB-JAR file, within a single WAR file, within a single application client jar, or within
     * an EAR. See Section 8.2.2, “Persistence Unit Scope”.
     *
     * @param listPUHolders
     * @return
     */
    private PersistenceUnitMetadataHolder normalize(List<PersistenceUnitMetadataHolder> listPUHolders) {
        // eliminate duplicates (keeping the first instance of each PU by name)
        Map<String, PersistenceUnitMetadata> flattened = new HashMap<String, PersistenceUnitMetadata>();
        for (PersistenceUnitMetadataHolder puHolder : listPUHolders) {
            for (PersistenceUnitMetadata pu : puHolder.getPersistenceUnits()) {
                if (!flattened.containsKey(pu.getPersistenceUnitName())) {
                    flattened.put(pu.getPersistenceUnitName(), pu);
                } else {
                    PersistenceUnitMetadata first = flattened.get(pu.getPersistenceUnitName());
                    PersistenceUnitMetadata duplicate = pu;
                    ROOT_LOGGER.duplicatePersistenceUnitDefinition(duplicate.getPersistenceUnitName(), first.getScopedPersistenceUnitName(), duplicate.getScopedPersistenceUnitName());
                }
            }
        }
        PersistenceUnitMetadataHolder holder = new PersistenceUnitMetadataHolder(new ArrayList<PersistenceUnitMetadata>(flattened.values()));
        return holder;
    }

    static boolean isEarDeployment(final DeploymentUnit context) {
        return (DeploymentTypeMarker.isType(DeploymentType.EAR, context));
    }

    static boolean isWarDeployment(final DeploymentUnit context) {
        return (DeploymentTypeMarker.isType(DeploymentType.WAR, context));
    }

    private static String getScopedDeploymentUnitPath(DeploymentUnit deploymentUnit) {
        ArrayList<String> parts = new ArrayList<String>();  // order of deployment elements will start with parent

        do {
            final ResourceRoot deploymentRoot = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
            DeploymentUnit parentdeploymentUnit = deploymentUnit.getParent();
            if (parentdeploymentUnit != null) {
                ResourceRoot parentDeploymentRoot = parentdeploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
                parts.add(0, deploymentRoot.getRoot().getPathNameRelativeTo(parentDeploymentRoot.getRoot()));
            } else {
                parts.add(0, deploymentRoot.getRoot().getName());
            }
        }
        while ((deploymentUnit = deploymentUnit.getParent()) != null);

        StringBuilder result = new StringBuilder();
        boolean needSeparator = false;
        for (String part : parts) {
            if (needSeparator) {
                result.append('/');
            }
            result.append(part);
            needSeparator = true;
        }
        return result.toString();
    }

    // make scoped pu name (e.g. test2.ear/w2.war#warPUnit_PU or test3.jar#CTS-EXT-UNIT)
    // old as6 names looked like:  persistence.unit:unitName=ejb3_ext_propagation.ear/lib/ejb3_ext_propagation.jar#CTS-EXT-UNIT
    public static String createBeanName(DeploymentUnit deploymentUnit, String persistenceUnitName) {
        // persistenceUnitName must be a simple name
        if (persistenceUnitName.indexOf('/') != -1) {
            throw JpaLogger.ROOT_LOGGER.invalidPersistenceUnitName(persistenceUnitName, '/');
        }
        if (persistenceUnitName.indexOf('#') != -1) {
            throw JpaLogger.ROOT_LOGGER.invalidPersistenceUnitName(persistenceUnitName, '#');
        }

        String unitName = getScopedDeploymentUnitPath(deploymentUnit) + "#" + persistenceUnitName;
        return unitName;
    }

    private void markDU(PersistenceUnitMetadataHolder holder, DeploymentUnit deploymentUnit) {
        if (holder.getPersistenceUnits() != null && holder.getPersistenceUnits().size() > 0) {
            JPADeploymentMarker.mark(deploymentUnit);
        }
    }

    private void incrementPersistenceUnitCount(DeploymentUnit topDeploymentUnit, int persistenceUnitCount) {
        topDeploymentUnit = DeploymentUtils.getTopDeploymentUnit(topDeploymentUnit);

        // create persistence unit counter if not done already
        synchronized (topDeploymentUnit) {  // ensure that only one deployment thread sets this at a time
            PersistenceUnitsInApplication persistenceUnitsInApplication = getPersistenceUnitsInApplication(topDeploymentUnit);
            persistenceUnitsInApplication.increment(persistenceUnitCount);
        }
        ROOT_LOGGER.tracef("incrementing PU count for %s by %d", topDeploymentUnit.getName(), persistenceUnitCount);
    }

    private void addApplicationDependenciesOnProvider(DeploymentUnit topDeploymentUnit, PersistenceUnitMetadataHolder holder) {
        topDeploymentUnit = DeploymentUtils.getTopDeploymentUnit(topDeploymentUnit);

        synchronized (topDeploymentUnit) {  // ensure that only one deployment thread sets this at a time
            PersistenceUnitsInApplication persistenceUnitsInApplication = getPersistenceUnitsInApplication(topDeploymentUnit);
            persistenceUnitsInApplication.addPersistenceUnitHolder(holder);
        }
    }

    private PersistenceUnitsInApplication getPersistenceUnitsInApplication(DeploymentUnit topDeploymentUnit) {

        PersistenceUnitsInApplication persistenceUnitsInApplication = topDeploymentUnit.getAttachment(PersistenceUnitsInApplication.PERSISTENCE_UNITS_IN_APPLICATION);
        if (persistenceUnitsInApplication == null) {
            persistenceUnitsInApplication = new PersistenceUnitsInApplication();
            topDeploymentUnit.putAttachment(PersistenceUnitsInApplication.PERSISTENCE_UNITS_IN_APPLICATION, persistenceUnitsInApplication);
        }
        return persistenceUnitsInApplication;
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, 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.jms;

import static org.wildfly.extension.messaging.activemq.AbstractTransportDefinition.CONNECTOR_CAPABILITY_NAME;
import static org.wildfly.extension.messaging.activemq.jms.ConnectionFactoryAttribute.getDefinitions;

import java.util.Arrays;
import java.util.Collection;

import org.jboss.as.controller.AbstractAttributeDefinitionBuilder;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.AttributeMarshaller;
import org.jboss.as.controller.AttributeParser;
import org.jboss.as.controller.PrimitiveListAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleListAttributeDefinition;
import org.jboss.as.controller.SimpleMapAttributeDefinition;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.StringListAttributeDefinition;
import org.jboss.as.controller.capability.RuntimeCapability;
import org.jboss.as.controller.registry.AttributeAccess;
import org.jboss.dmr.ModelNode;
import org.wildfly.extension.messaging.activemq.AbstractTransportDefinition;
import org.wildfly.extension.messaging.activemq.CommonAttributes;
import org.wildfly.extension.messaging.activemq.MessagingExtension;
import org.wildfly.extension.messaging.activemq.jms.ConnectionFactoryAttributes.Common;
import org.wildfly.extension.messaging.activemq.jms.ConnectionFactoryAttributes.Pooled;

/**
 * JMS external pooled Connection Factory resource definition.
 * By 'external' it means that this PCF is targeting an external broker.
 * @author Emmanuel Hugonnet (c) 2019 Red Hat, Inc.
 */
public class ExternalPooledConnectionFactoryDefinition extends PooledConnectionFactoryDefinition {

    static final String CAPABILITY_NAME = "org.wildfly.messaging.activemq.external.connection-factory";
    static final RuntimeCapability<Void> CAPABILITY = RuntimeCapability.Builder.of(CAPABILITY_NAME, true, ExternalConnectionFactoryService.class).
            build();

    // the generation of the Pooled CF attributes is a bit ugly but it is with purpose:
    // * factorize the attributes which are common between the regular CF and the pooled CF
    // * keep in a single place the subtle differences (e.g. different default values for reconnect-attempts between
    //   the regular and pooled CF
    private static ConnectionFactoryAttribute[] define(ConnectionFactoryAttribute[] specific, ConnectionFactoryAttribute... common) {
        int size = common.length + specific.length;
        ConnectionFactoryAttribute[] result = new ConnectionFactoryAttribute[size];
        for (int i = 0; i < specific.length; i++) {
            ConnectionFactoryAttribute attr = specific[i];
            AttributeDefinition definition = attr.getDefinition();
            if (definition == ConnectionFactoryAttributes.Pooled.INITIAL_CONNECT_ATTEMPTS) {
                result[i] = ConnectionFactoryAttribute.create(
                        SimpleAttributeDefinitionBuilder
                                .create(ConnectionFactoryAttributes.Pooled.INITIAL_CONNECT_ATTEMPTS)
                                .setDefaultValue(new ModelNode(-1))
                                .build(),
                        attr.getPropertyName(),
                        true);
            } else {
                result[i] = attr;
            }
        }
        for (int i = 0; i < common.length; i++) {
            ConnectionFactoryAttribute attr = common[i];
            AttributeDefinition definition = attr.getDefinition();

            ConnectionFactoryAttribute newAttr;
            // replace the reconnect-attempts attribute to use a different default value for pooled CF
            if (definition == Common.RECONNECT_ATTEMPTS) {
                AttributeDefinition copy = copy(Pooled.RECONNECT_ATTEMPTS, AttributeAccess.Flag.RESTART_ALL_SERVICES);
                newAttr = ConnectionFactoryAttribute.create(copy, Pooled.RECONNECT_ATTEMPTS_PROP_NAME, true);
            } else if (definition == CommonAttributes.HA) {
                newAttr = ConnectionFactoryAttribute.create(
                        SimpleAttributeDefinitionBuilder
                                .create(CommonAttributes.HA)
                                .setDefaultValue(ModelNode.TRUE)
                                .setFlags(AttributeAccess.Flag.RESTART_ALL_SERVICES)
                                .build(),
                        attr.getPropertyName(),
                        true);
            } else if (definition == Common.CONNECTORS) {
                StringListAttributeDefinition copy = new StringListAttributeDefinition.Builder(Common.CONNECTORS)
                        .setAlternatives(CommonAttributes.DISCOVERY_GROUP)
                        .setRequired(true)
                        .setAttributeParser(AttributeParser.STRING_LIST)
                        .setAttributeMarshaller(AttributeMarshaller.STRING_LIST)
                        .setCapabilityReference(new AbstractTransportDefinition.TransportCapabilityReferenceRecorder(CAPABILITY_NAME, CONNECTOR_CAPABILITY_NAME, true))
                        .setRestartAllServices()
                        .build();
                newAttr = ConnectionFactoryAttribute.create(copy, attr.getPropertyName(), attr.isResourceAdapterProperty(), attr.getConfigType());
            } else {
                AttributeDefinition copy = copy(definition, AttributeAccess.Flag.RESTART_ALL_SERVICES);
                newAttr = ConnectionFactoryAttribute.create(copy, attr.getPropertyName(), attr.isResourceAdapterProperty(), attr.getConfigType());
            }
            result[specific.length + i] = newAttr;
        }
        return result;
    }

    private static AttributeDefinition copy(AttributeDefinition attribute, AttributeAccess.Flag flag) {
        AbstractAttributeDefinitionBuilder builder;
        if (attribute instanceof SimpleListAttributeDefinition) {
            builder = new SimpleListAttributeDefinition.Builder((SimpleListAttributeDefinition) attribute);
        } else if (attribute instanceof SimpleMapAttributeDefinition) {
            builder = new SimpleMapAttributeDefinition.Builder((SimpleMapAttributeDefinition) attribute);
        } else if (attribute instanceof PrimitiveListAttributeDefinition) {
            builder = new PrimitiveListAttributeDefinition.Builder((PrimitiveListAttributeDefinition) attribute);
        } else {
            builder = new SimpleAttributeDefinitionBuilder((SimpleAttributeDefinition) attribute);
        }
        builder.setFlags(flag);
        return builder.build();
    }

    public static final ConnectionFactoryAttribute[] ATTRIBUTES = define(Pooled.ATTRIBUTES, Common.ATTRIBUTES);

    public static final ExternalPooledConnectionFactoryDefinition INSTANCE = new ExternalPooledConnectionFactoryDefinition(false);

    public static final ExternalPooledConnectionFactoryDefinition DEPLOYMENT_INSTANCE = new ExternalPooledConnectionFactoryDefinition(true);

    public ExternalPooledConnectionFactoryDefinition(final boolean deployed) {
        super(new SimpleResourceDefinition.Parameters(MessagingExtension.POOLED_CONNECTION_FACTORY_PATH, MessagingExtension.getResourceDescriptionResolver(CommonAttributes.POOLED_CONNECTION_FACTORY))
                .setAddHandler(ExternalPooledConnectionFactoryAdd.INSTANCE)
                .setRemoveHandler(ExternalPooledConnectionFactoryRemove.INSTANCE)
                .setCapabilities(CAPABILITY), deployed);
    }

    @Override
    public Collection<AttributeDefinition> getAttributes() {
        return Arrays.asList(getDefinitions(ATTRIBUTES));
    }
}


/*
 * 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.ee.utils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import org.jboss.as.ee.logging.EeLogger;

/**
 * Utility class for working with method descriptors
 *
 * @author Stuart Douglas
 */
public class DescriptorUtils {

    private static final Map<Class<?>, String> primitives;

    static {
        Map<Class<?>, String> p = new IdentityHashMap<Class<?>, String>();
        p.put(void.class, "V");
        p.put(byte.class, "B");
        p.put(char.class, "C");
        p.put(double.class, "D");
        p.put(float.class, "F");
        p.put(int.class, "I");
        p.put(long.class, "J");
        p.put(short.class, "S");
        p.put(boolean.class, "Z");
        primitives = Collections.unmodifiableMap(p);
    }

    /**
     * Changes a class name to the internal form suitable for use in a descriptor string.
     * <p/>
     * e.g. java.lang.String => Ljava/lang/String;
     */
    public static String makeDescriptor(String className) {
        String repl = className.replace(".", "/");
        return 'L' + repl + ';';
    }

    public static String makeDescriptor(Class<?> c) {
        String primitive = primitives.get(c);
        if(primitive != null) {
            return primitive;
        }else if (c.isArray()) {
            return c.getName().replace(".", "/");
        } else {
            return makeDescriptor(c.getName());
        }
    }

    public static String makeDescriptor(Constructor<?> c) {
        StringBuilder desc = new StringBuilder("(");
        for (Class<?> p : c.getParameterTypes()) {
            desc.append(DescriptorUtils.makeDescriptor(p));
        }
        desc.append(")");
        desc.append("V");
        return desc.toString();
    }

    /**
     * returns an array of String representations of the parameter types. Primitives are returned as their native
     * representations, while classes are returned in the internal descriptor form e.g. Ljava/lang/Integer;
     */
    public static String[] parameterDescriptors(String methodDescriptor) {
        int i = 1; // char 0 is a '('
        List<String> ret = new ArrayList<String>();
        int arrayStart = -1;
        while (methodDescriptor.charAt(i) != ')') {
            String type = null;
            if (methodDescriptor.charAt(i) == '[') {
                if (arrayStart == -1) {
                    arrayStart = i;
                }
            } else {
                if (methodDescriptor.charAt(i) == 'L') {
                    int start = i;
                    i++;
                    while (methodDescriptor.charAt(i) != ';') {
                        ++i;
                    }
                    if (arrayStart == -1) {
                        type = methodDescriptor.substring(start, i);
                    } else {
                        type = methodDescriptor.substring(arrayStart, i);
                    }
                } else {
                    if (arrayStart == -1) {
                        type = methodDescriptor.charAt(i) + "";
                    } else {
                        type = methodDescriptor.substring(arrayStart, i + 1);
                    }
                }
                arrayStart = -1;
                ret.add(type);
            }
            ++i;
        }
        String[] r = new String[ret.size()];
        for (int j = 0; j < ret.size(); ++j) {
            r[j] = ret.get(j);
        }
        return r;
    }

    public static String[] parameterDescriptors(Method m) {
        return parameterDescriptors(m.getParameterTypes());
    }

    public static String[] parameterDescriptors(Class<?>[] parameters) {
        String[] ret = new String[parameters.length];
        for (int i = 0; i < ret.length; ++i) {
            ret[i] = DescriptorUtils.makeDescriptor(parameters[i]);
        }
        return ret;
    }

    public static String returnType(String methodDescriptor) {
        return methodDescriptor.substring(methodDescriptor.lastIndexOf(')') + 1, methodDescriptor.length());
    }

    /**
     * returns true if the descriptor represents a primitive type
     */
    public static boolean isPrimitive(String descriptor) {
        if (descriptor.length() == 1) {
            return true;
        }
        return false;
    }

    public static String methodDescriptor(Method m) {
        StringBuilder desc = new StringBuilder("(");
        for (Class<?> p : m.getParameterTypes()) {
            desc.append(DescriptorUtils.makeDescriptor(p));
        }
        desc.append(")");
        desc.append(DescriptorUtils.makeDescriptor(m.getReturnType()));
        return desc.toString();
    }

    public static String methodDescriptor(String[] parameters, String returnType) {
        StringBuilder desc = new StringBuilder("(");
        for (String p : parameters) {
            desc.append(p);
        }
        desc.append(")");
        desc.append(returnType);
        return desc.toString();
    }

    /**
     * performs basic validation on a descriptor
     */
    public static String validateDescriptor(String descriptor) {
        if (descriptor.length() == 0) {
            throw EeLogger.ROOT_LOGGER.cannotBeEmpty("descriptors");
        }
        if (descriptor.length() > 1) {
            if (descriptor.startsWith("L")) {
                if (!descriptor.endsWith(";")) {
                    throw EeLogger.ROOT_LOGGER.invalidDescriptor(descriptor);
                }
            } else if (descriptor.startsWith("[")) {

            } else {
                    throw EeLogger.ROOT_LOGGER.invalidDescriptor(descriptor);
            }
        } else {
            char type = descriptor.charAt(0);
            switch (type) {
                case 'I':
                case 'Z':
                case 'S':
                case 'B':
                case 'F':
                case 'D':
                case 'V':
                case 'J':
                case 'C':
                    break;
                default:
                    throw EeLogger.ROOT_LOGGER.invalidDescriptor(descriptor);
            }
        }
        return descriptor;
    }
}


/*
 * 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.pojo;

import org.jboss.as.pojo.descriptor.BeanMetaDataConfig;
import org.jboss.as.pojo.descriptor.ConfigVisitor;
import org.jboss.as.pojo.descriptor.DefaultConfigVisitor;
import org.jboss.as.pojo.descriptor.KernelDeploymentXmlDescriptor;
import org.jboss.as.pojo.logging.PojoLogger;
import org.jboss.as.pojo.service.DescribedPojoPhase;
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.reflect.DeploymentReflectionIndex;
import org.jboss.modules.Module;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceTarget;

import java.util.List;

/**
 * DeploymentUnit processor responsible for taking KernelDeploymentXmlDescriptor
 * configuration and creating the corresponding services.
 *
 * @author Ales Justin
 */
public class ParsedKernelDeploymentProcessor implements DeploymentUnitProcessor {
    /**
     * Process a deployment for KernelDeployment configuration.
     * Will install a {@code POJO} for each configured bean.
     *
     * @param phaseContext the deployment unit context
     * @throws DeploymentUnitProcessingException
     *
     */
    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final DeploymentUnit unit = phaseContext.getDeploymentUnit();
        final List<KernelDeploymentXmlDescriptor> kdXmlDescriptors = unit.getAttachment(KernelDeploymentXmlDescriptor.ATTACHMENT_KEY);
        if (kdXmlDescriptors == null || kdXmlDescriptors.isEmpty())
            return;

        final Module module = unit.getAttachment(Attachments.MODULE);
        if (module == null)
            throw PojoLogger.ROOT_LOGGER.noModuleFound(unit);

        final ServiceTarget serviceTarget = phaseContext.getServiceTarget();
        final DeploymentReflectionIndex index = unit.getAttachment(Attachments.REFLECTION_INDEX);
        if (index == null)
            throw PojoLogger.ROOT_LOGGER.missingReflectionIndex(unit);

        for (KernelDeploymentXmlDescriptor kdXmlDescriptor : kdXmlDescriptors) {
            final List<BeanMetaDataConfig> beanConfigs = kdXmlDescriptor.getBeans();
            for (final BeanMetaDataConfig beanConfig : beanConfigs) {
                describeBean(module, serviceTarget, index, beanConfig);
            }
            // TODO -- KD::classloader, KD::aliases
        }
    }

    public void undeploy(final DeploymentUnit context) {
    }

    protected void describeBean(final Module module, final ServiceTarget serviceTarget, DeploymentReflectionIndex deploymentIndex, BeanMetaDataConfig beanConfig) {
        final BeanState state = BeanState.NOT_INSTALLED;
        final ServiceName describedServiceName = BeanMetaDataConfig.toBeanName(beanConfig.getName(), state.next());
        final DescribedPojoPhase describedService = new DescribedPojoPhase(deploymentIndex, beanConfig);
        final ServiceBuilder describedServiceBuilder = serviceTarget.addService(describedServiceName, describedService);
        describedService.registerAliases(describedServiceBuilder);
        final ConfigVisitor visitor = new DefaultConfigVisitor(describedServiceBuilder, state, module, deploymentIndex);
        beanConfig.visit(visitor);
        describedServiceBuilder.setInitialMode(beanConfig.getMode().getMode());
        describedServiceBuilder.install();
    }
}