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

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.ws.rs.core.Application;

import org.jboss.as.controller.PathElement;
import org.jboss.as.jaxrs.DeploymentRestResourcesDefintion;
import org.jboss.as.jaxrs.JaxrsAnnotations;
import org.jboss.as.jaxrs.JaxrsExtension;
import org.jboss.as.jaxrs.logging.JaxrsLogger;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentResourceSupport;
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.as.web.common.WarMetaData;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.metadata.javaee.spec.ParamValueMetaData;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import org.jboss.metadata.web.spec.FilterMetaData;
import org.jboss.metadata.web.spec.ServletMetaData;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoadException;
import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher;
import org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrapClasses;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;

import static org.jboss.as.jaxrs.logging.JaxrsLogger.JAXRS_LOGGER;
import static org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters.RESTEASY_SCAN;
import static org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters.RESTEASY_SCAN_PROVIDERS;
import static org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters.RESTEASY_SCAN_RESOURCES;

/**
 * Processor that finds jax-rs classes in the deployment
 *
 * @author Stuart Douglas
 */
public class JaxrsScanningProcessor implements DeploymentUnitProcessor {

    private static final DotName DECORATOR = DotName.createSimple("javax.decorator.Decorator");

    public static final DotName APPLICATION = DotName.createSimple(Application.class.getName());
    private static final String ORG_APACHE_CXF = "org.apache.cxf";

    @Override
    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        if (!JaxrsDeploymentMarker.isJaxrsDeployment(deploymentUnit)) {
            return;
        }
        final DeploymentUnit parent = deploymentUnit.getParent() == null ? deploymentUnit : deploymentUnit.getParent();
        final Map<ModuleIdentifier, ResteasyDeploymentData> deploymentData;
        if (deploymentUnit.getParent() == null) {
            deploymentData = Collections.synchronizedMap(new HashMap<ModuleIdentifier, ResteasyDeploymentData>());
            deploymentUnit.putAttachment(JaxrsAttachments.ADDITIONAL_RESTEASY_DEPLOYMENT_DATA, deploymentData);
        } else {
            deploymentData = parent.getAttachment(JaxrsAttachments.ADDITIONAL_RESTEASY_DEPLOYMENT_DATA);
        }

        final ModuleIdentifier moduleIdentifier = deploymentUnit.getAttachment(Attachments.MODULE_IDENTIFIER);

        ResteasyDeploymentData resteasyDeploymentData = new ResteasyDeploymentData();
        final WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
        final Module module = deploymentUnit.getAttachment(Attachments.MODULE);

        try {

            if (warMetaData == null) {
                resteasyDeploymentData.setScanAll(true);
                scan(deploymentUnit, module.getClassLoader(), resteasyDeploymentData);
                deploymentData.put(moduleIdentifier, resteasyDeploymentData);
            } else {
                scanWebDeployment(deploymentUnit, warMetaData.getMergedJBossWebMetaData(), module.getClassLoader(), resteasyDeploymentData);
                scan(deploymentUnit, module.getClassLoader(), resteasyDeploymentData);

                // When BootStrap classes are present and no Application subclass declared
                // must check context param for Application subclass declaration
                if (resteasyDeploymentData.getScannedResourceClasses().isEmpty() &&
                    !resteasyDeploymentData.isDispatcherCreated() &&
                    hasBootClasses(warMetaData.getMergedJBossWebMetaData())) {
                    checkOtherParams(deploymentUnit, warMetaData.getMergedJBossWebMetaData(), module.getClassLoader(), resteasyDeploymentData);
                }
            }
            deploymentUnit.putAttachment(JaxrsAttachments.RESTEASY_DEPLOYMENT_DATA, resteasyDeploymentData);
            List<String> rootRestClasses = new ArrayList<>(resteasyDeploymentData.getScannedResourceClasses());
            Collections.sort(rootRestClasses);
            for(String cls: rootRestClasses) {
                addManagement(deploymentUnit, cls);
            }
        } catch (ModuleLoadException e) {
            throw new DeploymentUnitProcessingException(e);
        }
    }

    private void addManagement(DeploymentUnit deploymentUnit, String componentClass) {
        try {
            final DeploymentResourceSupport deploymentResourceSupport = deploymentUnit.getAttachment(org.jboss.as.server.deployment.Attachments.DEPLOYMENT_RESOURCE_SUPPORT);
            deploymentResourceSupport.getDeploymentSubModel(JaxrsExtension.SUBSYSTEM_NAME, PathElement.pathElement(DeploymentRestResourcesDefintion.REST_RESOURCE_NAME, componentClass));
        } catch (Exception e) {
            JaxrsLogger.JAXRS_LOGGER.failedToRegisterManagementViewForRESTResources(componentClass, e);
        }
    }

    private void checkOtherParams(final DeploymentUnit du,
                                  final JBossWebMetaData webdata,
                                  final ClassLoader classLoader,
                                  final ResteasyDeploymentData resteasyDeploymentData)
        throws DeploymentUnitProcessingException{

        HashSet<String> appClazzList = new HashSet<>();
        List<ParamValueMetaData> contextParamList = webdata.getContextParams();
        if (contextParamList !=null) {
            for(ParamValueMetaData param: contextParamList) {
                if ("javax.ws.rs.core.Application".equals(param.getParamName())) {
                    appClazzList.add(param.getParamValue());
                }
            }
        }

        if (webdata.getServlets() != null) {
            for (ServletMetaData servlet : webdata.getServlets()) {
                List<ParamValueMetaData> initParamList = servlet.getInitParam();
                if (initParamList != null) {
                    for(ParamValueMetaData param: initParamList) {
                        if ("javax.ws.rs.core.Application".equals(param.getParamName())) {
                            appClazzList.add(param.getParamValue());
                        }
                    }
                }
            }
        }

        processDeclaredApplicationClasses(du, appClazzList, webdata, classLoader, resteasyDeploymentData);
    }

    private void processDeclaredApplicationClasses(final DeploymentUnit du,
                                                   final Set<String> appClazzList,
                                                 final JBossWebMetaData webdata,
                                                 final ClassLoader classLoader,
                                                 final ResteasyDeploymentData resteasyDeploymentData)
        throws DeploymentUnitProcessingException {

        final CompositeIndex index = du.getAttachment(Attachments.COMPOSITE_ANNOTATION_INDEX);
        List<AnnotationInstance> resources = index.getAnnotations(JaxrsAnnotations.PATH.getDotName());
        Map<String, ClassInfo> resourceMap = new HashMap<>(resources.size());
        if (resources != null) {
           for (AnnotationInstance a: resources) {
               if (a.target() instanceof ClassInfo) {
                   resourceMap.put(((ClassInfo)a.target()).name().toString(),
                       (ClassInfo)a.target());
               }
           }
        }

        for (String clazzName: appClazzList) {
            Class<?> clazz = null;
            try {
                clazz = classLoader.loadClass(clazzName);
            } catch (ClassNotFoundException e) {
                throw new DeploymentUnitProcessingException(e);
            }

            if (Application.class.isAssignableFrom(clazz)) {
                try {
                    Application appClazz = (Application) clazz.newInstance();
                    Set<Class<?>> declClazzs = appClazz.getClasses();
                    Set<Object> declSingletons = appClazz.getSingletons();
                    HashSet<Class<?>> clazzSet = new HashSet<>();
                    if (declClazzs != null) {
                        clazzSet.addAll(declClazzs);
                    }
                    if (declSingletons != null) {
                        for (Object obj : declSingletons) {
                            clazzSet.add((Class) obj);
                        }
                    }

                    Set<String> scannedResourceClasses = resteasyDeploymentData.getScannedResourceClasses();
                    for (Class<?> cClazz : clazzSet) {
                        if (cClazz.isAnnotationPresent(javax.ws.rs.Path.class)) {
                            final ClassInfo info = resourceMap.get(cClazz.getName());
                            if (info != null) {
                                if (info.annotations().containsKey(DECORATOR)) {
                                    //we do not add decorators as resources
                                    //we can't pick up on programatically added decorators, but that is such an edge case it should not really matter
                                    continue;
                                }
                                if (!Modifier.isInterface(info.flags())) {
                                    scannedResourceClasses.add(info.name().toString());
                                }
                            }
                        }
                    }

                } catch (Exception e) {
                    JAXRS_LOGGER.cannotLoadApplicationClass(e);
                }
            }
        }
    }

    @Override
    public void undeploy(DeploymentUnit context) {

    }

    public static final Set<String> BOOT_CLASSES = new HashSet<String>();

    static {
        Collections.addAll(BOOT_CLASSES, ResteasyBootstrapClasses.BOOTSTRAP_CLASSES);
    }

    /**
     * If any servlet/filter classes are declared, then we probably don't want to scan.
     */
    protected boolean hasBootClasses(JBossWebMetaData webdata) throws DeploymentUnitProcessingException {
        if (webdata.getServlets() != null) {
            for (ServletMetaData servlet : webdata.getServlets()) {
                String servletClass = servlet.getServletClass();
                if (BOOT_CLASSES.contains(servletClass))
                    return true;
            }
        }
        if (webdata.getFilters() != null) {
            for (FilterMetaData filter : webdata.getFilters()) {
                if (BOOT_CLASSES.contains(filter.getFilterClass()))
                    return true;
            }
        }
        return false;

    }

    protected void scanWebDeployment(final DeploymentUnit du, final JBossWebMetaData webdata, final ClassLoader classLoader, final ResteasyDeploymentData resteasyDeploymentData) throws DeploymentUnitProcessingException {

        // If there is a resteasy boot class in web.xml, then the default should be to not scan
        // make sure this call happens before checkDeclaredApplicationClassAsServlet!!!
        boolean hasBoot = hasBootClasses(webdata);
        resteasyDeploymentData.setBootClasses(hasBoot);

        Class<?> declaredApplicationClass = checkDeclaredApplicationClassAsServlet(webdata, classLoader);
        // Assume that checkDeclaredApplicationClassAsServlet created the dispatcher
        if (declaredApplicationClass != null) {
            resteasyDeploymentData.setDispatcherCreated(true);

            // Instigate creation of resteasy configuration switches for
            // found provider and resource classes
            resteasyDeploymentData.setScanProviders(true);
            resteasyDeploymentData.setScanResources(true);
        }

        // set scanning on only if there are no boot classes
        if (!hasBoot && !webdata.isMetadataComplete()) {
            resteasyDeploymentData.setScanAll(true);
            resteasyDeploymentData.setScanProviders(true);
            resteasyDeploymentData.setScanResources(true);
        }

        // check resteasy configuration flags

        List<ParamValueMetaData> contextParams = webdata.getContextParams();

        if (contextParams != null) {
            for (ParamValueMetaData param : contextParams) {
                if (param.getParamName().equals(RESTEASY_SCAN)) {
                    resteasyDeploymentData.setScanAll(valueOf(RESTEASY_SCAN, param.getParamValue()));
                } else if (param.getParamName().equals(ResteasyContextParameters.RESTEASY_SCAN_PROVIDERS)) {
                    resteasyDeploymentData.setScanProviders(valueOf(RESTEASY_SCAN_PROVIDERS, param.getParamValue()));
                } else if (param.getParamName().equals(RESTEASY_SCAN_RESOURCES)) {
                    resteasyDeploymentData.setScanResources(valueOf(RESTEASY_SCAN_RESOURCES, param.getParamValue()));
                } else if (param.getParamName().equals(ResteasyContextParameters.RESTEASY_UNWRAPPED_EXCEPTIONS)) {
                    resteasyDeploymentData.setUnwrappedExceptionsParameterSet(true);
                }
            }
        }

    }

    protected void scan(final DeploymentUnit du, final ClassLoader classLoader, final ResteasyDeploymentData resteasyDeploymentData)
            throws DeploymentUnitProcessingException, ModuleLoadException {

        final CompositeIndex index = du.getAttachment(Attachments.COMPOSITE_ANNOTATION_INDEX);

        if (!resteasyDeploymentData.shouldScan()) {
            return;
        }

        if (!resteasyDeploymentData.isDispatcherCreated()) {
            final Set<ClassInfo> applicationClasses = index.getAllKnownSubclasses(APPLICATION);
            try {
                for (ClassInfo c : applicationClasses) {
                    if (Modifier.isAbstract(c.flags())) continue;
                    @SuppressWarnings("unchecked")
                    Class<? extends Application> scanned = (Class<? extends Application>) classLoader.loadClass(c.name().toString());
                    resteasyDeploymentData.getScannedApplicationClasses().add(scanned);
                }
            } catch (ClassNotFoundException e) {
                throw JaxrsLogger.JAXRS_LOGGER.cannotLoadApplicationClass(e);
            }
        }

        List<AnnotationInstance> resources = null;
        List<AnnotationInstance> providers = null;
        if (resteasyDeploymentData.isScanResources()) {
            resources = index.getAnnotations(JaxrsAnnotations.PATH.getDotName());
        }
        if (resteasyDeploymentData.isScanProviders()) {
            providers = index.getAnnotations(JaxrsAnnotations.PROVIDER.getDotName());
        }

        if ((resources == null || resources.isEmpty()) && (providers == null || providers.isEmpty()))
            return;
        final Set<ClassInfo> pathInterfaces = new HashSet<ClassInfo>();
        if (resources != null) {
            for (AnnotationInstance e : resources) {
                final ClassInfo info;
                if (e.target() instanceof ClassInfo) {
                    info = (ClassInfo) e.target();
                } else if (e.target() instanceof MethodInfo) {
                    //ignore
                    continue;
                } else {
                    JAXRS_LOGGER.classOrMethodAnnotationNotFound("@Path", e.target());
                    continue;
                }
                if(info.name().toString().startsWith(ORG_APACHE_CXF)) {
                    //do not add CXF classes
                    //see WFLY-9752
                    continue;
                }
                if(info.annotations().containsKey(DECORATOR)) {
                    //we do not add decorators as resources
                    //we can't pick up on programatically added decorators, but that is such an edge case it should not really matter
                    continue;
                }
                if (!Modifier.isInterface(info.flags())) {
                    resteasyDeploymentData.getScannedResourceClasses().add(info.name().toString());
                } else {
                    pathInterfaces.add(info);
                }
            }
        }
        if (providers != null) {
            for (AnnotationInstance e : providers) {
                if (e.target() instanceof ClassInfo) {
                    ClassInfo info = (ClassInfo) e.target();

                    if(info.name().toString().startsWith(ORG_APACHE_CXF)) {
                        //do not add CXF classes
                        //see WFLY-9752
                        continue;
                    }
                    if(info.annotations().containsKey(DECORATOR)) {
                        //we do not add decorators as providers
                        //we can't pick up on programatically added decorators, but that is such an edge case it should not really matter
                        continue;
                    }
                    if (!Modifier.isInterface(info.flags())) {
                        resteasyDeploymentData.getScannedProviderClasses().add(info.name().toString());
                    }
                } else {
                    JAXRS_LOGGER.classAnnotationNotFound("@Provider", e.target());
                }
            }
        }

        // look for all implementations of interfaces annotated @Path
        for (final ClassInfo iface : pathInterfaces) {
            final Set<ClassInfo> implementors = index.getAllKnownImplementors(iface.name());
            for (final ClassInfo implementor : implementors) {
                if(implementor.name().toString().startsWith(ORG_APACHE_CXF)) {
                    //do not add CXF classes
                    //see WFLY-9752
                    continue;
                }

                if(implementor.annotations().containsKey(DECORATOR)) {
                    //we do not add decorators as resources
                    //we can't pick up on programatically added decorators, but that is such an edge case it should not really matter
                    continue;
                }
                resteasyDeploymentData.getScannedResourceClasses().add(implementor.name().toString());
            }
        }
    }

    protected Class<?> checkDeclaredApplicationClassAsServlet(JBossWebMetaData webData,
                                                              ClassLoader classLoader) throws DeploymentUnitProcessingException {
        if (webData.getServlets() == null)
            return null;

        for (ServletMetaData servlet : webData.getServlets()) {
            String servletClass = servlet.getServletClass();
            if (servletClass == null)
                continue;
            Class<?> clazz = null;
            try {
                clazz = classLoader.loadClass(servletClass);
            } catch (ClassNotFoundException e) {
                throw new DeploymentUnitProcessingException(e);
            }
            if (Application.class.isAssignableFrom(clazz)) {
                servlet.setServletClass(HttpServlet30Dispatcher.class.getName());
                servlet.setAsyncSupported(true);
                ParamValueMetaData param = new ParamValueMetaData();
                param.setParamName("javax.ws.rs.Application");
                param.setParamValue(servletClass);
                List<ParamValueMetaData> params = servlet.getInitParam();
                if (params == null) {
                    params = new ArrayList<ParamValueMetaData>();
                    servlet.setInitParam(params);
                }
                params.add(param);

                return clazz;
            }
        }
        return null;
    }

    private boolean valueOf(String paramName, String value) throws DeploymentUnitProcessingException {
        if (value == null) {
            throw JaxrsLogger.JAXRS_LOGGER.invalidParamValue(paramName, value);
        }
        if (value.toLowerCase(Locale.ENGLISH).equals("true")) {
            return true;
        } else if (value.toLowerCase(Locale.ENGLISH).equals("false")) {
            return false;
        } else {
            throw JaxrsLogger.JAXRS_LOGGER.invalidParamValue(paramName, value);
        }
    }

}


/*
 * 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.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;

import org.jboss.invocation.Interceptor;
import org.jboss.invocation.InterceptorContext;
import org.wildfly.security.auth.server.SecurityDomain;
import org.wildfly.security.manager.WildFlySecurityManager;

/**
 * An invocation handler for a component proxy.
 *
 * @author David M. Lloyd
 */
public final class ProxyInvocationHandler implements InvocationHandler {

    private final Map<Method, Interceptor> interceptors;
    private final ComponentView componentView;
    private final ComponentClientInstance instance;

    /**
     * Construct a new instance.
     *
     * @param interceptors the interceptors map to use
     * @param instance The view instane data
     * @param componentView The component view
     */
    public ProxyInvocationHandler(final Map<Method, Interceptor> interceptors, ComponentClientInstance instance, ComponentView componentView) {
        this.interceptors = interceptors;
        this.instance = instance;
        this.componentView = componentView;
    }

    /** {@inheritDoc} */
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        final Interceptor interceptor = interceptors.get(method);
        if (interceptor == null) {
            throw new NoSuchMethodError(method.toString());
        }
        final InterceptorContext context = new InterceptorContext();
        // special location for original proxy
        context.putPrivateData(Object.class, proxy);
        context.putPrivateData(Component.class, componentView.getComponent());
        context.putPrivateData(ComponentView.class, componentView);
        context.putPrivateData(SecurityDomain.class, WildFlySecurityManager.isChecking() ?
                AccessController.doPrivileged((PrivilegedAction<SecurityDomain>) SecurityDomain::getCurrent) :
                SecurityDomain.getCurrent());
        instance.prepareInterceptorContext(context);
        context.setParameters(args);
        context.setMethod(method);
        // setup the public context data
        context.setContextData(new HashMap<String, Object>());
        context.setBlockingCaller(true);
        return interceptor.processInvocation(context);
    }
}


/*
 * Copyright (c) 1999, 2004, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package org.wildfly.iiop.openjdk.naming.jndi;

import java.rmi.Remote;
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.spi.StateFactory;

import org.wildfly.iiop.openjdk.logging.IIOPLogger;

/**
 * StateFactory that turns java.rmi.Remote objects to org.omg.CORBA.Object.
 *
 * @author Rosanna Lee
 */

public class RemoteToCorba implements StateFactory {
    public RemoteToCorba() {
    }

    /**
     * Returns the CORBA object for a Remote object.
     * If input is not a Remote object, or if Remote object uses JRMP, return null.
     * If the RMI-IIOP library is not available, throw ConfigurationException.
     *
     * @param orig The object to turn into a CORBA object. If not Remote,
     *             or if is a JRMP stub or impl, return null.
     * @param name Ignored
     * @param ctx  The non-null CNCtx whose ORB to use.
     * @param env  Ignored
     * @return The CORBA object for <tt>orig</tt> or null.
     * @throws javax.naming.ConfigurationException If the CORBA object cannot be obtained
     *                                due to configuration problems, for instance, if RMI-IIOP not available.
     * @throws NamingException        If some other problem prevented a CORBA
     *                                object from being obtained from the Remote object.
     */
    public Object getStateToBind(Object orig, Name name, Context ctx,
                                 Hashtable<?, ?> env) throws NamingException {
        if (orig instanceof org.omg.CORBA.Object) {
            // Already a CORBA object, just use it
            return null;
        }

        if (orig instanceof Remote) {
            // Turn remote object into org.omg.CORBA.Object
            try {
                // Returns null if JRMP; let next factory try
                // CNCtx will eventually throw IllegalArgumentException if
                // no CORBA object gotten
                return
                        CorbaUtils.remoteToCorba((Remote) orig, ((CNCtx) ctx)._orb);
            } catch (ClassNotFoundException e) {
                // RMI-IIOP library not available
                throw IIOPLogger.ROOT_LOGGER.unavailableRMIPackages();
            }
        }
        return null; // pass and let next state factory try
    }
}


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

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Value meta data.
 *
 * @author Ales Justin
 */
public abstract class ValueConfig extends AbstractConfigVisitorNode implements Serializable {
    private static final long serialVersionUID = 1L;

    private String type;
    private int index = -1;

    /**
     * Get value.
     *
     * @param type the type
     * @return value
     */
    public Object getValue(Type type) {
        if (type == null || (type instanceof Class)) {
            return getClassValue((Class) type);
        } else if (type instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) type;
            return getPtValue(pt);
        } else {
            throw PojoLogger.ROOT_LOGGER.unknownType(type);
        }
    }

    /**
     * Get value.
     *
     * @param type the parameterized type
     * @return value
     */
    protected Object getPtValue(ParameterizedType type) {
        return getValue(type.getRawType());
    }

    /**
     * Get value, use type to narrow down exact value.
     *
     * @param type the injection point type
     * @return value
     */
    protected abstract Object getClassValue(Class<?> type);

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }
}


/*
 * 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.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jboss.as.ee.logging.EeLogger;
import org.jboss.as.ee.component.interceptors.InterceptorClassDescription;
import org.jboss.as.naming.ManagedReferenceFactory;
import org.jboss.as.naming.deployment.ContextNames;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.reflect.ClassReflectionIndex;
import org.jboss.invocation.proxy.MethodIdentifier;
import org.jboss.modules.ModuleLoader;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.value.InjectedValue;

/**
 * A description of a generic Java EE component.  The description is pre-classloading so it references everything by name.
 *
 * @author David M. Lloyd
 * @author Richard Opalka
 */
public class ComponentDescription implements ResourceInjectionTarget {

    private static final DefaultComponentConfigurator DEFAULT_COMPONENT_CONFIGURATOR = new DefaultComponentConfigurator();
    private static final DefaultInterceptorConfigurator DEFAULT_INTERCEPTOR_CONFIGURATOR = new DefaultInterceptorConfigurator();
    private static final DefaultComponentViewConfigurator DEFAULT_COMPONENT_VIEW_CONFIGURATOR = new DefaultComponentViewConfigurator();

    private final ServiceName serviceName;
    private ServiceName contextServiceName;
    private final String componentName;
    private final String componentClassName;
    private final EEModuleDescription moduleDescription;
    private final Set<ViewDescription> views = new HashSet<ViewDescription>();

    /**
     * Interceptors methods that have been defined by the deployment descriptor
     */
    private final Map<String, InterceptorClassDescription> interceptorClassOverrides = new HashMap<String, InterceptorClassDescription>();

    private List<InterceptorDescription> classInterceptors = new ArrayList<InterceptorDescription>();
    private List<InterceptorDescription> defaultInterceptors = new ArrayList<InterceptorDescription>();
    private final Map<MethodIdentifier, List<InterceptorDescription>> methodInterceptors = new HashMap<MethodIdentifier, List<InterceptorDescription>>();
    private final Set<MethodIdentifier> methodExcludeDefaultInterceptors = new HashSet<MethodIdentifier>();
    private final Set<MethodIdentifier> methodExcludeClassInterceptors = new HashSet<MethodIdentifier>();
    private Set<InterceptorDescription> allInterceptors;
    private boolean excludeDefaultInterceptors = false;
    private boolean ignoreLifecycleInterceptors = false;

    private final Set<ServiceName> dependencies = new HashSet<ServiceName>();

    private ComponentNamingMode namingMode = ComponentNamingMode.USE_MODULE;

    private DeploymentDescriptorEnvironment deploymentDescriptorEnvironment;

    // Bindings
    private final List<BindingConfiguration> bindingConfigurations = new ArrayList<BindingConfiguration>();
    //injections that have been set in the components deployment descriptor
    private final Map<String, Map<InjectionTarget, ResourceInjectionConfiguration>> resourceInjections = new HashMap<String, Map<InjectionTarget, ResourceInjectionConfiguration>>();

    private final Deque<ComponentConfigurator> configurators = new ArrayDeque<ComponentConfigurator>();

    /**
     * If this component is deployed in a bean deployment archive this stores the id of the BDA
     */
    private String beanDeploymentArchiveId;

    /**
     * Construct a new instance.
     *
     * @param componentName             the component name
     * @param componentClassName        the component instance class name
     * @param moduleDescription         the EE module description
     * @param deploymentUnitServiceName the service name of the DU containing this component
     */
    public ComponentDescription(final String componentName, final String componentClassName, final EEModuleDescription moduleDescription, final ServiceName deploymentUnitServiceName) {
        this.moduleDescription = moduleDescription;
        if (componentName == null) {
            throw EeLogger.ROOT_LOGGER.nullName("component");
        }
        if (componentClassName == null) {
            throw EeLogger.ROOT_LOGGER.nullVar("componentClassName", "component", componentName);
        }
        if (moduleDescription == null) {
            throw EeLogger.ROOT_LOGGER.nullVar("moduleDescription", "component", componentName);
        }
        if (deploymentUnitServiceName == null) {
            throw EeLogger.ROOT_LOGGER.nullVar("deploymentUnitServiceName", "component", componentName);
        }
        serviceName = BasicComponent.serviceNameOf(deploymentUnitServiceName, componentName);
        this.componentName = componentName;
        this.componentClassName = componentClassName;
        configurators.addLast(DEFAULT_COMPONENT_CONFIGURATOR);
        configurators.addLast(DEFAULT_INTERCEPTOR_CONFIGURATOR);
        configurators.addLast(DEFAULT_COMPONENT_VIEW_CONFIGURATOR);
    }

    public ComponentConfiguration createConfiguration(final ClassReflectionIndex classIndex, final ClassLoader moduleClassLoader, final ModuleLoader moduleLoader) {
        return new ComponentConfiguration(this, classIndex, moduleClassLoader, moduleLoader);
    }

    /**
     * Get the component name.
     *
     * @return the component name
     */
    public String getComponentName() {
        return componentName;
    }

    /**
     * Set context service name.
     *
     * @param contextServiceName
     */
    public void setContextServiceName(final ServiceName contextServiceName) {
        this.contextServiceName = contextServiceName;
    }

    /**
     * Get the context service name.
     *
     * @return the context service name
     */
    public ServiceName getContextServiceName() {
        if (contextServiceName != null) return contextServiceName;
        if (getNamingMode() == ComponentNamingMode.CREATE) {
            return ContextNames.contextServiceNameOfComponent(getApplicationName(), getModuleName(), getComponentName());
        } else if (getNamingMode() == ComponentNamingMode.USE_MODULE) {
            return ContextNames.contextServiceNameOfModule(getApplicationName(), getModuleName());
        } else {
            throw new IllegalStateException();
        }
    }

    /**
     * Get the base service name for this component.
     *
     * @return the base service name
     */
    public ServiceName getServiceName() {
        return serviceName;
    }

    /**
     * Get the service name of this components start service
     *
     * @return The start service name
     */
    public ServiceName getStartServiceName() {
        return serviceName.append("START");
    }

    /**
     * Get the service name of this components create service
     *
     * @return The create service name
     */
    public ServiceName getCreateServiceName() {
        return serviceName.append("CREATE");
    }

    /**
     * Get the component instance class name.
     *
     * @return the component class name
     */
    public String getComponentClassName() {
        return componentClassName;
    }

    /**
     * Get the component's module name.
     *
     * @return the module name
     */
    public String getModuleName() {
        return moduleDescription.getModuleName();
    }

    /**
     * Get the component's module's application name.
     *
     * @return the application name
     */
    public String getApplicationName() {
        return moduleDescription.getApplicationName();
    }

    /**
     * Get the list of interceptor classes applied directly to class. These interceptors will have lifecycle methods invoked
     *
     * @return the interceptor classes
     */
    public List<InterceptorDescription> getClassInterceptors() {
        return classInterceptors;
    }

    /**
     * Override the class interceptors with a new set of interceptors
     *
     * @param classInterceptors
     */
    public void setClassInterceptors(List<InterceptorDescription> classInterceptors) {
        this.classInterceptors = classInterceptors;
        this.allInterceptors = null;
    }

    /**
     * @return the components default interceptors
     */
    public List<InterceptorDescription> getDefaultInterceptors() {
        return defaultInterceptors;
    }

    public void setDefaultInterceptors(final List<InterceptorDescription> defaultInterceptors) {
        allInterceptors = null;
        this.defaultInterceptors = defaultInterceptors;
    }

    /**
     * Returns a combined map of class and method level interceptors
     *
     * @return all interceptors on the class
     */
    public Set<InterceptorDescription> getAllInterceptors() {
        if (allInterceptors == null) {
            allInterceptors = new HashSet<InterceptorDescription>();
            allInterceptors.addAll(classInterceptors);
            if (!excludeDefaultInterceptors) {
                allInterceptors.addAll(defaultInterceptors);
            }
            for (List<InterceptorDescription> interceptors : methodInterceptors.values()) {
                allInterceptors.addAll(interceptors);
            }
        }
        return allInterceptors;
    }

    /**
     * @return true if the ExcludeDefaultInterceptors annotation was applied to the class
     */
    public boolean isExcludeDefaultInterceptors() {
        return excludeDefaultInterceptors;
    }

    public void setExcludeDefaultInterceptors(boolean excludeDefaultInterceptors) {
        allInterceptors = null;
        this.excludeDefaultInterceptors = excludeDefaultInterceptors;
    }

    public boolean isIgnoreLifecycleInterceptors() {
        return ignoreLifecycleInterceptors;
    }

    /**
     * If this component should ignore lifecycle interceptors. This should generally only be set when they are going
     * to be handled by an external framework such as Weld.
     *
     */
    public void setIgnoreLifecycleInterceptors(boolean ignoreLifecycleInterceptors) {
        this.ignoreLifecycleInterceptors = ignoreLifecycleInterceptors;
    }

    /**
     * @param method The method that has been annotated @ExcludeDefaultInterceptors
     */
    public void excludeDefaultInterceptors(MethodIdentifier method) {
        methodExcludeDefaultInterceptors.add(method);
    }

    public boolean isExcludeDefaultInterceptors(MethodIdentifier method) {
        return methodExcludeDefaultInterceptors.contains(method);
    }

    /**
     * @param method The method that has been annotated @ExcludeClassInterceptors
     */
    public void excludeClassInterceptors(MethodIdentifier method) {
        methodExcludeClassInterceptors.add(method);
    }

    public boolean isExcludeClassInterceptors(MethodIdentifier method) {
        return methodExcludeClassInterceptors.contains(method);
    }

    /**
     * Add a class level interceptor.
     *
     * @param description the interceptor class description
     */
    public void addClassInterceptor(InterceptorDescription description) {
        classInterceptors.add(description);
        this.allInterceptors = null;
    }

    /**
     * Returns the {@link InterceptorDescription} for the passed interceptorClassName, if such a class
     * interceptor exists for this component description. Else returns null.
     *
     * @param interceptorClassName The fully qualified interceptor class name
     * @return
     */
    public InterceptorDescription getClassInterceptor(String interceptorClassName) {
        for (InterceptorDescription interceptor : classInterceptors) {
            if (interceptor.getInterceptorClassName().equals(interceptorClassName)) {
                return interceptor;
            }
        }
        return null;
    }

    /**
     * Get the method interceptor configurations.  The key is the method identifier, the value is
     * the set of class names of interceptors to configure on that method.
     *
     * @return the method interceptor configurations
     */
    public Map<MethodIdentifier, List<InterceptorDescription>> getMethodInterceptors() {
        return methodInterceptors;
    }

    /**
     * Add a method interceptor class name.
     *
     * @param method      the method
     * @param description the interceptor descriptor
     */
    public void addMethodInterceptor(MethodIdentifier method, InterceptorDescription description) {
        //we do not add method level interceptors to the set of interceptor classes,
        //as their around invoke annotations
        List<InterceptorDescription> interceptors = methodInterceptors.get(method);
        if (interceptors == null) {
            methodInterceptors.put(method, interceptors = new ArrayList<InterceptorDescription>());
        }
        final String name = description.getInterceptorClassName();
        // add the interceptor class to the EEModuleDescription
        interceptors.add(description);
        this.allInterceptors = null;
    }

    /**
     * Sets the method level interceptors for a method, and marks it as exclude class and default level interceptors.
     *

     * This is used to set the final interceptor order after it has been modifier by the deployment descriptor
     *
     * @param identifier              the method identifier
     * @param interceptorDescriptions The interceptors
     */
    public void setMethodInterceptors(MethodIdentifier identifier, List<InterceptorDescription> interceptorDescriptions) {
        methodInterceptors.put(identifier, interceptorDescriptions);
        methodExcludeClassInterceptors.add(identifier);
        methodExcludeDefaultInterceptors.add(identifier);
    }

    /**
     * Adds an interceptor class method override, merging it with existing overrides (if any)
     *
     * @param className The class name
     * @param override  The method override
     */
    public void addInterceptorMethodOverride(final String className, final InterceptorClassDescription override) {
        interceptorClassOverrides.put(className, InterceptorClassDescription.merge(interceptorClassOverrides.get(className), override));
    }

    /**
     * Get the naming mode of this component.
     *
     * @return the naming mode
     */
    public ComponentNamingMode getNamingMode() {
        return namingMode;
    }

    /**
     * Set the naming mode of this component.  May not be {@code null}.
     *
     * @param namingMode the naming mode
     */
    public void setNamingMode(final ComponentNamingMode namingMode) {
        if (namingMode == null) {
            throw EeLogger.ROOT_LOGGER.nullVar("namingMode", "component", componentName);
        }
        this.namingMode = namingMode;
    }

    /**
     * @return The module description for the component
     */
    public EEModuleDescription getModuleDescription() {
        return moduleDescription;
    }

    /**
     * Add a dependency to this component.  If the same dependency is added multiple times, only the first will
     * take effect.
     *
     * @param serviceName the service name of the dependency
     */
    public void addDependency(ServiceName serviceName) {
        if (serviceName == null) {
            throw EeLogger.ROOT_LOGGER.nullVar("serviceName", "component", componentName);
        }
        dependencies.add(serviceName);
    }

    /**
     * Get the dependency set.
     *
     * @return the dependency set
     */
    public Set<ServiceName> getDependencies() {
        return dependencies;
    }

    public DeploymentDescriptorEnvironment getDeploymentDescriptorEnvironment() {
        return deploymentDescriptorEnvironment;
    }

    public void setDeploymentDescriptorEnvironment(DeploymentDescriptorEnvironment deploymentDescriptorEnvironment) {
        this.deploymentDescriptorEnvironment = deploymentDescriptorEnvironment;
    }

    /**
     * Get the binding configurations for this component.  This list contains bindings which are specific to the
     * component.
     *
     * @return the binding configurations
     */
    public List<BindingConfiguration> getBindingConfigurations() {
        return bindingConfigurations;
    }

    /**
     * Get the list of views which apply to this component.
     *
     * @return the list of views
     */
    public Set<ViewDescription> getViews() {
        return views;
    }

    /**
     *
     *
     * @return true If this component type is eligible for a timer service
     */
    public boolean isTimerServiceApplicable() {
        return false;
    }

    /**
     *
     * @return true if this component has timeout methods and is eligible for a 'real' timer service
     */
    public boolean isTimerServiceRequired() {
        return isTimerServiceApplicable() && !getTimerMethods().isEmpty();
    }

    /**
     *
     * @return The set of all method identifiers for the timeout methods
     */
    public Set<MethodIdentifier> getTimerMethods() {
        return Collections.emptySet();
    }

    public boolean isPassivationApplicable() {
        return false;
    }

    /**
     * Get the configurators for this component.
     *
     * @return the configurators
     */
    public Deque<ComponentConfigurator> getConfigurators() {
        return configurators;
    }

    public boolean isIntercepted() {
        return true;
    }

    /**
     * @return true if errors should be ignored when installing this component
     */
    public boolean isOptional() {
        return false;
    }

    static class InjectedConfigurator implements DependencyConfigurator<ComponentStartService> {

        private final ResourceInjectionConfiguration injectionConfiguration;
        private final ComponentConfiguration configuration;
        private final DeploymentPhaseContext context;
        private final InjectedValue<ManagedReferenceFactory> managedReferenceFactoryValue;

        InjectedConfigurator(final ResourceInjectionConfiguration injectionConfiguration, final ComponentConfiguration configuration, final DeploymentPhaseContext context, final InjectedValue<ManagedReferenceFactory> managedReferenceFactoryValue) {
            this.injectionConfiguration = injectionConfiguration;
            this.configuration = configuration;
            this.context = context;
            this.managedReferenceFactoryValue = managedReferenceFactoryValue;
        }

        public void configureDependency(final ServiceBuilder<?> serviceBuilder, ComponentStartService service) throws DeploymentUnitProcessingException {
            InjectionSource.ResolutionContext resolutionContext = new InjectionSource.ResolutionContext(
                    configuration.getComponentDescription().getNamingMode() == ComponentNamingMode.USE_MODULE,
                    configuration.getComponentName(),
                    configuration.getModuleName(),
                    configuration.getApplicationName()
            );
            injectionConfiguration.getSource().getResourceValue(resolutionContext, serviceBuilder, context, managedReferenceFactoryValue);
        }

    }

    public String getBeanDeploymentArchiveId() {
        return beanDeploymentArchiveId;
    }

    public void setBeanDeploymentArchiveId(final String beanDeploymentArchiveId) {
        this.beanDeploymentArchiveId = beanDeploymentArchiveId;
    }

    public void addResourceInjection(final ResourceInjectionConfiguration injection) {
        String className = injection.getTarget().getClassName();
        Map<InjectionTarget, ResourceInjectionConfiguration> map = resourceInjections.get(className);
        if (map == null) {
            resourceInjections.put(className, map = new HashMap<InjectionTarget, ResourceInjectionConfiguration>());
        }
        map.put(injection.getTarget(), injection);
    }

    public Map<InjectionTarget, ResourceInjectionConfiguration> getResourceInjections(final String className) {
        Map<InjectionTarget, ResourceInjectionConfiguration> injections = resourceInjections.get(className);
        if (injections == null) {
            return Collections.emptyMap();
        } else {
            return Collections.unmodifiableMap(injections);
        }
    }

    /**
     * If this method returns true then Weld will directly create the component instance,
     * which will apply interceptors and decorators via sub classing.
     *
     * For most components this is not necessary.
     *
     * Also not that even though EJB's are intercepted, their interceptor is done through
     * a different method that integrates with the existing EJB interceptor chain
     *
     */
    public boolean isCDIInterceptorEnabled() {
        return false;
    }

    public static InterceptorClassDescription mergeInterceptorConfig(final Class<?> clazz, final EEModuleClassDescription classDescription, final ComponentDescription description, final boolean metadataComplete) {
        final InterceptorClassDescription interceptorConfig;
        if (classDescription != null && !metadataComplete) {
            interceptorConfig = InterceptorClassDescription.merge(classDescription.getInterceptorClassDescription(), description.interceptorClassOverrides.get(clazz.getName()));
        } else {
            interceptorConfig = InterceptorClassDescription.merge(null, description.interceptorClassOverrides.get(clazz.getName()));
        }
        return interceptorConfig;
    }

}


/*
 * 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.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jboss.as.ee.logging.EeLogger;
import org.jboss.as.ee.component.interceptors.OrderedItemContainer;
import org.jboss.as.ee.concurrent.ConcurrentContext;
import org.jboss.as.naming.context.NamespaceContextSelector;
import org.jboss.as.server.deployment.reflect.ClassReflectionIndex;
import org.jboss.invocation.InterceptorFactory;
import org.jboss.modules.ModuleLoader;
import org.jboss.msc.service.Service;

/**
 * The construction parameter set passed in to an abstract component.
 *

 *
Interceptors

 * The interceptor factories provided herein are assembled from the component's EE module class as well as the EE
 * module classes of the declared interceptor classes for this component by way of a configurator.
 *
 * @author David M. Lloyd
 */
public class ComponentConfiguration {

    private final ComponentDescription componentDescription;

    // Core component config
    private final ClassReflectionIndex classIndex;
    private final ModuleLoader moduleLoader;
    private final ClassLoader moduleClassLoader;

    private final ConcurrentContext concurrentContext;

    private ComponentCreateServiceFactory componentCreateServiceFactory = ComponentCreateServiceFactory.BASIC;

    // Interceptor config
    private final OrderedItemContainer<List<InterceptorFactory>> aroundConstructInterceptors = new OrderedItemContainer<>();
    private final OrderedItemContainer<List<InterceptorFactory>> postConstructInterceptors = new OrderedItemContainer<>();
    private final OrderedItemContainer<List<InterceptorFactory>> preDestroyInterceptors = new OrderedItemContainer<>();
    private final OrderedItemContainer<List<InterceptorFactory>> prePassivateInterceptors = new OrderedItemContainer<>();
    private final OrderedItemContainer<List<InterceptorFactory>> postActivateInterceptors = new OrderedItemContainer<>();
    private final Map<Method, OrderedItemContainer<List<InterceptorFactory>>> componentInterceptors = new IdentityHashMap<>();

    //TODO: move this into an EJB specific configuration
    private final Map<Method, OrderedItemContainer<InterceptorFactory>> timeoutInterceptors = new IdentityHashMap<>();

    // Component instance management
    private ComponentFactory instanceFactory;

    private final List<DependencyConfigurator<? extends Service<Component>>> createDependencies = new ArrayList<DependencyConfigurator<? extends Service<Component>>>();
    private final List<DependencyConfigurator<ComponentStartService>> startDependencies = new ArrayList<DependencyConfigurator<ComponentStartService>>();

    // Views
    private final List<ViewConfiguration> views = new ArrayList<ViewConfiguration>();

    private InterceptorFactory namespaceContextInterceptorFactory;

    private NamespaceContextSelector namespaceContextSelector;

    private final Set<Object> interceptorContextKeys = new HashSet<Object>();

    /**
     * Contains a set of all lifecycle methods defined by the bean
     */
    private final Set<Method> lifecycleMethods = new HashSet<>();

    public ComponentConfiguration(final ComponentDescription componentDescription, final ClassReflectionIndex classIndex, final ClassLoader moduleClassLoader, final ModuleLoader moduleLoader) {
        this.componentDescription = componentDescription;
        this.classIndex = classIndex;
        this.moduleClassLoader = moduleClassLoader;
        this.moduleLoader = moduleLoader;
        this.concurrentContext = new ConcurrentContext();
    }

    /**
     * Get the component description.
     *
     * @return the component description
     */
    public ComponentDescription getComponentDescription() {
        return componentDescription;
    }

    /**
     * Get the component class.
     *
     * @return the component class
     */
    public Class<?> getComponentClass() {
        return classIndex.getIndexedClass();
    }

    /**
     * Get the component name.
     *
     * @return the component name
     */
    public String getComponentName() {
        return componentDescription.getComponentName();
    }

    /**
     * Get the set of currently known component methods.  This is an identity set.
     *
     * @return the set of methods
     */
    public Set<Method> getDefinedComponentMethods() {
        return classIndex.getClassMethods();
    }

    /**
     * Gets the interceptor list for a given method. This should not be called until
     * all interceptors have been added.
     *
     * @param method the component method
     * @return the deque
     */
    public List<InterceptorFactory> getComponentInterceptors(Method method) {
        Map<Method, OrderedItemContainer<List<InterceptorFactory>>> map = componentInterceptors;
        OrderedItemContainer<List<InterceptorFactory>> interceptors = map.get(method);
        if (interceptors == null) {
            return Collections.emptyList();
        }
        List<List<InterceptorFactory>> sortedItems = interceptors.getSortedItems();
        List<InterceptorFactory> ret = new ArrayList<>();
        for(List<InterceptorFactory> item : sortedItems) {
            ret.addAll(item);
        }
        return ret;
    }

    /**
     * Gets the around timeout interceptor list for a given method. This should not be called until
     * all interceptors have been added.
     *
     * @param method the component method
     * @return the deque
     */
    public List<InterceptorFactory> getAroundTimeoutInterceptors(Method method) {
        Map<Method, OrderedItemContainer<InterceptorFactory>> map = timeoutInterceptors;
        OrderedItemContainer<InterceptorFactory> interceptors = map.get(method);
        if (interceptors == null) {
            return Collections.emptyList();
        }
        return interceptors.getSortedItems();
    }

    /**
     * Adds an interceptor factory to every method on the component.
     *
     * @param factory    The interceptor factory to add
     * @param priority   The interceptors relative order
     * @param publicOnly If true then then interceptor is only added to public methods
     */
    public void addComponentInterceptor(InterceptorFactory factory, int priority, boolean publicOnly) {
        addComponentInterceptors(Collections.singletonList(factory), priority, publicOnly);
    }
    /**
     * Adds an interceptor factory to every method on the component.
     *
     * @param factory    The interceptor factory to add
     * @param priority   The interceptors relative order
     * @param publicOnly If true then then interceptor is only added to public methods
     */
    public void addComponentInterceptors(List<InterceptorFactory> factory, int priority, boolean publicOnly) {
        for (Method method : (Iterable<Method>)classIndex.getClassMethods()) {
            if (publicOnly && !Modifier.isPublic(method.getModifiers())) {
                continue;
            }
            OrderedItemContainer<List<InterceptorFactory>> interceptors = componentInterceptors.get(method);
            if (interceptors == null) {
                componentInterceptors.put(method, interceptors = new OrderedItemContainer<List<InterceptorFactory>>());
            }
            interceptors.add(factory, priority);
        }
    }

    /**
     * Adds an interceptor factory to a given method. The method parameter *must* be retrived from either the
     * {@link org.jboss.as.server.deployment.reflect.DeploymentReflectionIndex} or from {@link #getDefinedComponentMethods()},
     * as the methods are stored in an identity hash map
     *
     * @param method   The method to add the interceptor to
     * @param factory  The interceptor factory to add
     * @param priority The interceptors relative order
     */
    public void addComponentInterceptor(Method method, InterceptorFactory factory, int priority) {
        addComponentInterceptors(method, Collections.singletonList(factory), priority);
    }

    /**
     * Adds an interceptor factory to a given method. The method parameter *must* be retrived from either the
     * {@link org.jboss.as.server.deployment.reflect.DeploymentReflectionIndex} or from {@link #getDefinedComponentMethods()},
     * as the methods are stored in an identity hash map
     *
     * @param method   The method to add the interceptor to
     * @param factory  The interceptor factory to add
     * @param priority The interceptors relative order
     */
    public void addComponentInterceptors(Method method, List<InterceptorFactory> factory, int priority) {
        OrderedItemContainer<List<InterceptorFactory>> interceptors = componentInterceptors.get(method);
        if (interceptors == null) {
            componentInterceptors.put(method, interceptors = new OrderedItemContainer<List<InterceptorFactory>>());
        }
        interceptors.add(factory, priority);
    }
    /**
     * Adds a timeout interceptor factory to every method on the component.
     *
     * @param factory  The interceptor factory to add
     * @param priority The interceptors relative order
     */
    public void addTimeoutViewInterceptor(InterceptorFactory factory, int priority) {
        for (Method method : (Iterable<Method>)classIndex.getClassMethods()) {
            OrderedItemContainer<InterceptorFactory> interceptors = timeoutInterceptors.get(method);
            if (interceptors == null) {
                timeoutInterceptors.put(method, interceptors = new OrderedItemContainer<InterceptorFactory>());
            }
            interceptors.add(factory, priority);
        }
    }

    /**
     * Adds a timeout interceptor factory to every method on the component.
     *
     * @param method   The method to add it to
     * @param factory  The interceptor factory to add
     * @param priority The interceptors relative order
     */
    public void addTimeoutViewInterceptor(final Method method, InterceptorFactory factory, int priority) {
        OrderedItemContainer<InterceptorFactory> interceptors = timeoutInterceptors.get(method);
        if (interceptors == null) {
            timeoutInterceptors.put(method, interceptors = new OrderedItemContainer<InterceptorFactory>());
        }
        interceptors.add(factory, priority);
    }

    /**
     * Get the create dependencies list.
     *
     * @return the create dependencies list
     */
    public List<DependencyConfigurator<? extends Service<Component>>> getCreateDependencies() {
        return createDependencies;
    }

    /**
     * Get the start dependencies list.
     *
     * @return the start dependencies list
     */
    public List<DependencyConfigurator<ComponentStartService>> getStartDependencies() {
        return startDependencies;
    }

    /**
     * Get the list of views for this component.
     *
     * @return the list of views
     */
    public List<ViewConfiguration> getViews() {
        return views;
    }

    /**
     * Get the around-construct interceptors.
     *

     * This method should only be called after all interceptors have been added
     *
     * @return the sorted interceptors
     */
    public List<InterceptorFactory> getAroundConstructInterceptors() {
        List<List<InterceptorFactory>> sortedItems = aroundConstructInterceptors.getSortedItems();
        List<InterceptorFactory> interceptorFactories = new ArrayList<>();
        for(List<InterceptorFactory> i : sortedItems) {
            interceptorFactories.addAll(i);
        }
        return interceptorFactories;
    }

    /**
     * Adds an around-construct interceptor
     *
     * @param factories The interceptors to add
     * @param priority           The priority
     */
    public void addAroundConstructInterceptors(List<InterceptorFactory> factories, int priority) {
        aroundConstructInterceptors.add(factories, priority);
    }
    /**
     * Adds an around-construct interceptor
     *
     * @param interceptorFactory The interceptor to add
     * @param priority           The priority
     */
    public void addAroundConstructInterceptor(InterceptorFactory interceptorFactory, int priority) {
        aroundConstructInterceptors.add(Collections.singletonList(interceptorFactory), priority);
    }

    /**
     * Get the post-construct interceptors.
     *

     * This method should only be called after all interceptors have been added
     *
     * @return the sorted interceptors
     */
    public List<InterceptorFactory> getPostConstructInterceptors() {
        List<List<InterceptorFactory>> sortedItems = postConstructInterceptors.getSortedItems();
        List<InterceptorFactory> interceptorFactories = new ArrayList<>();
        for(List<InterceptorFactory> i : sortedItems) {
            interceptorFactories.addAll(i);
        }
        return interceptorFactories;
    }

    /**
     * Adds a post construct interceptor
     *
     * @param interceptorFactory The interceptor to add
     * @param priority           The priority
     */
    public void addPostConstructInterceptors(List<InterceptorFactory> interceptorFactory, int priority) {
        postConstructInterceptors.add(interceptorFactory, priority);
    }

    /**
     * Adds a post construct interceptor
     *
     * @param interceptorFactory The interceptor to add
     * @param priority           The priority
     */
    public void addPostConstructInterceptor(InterceptorFactory interceptorFactory, int priority) {
        postConstructInterceptors.add(Collections.singletonList(interceptorFactory), priority);
    }
    /**
     * Get the pre-destroy interceptors.
     *

     * This method should only be called after all interceptors have been added
     *
     * @return the sorted interceptor
     */
    public List<InterceptorFactory> getPreDestroyInterceptors() {
        List<List<InterceptorFactory>> sortedItems = preDestroyInterceptors.getSortedItems();
        List<InterceptorFactory> interceptorFactories = new ArrayList<>();
        for(List<InterceptorFactory> i : sortedItems) {
            interceptorFactories.addAll(i);
        }
        return interceptorFactories;
    }

    /**
     * Adds a pre destroy interceptor
     *
     * @param factories The interceptor factory to add
     * @param priority           The factories priority
     */
    public void addPreDestroyInterceptors(List<InterceptorFactory> factories, int priority) {
        preDestroyInterceptors.add(factories, priority);
    }
    /**
     * Adds a pre destroy interceptor
     *
     * @param interceptorFactory The interceptor factory to add
     * @param priority           The factories priority
     */
    public void addPreDestroyInterceptor(InterceptorFactory interceptorFactory, int priority) {
        preDestroyInterceptors.add(Collections.singletonList(interceptorFactory), priority);
    }

    /**
     * Get the pre-passivate interceptors.
     *

     * This method should only be called after all interceptors have been added
     *
     * @return the sorted interceptors
     */
    public List<InterceptorFactory> getPrePassivateInterceptors() {
        List<List<InterceptorFactory>> sortedItems = prePassivateInterceptors.getSortedItems();
        List<InterceptorFactory> interceptorFactories = new ArrayList<>();
        for(List<InterceptorFactory> i : sortedItems) {
            interceptorFactories.addAll(i);
        }
        return interceptorFactories;
    }

    /**
     * Adds a pre passivate interceptor
     *
     * @param factories The interceptor to add
     * @param priority           The priority
     */
    public void addPrePassivateInterceptors(List<InterceptorFactory> factories, int priority) {
        prePassivateInterceptors.add(factories, priority);
    }

    /**
     * Adds a pre passivate interceptor
     *
     * @param interceptorFactory The interceptor to add
     * @param priority           The priority
     */
    public void addPrePassivateInterceptor(InterceptorFactory interceptorFactory, int priority) {
        prePassivateInterceptors.add(Collections.singletonList(interceptorFactory), priority);
    }

    /**
     * Get the post-activate interceptors.
     *

     * This method should only be called after all interceptors have been added
     *
     * @return the sorted interceptors
     */
    public List<InterceptorFactory> getPostActivateInterceptors() {
        List<List<InterceptorFactory>> sortedItems = postActivateInterceptors.getSortedItems();
        List<InterceptorFactory> interceptorFactories = new ArrayList<>();
        for(List<InterceptorFactory> i : sortedItems) {
            interceptorFactories.addAll(i);
        }
        return interceptorFactories;
    }

    /**
     * Adds a post activate interceptor
     *
     * @param interceptorFactory The interceptor to add
     * @param priority           The priority
     */
    public void addPostActivateInterceptors(List<InterceptorFactory> interceptorFactory, int priority) {
        postActivateInterceptors.add(interceptorFactory, priority);
    }

    /**
     * Adds a post activate interceptor
     *
     * @param interceptorFactory The interceptor to add
     * @param priority           The priority
     */
    public void addPostActivateInterceptor(InterceptorFactory interceptorFactory, int priority) {
        postActivateInterceptors.add(Collections.singletonList(interceptorFactory), priority);
    }
    /**
     * Get the application name.
     *
     * @return the application name
     */
    public String getApplicationName() {
        return componentDescription.getApplicationName();
    }

    /**
     * Get the module name.
     *
     * @return the module name
     */
    public String getModuleName() {
        return componentDescription.getModuleName();
    }

    /**
     * Get the instance factory for this component.
     *
     * @return the instance factory
     */
    public ComponentFactory getInstanceFactory() {
        return instanceFactory;
    }

    /**
     * Set the instance factory for this component.
     *
     * @param instanceFactory the instance factory
     */
    public void setInstanceFactory(final ComponentFactory instanceFactory) {
        this.instanceFactory = instanceFactory;
    }

    public ClassReflectionIndex getClassIndex() {
        return classIndex;
    }

    /**
     * Get the component create service factory for this component.
     *
     * @return the component create service factory
     */
    public ComponentCreateServiceFactory getComponentCreateServiceFactory() {
        return componentCreateServiceFactory;
    }

    /**
     * Set the component create service factory for this component.
     *
     * @param componentCreateServiceFactory the component create service factory
     */
    public void setComponentCreateServiceFactory(final ComponentCreateServiceFactory componentCreateServiceFactory) {
        if (componentCreateServiceFactory == null) {
            throw EeLogger.ROOT_LOGGER.nullVar("componentCreateServiceFactory", "component", getComponentName());
        }
        this.componentCreateServiceFactory = componentCreateServiceFactory;
    }

    public String toString() {
        return getClass().getName() + "[name=" + componentDescription.getComponentName() + " class=" + componentDescription.getComponentClassName() + "]";
    }

    public InterceptorFactory getNamespaceContextInterceptorFactory() {
        return namespaceContextInterceptorFactory;
    }

    public void setNamespaceContextInterceptorFactory(InterceptorFactory interceptorFactory) {
        this.namespaceContextInterceptorFactory = interceptorFactory;
    }

    public ClassLoader getModuleClassLoader() {
        return moduleClassLoader;
    }

    public ModuleLoader getModuleLoader() {
        return moduleLoader;
    }

    /**
     * @return The components namespace context selector, if any
     */
    public NamespaceContextSelector getNamespaceContextSelector() {
        return namespaceContextSelector;
    }

    public void setNamespaceContextSelector(final NamespaceContextSelector namespaceContextSelector) {
        this.namespaceContextSelector = namespaceContextSelector;
    }

    public Set<Object> getInterceptorContextKeys() {
        return interceptorContextKeys;
    }

    public ConcurrentContext getConcurrentContext() {
        return concurrentContext;
    }

    /**
     * Adds a lifecycle method to the lifecycle methods set
     *
     * @param method The lifecycle method
     */
    public void addLifecycleMethod(Method method) {
        lifecycleMethods.add(method);
    }

    /**
     * Returns a set of all lifecycle methods defined on the bean
     *
     * @return All lifecycle methods defined on the component class and its superclasses
     */
    public Set<Method> getLifecycleMethods() {
        return Collections.unmodifiableSet(lifecycleMethods);
    }
}


/*
 * 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.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Map;

import org.jboss.as.ee.logging.EeLogger;
import org.jboss.as.naming.context.NamespaceContextSelector;
import org.jboss.invocation.InterceptorFactory;
import org.jboss.invocation.Interceptors;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;

/**
 * A service for creating a component.
 *
 * @author David M. Lloyd
 * @author Richard Opalka
 */
public class BasicComponentCreateService implements Service<Component> {

    private final ServiceName serviceName;
    private final String componentName;
    private final Class<?> componentClass;
    private final InterceptorFactory postConstruct;
    private final InterceptorFactory preDestroy;
    private final Map<Method, InterceptorFactory> componentInterceptors;
    private final NamespaceContextSelector namespaceContextSelector;
    private BasicComponent component;

    /**
     * Construct a new instance.
     *
     * @param componentConfiguration the component configuration
     */
    public BasicComponentCreateService(final ComponentConfiguration componentConfiguration) {
        serviceName = componentConfiguration.getComponentDescription().getCreateServiceName();
        componentName = componentConfiguration.getComponentName();
        postConstruct = Interceptors.getChainedInterceptorFactory(componentConfiguration.getPostConstructInterceptors());
        preDestroy = Interceptors.getChainedInterceptorFactory(componentConfiguration.getPreDestroyInterceptors());
        final IdentityHashMap<Method, InterceptorFactory> componentInterceptors = new IdentityHashMap<Method, InterceptorFactory>();
        for (Method method : componentConfiguration.getDefinedComponentMethods()) {
            if(requiresInterceptors(method, componentConfiguration)) {
                componentInterceptors.put(method, Interceptors.getChainedInterceptorFactory(componentConfiguration.getComponentInterceptors(method)));
            }
        }
        componentClass = componentConfiguration.getComponentClass();
        this.componentInterceptors = componentInterceptors;
        this.namespaceContextSelector = componentConfiguration.getNamespaceContextSelector();
    }

    protected boolean requiresInterceptors(final Method method, final ComponentConfiguration componentConfiguration) {
        return Modifier.isPublic(method.getModifiers()) && !Modifier.isFinal(method.getModifiers()) && componentConfiguration.getComponentDescription().isIntercepted();
    }

    /**
     * {@inheritDoc}
     */
    public synchronized void start(final StartContext context) throws StartException {
        component = createComponent();
    }

    /**
     * Create the component.
     *
     * @return the component instance
     */
    protected BasicComponent createComponent() {
        return new BasicComponent(this);
    }

    /**
     * {@inheritDoc}
     */
    public synchronized void stop(final StopContext context) {
        component = null;
    }

    /**
     * {@inheritDoc}
     */
    public synchronized Component getValue() throws IllegalStateException, IllegalArgumentException {
        Component component = this.component;
        if (component == null) {
            throw EeLogger.ROOT_LOGGER.serviceNotStarted();
        }
        return component;
    }

    /**
     * Get the component name.
     *
     * @return the component name
     */
    public String getComponentName() {
        return componentName;
    }

    /**
     * Get the post-construct interceptor factory.
     *
     * @return the post-construct interceptor factory
     */
    public InterceptorFactory getPostConstruct() {
        return postConstruct;
    }

    /**
     * Get the pre-destroy interceptor factory.
     *
     * @return the pre-destroy interceptor factory
     */
    public InterceptorFactory getPreDestroy() {
        return preDestroy;
    }

    /**
     * Get the component interceptor factory map.
     *
     * @return the component interceptor factories
     */
    public Map<Method, InterceptorFactory> getComponentInterceptors() {
        return componentInterceptors;
    }

    /**
     * Get the component class.
     *
     * @return the component class
     */
    public Class<?> getComponentClass() {
        return componentClass;
    }

    /**
     *
     * @return the namespace context selector for the component, or null if it does not have one
     */
    public NamespaceContextSelector getNamespaceContextSelector() {
        return namespaceContextSelector;
    }

    public ServiceName getServiceName() {
        return this.serviceName;
    }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.as.weld.services.bootstrap;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.jboss.as.server.moduleservice.ServiceModuleLoader;
import org.jboss.as.weld.logging.WeldLogger;
import org.jboss.as.weld.util.Reflections;
import org.jboss.modules.ClassDefiner;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleClassLoader;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.weld.interceptor.proxy.LifecycleMixin;
import org.jboss.weld.logging.BeanLogger;
import org.jboss.weld.proxy.WeldConstruct;
import org.jboss.weld.serialization.spi.BeanIdentifier;
import org.jboss.weld.serialization.spi.ProxyServices;
import org.wildfly.security.manager.WildFlySecurityManager;

import static java.security.AccessController.doPrivileged;

/**
 * {@link ProxyServices} implementation that delegates to the module class loader if the bean class loader cannot be determined
 *
 * @author Stuart Douglas
 * @author Jozef Hartinger
 *
 */
public class ProxyServicesImpl implements ProxyServices {

    private static String[] REQUIRED_WELD_DEPENDENCIES = new String[]{
            "org.jboss.weld.core",
            "org.jboss.weld.spi",
            "org.jboss.weld.api"
    };

    // these are used to check whether a classloader is capable of loading Weld proxies
    private static String[] WELD_CLASSES = new String[]{
            BeanIdentifier.class.getName(), // Weld SPI
            LifecycleMixin.class.getName(), // Weld core
            WeldConstruct.class.getName() // Weld API
    };

    private final Module module;
    private final ConcurrentMap<ModuleIdentifier, Boolean> processedStaticModules = new ConcurrentHashMap<>();
    private final ClassDefiner classDefiner;

    public ProxyServicesImpl(Module module) {
        this.module = module;
        this.classDefiner = ClassDefiner.getInstance();
    }

    public ClassLoader getClassLoader(final Class<?> proxiedBeanType) {
        if (WildFlySecurityManager.isChecking()) {
            return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
                public ClassLoader run() {
                    return _getClassLoader(proxiedBeanType);
                }
            });
        } else {
            return _getClassLoader(proxiedBeanType);
        }
    }

    private ClassLoader _getClassLoader(Class<?> proxiedBeanType) {
        if (proxiedBeanType.getName().startsWith("java")) {
            return module.getClassLoader();
        } else if (proxiedBeanType.getClassLoader() instanceof ModuleClassLoader) {
            final ModuleClassLoader loader = (ModuleClassLoader) proxiedBeanType.getClassLoader();
            //even though this is not strictly spec compliant if a class from the app server is
            //being proxied we use the deployment CL to prevent a memory leak
            //in theory this means that package private methods will not work correctly
            //however the application does not have access to package private methods anyway
            //as it is in a different class loader
            if (loader.getModule().getModuleLoader() instanceof ServiceModuleLoader) {
                //this is a dynamic module
                //we can use it to load the proxy
                return proxiedBeanType.getClassLoader();
            } else {
                // this class comes from a static module
                // first, check if we can use its classloader to load proxy classes
                final Module definingModule = loader.getModule();
                Boolean hasWeldDependencies = processedStaticModules.get(definingModule.getIdentifier());
                boolean logWarning = false; // only log for the first class in the module

                if (hasWeldDependencies == null) {
                    hasWeldDependencies = canLoadWeldProxies(definingModule); // may be run multiple times but that does not matter
                    logWarning = processedStaticModules.putIfAbsent(definingModule.getIdentifier(), hasWeldDependencies) == null;
                }
                if (hasWeldDependencies) {
                    // this module declares weld dependencies - we can use module's classloader to load the proxy class
                    // pros: package-private members will work fine
                    // cons: proxy classes will remain loaded by the module's classloader after undeployment (nothing else leaks)
                    return proxiedBeanType.getClassLoader();
                } else {
                    // no weld dependencies - we use deployment's classloader to load the proxy class
                    // pros: proxy classes unloaded with undeployment
                    // cons: package-private methods and constructors will yield IllegalAccessException
                    if (logWarning) {
                        WeldLogger.ROOT_LOGGER.loadingProxiesUsingDeploymentClassLoader(definingModule.getIdentifier(), Arrays.toString(REQUIRED_WELD_DEPENDENCIES));
                    }
                    return this.module.getClassLoader();
                }
            }
        } else {
            return proxiedBeanType.getClassLoader();
        }
    }

    /**
     * This method corresponds to the original {@code _getClassLoader()} method and returns
     * {@link Module} based on original class. This module is then used when defining proxy classes.
     *
     * Most likely we want to return a {@link Module} into which the originalClass belongs but in case such a module
     * doesn't know Weld dependencies, we need to return different module, one that was used by the deployment.
     */
    private Module getModule(Class<?> originalClass) {
        if (originalClass.getName().startsWith("java")) {
            return module;
        } else {
            Module definingModule = Module.forClass(originalClass);
            Boolean hasWeldDependencies = processedStaticModules.get(definingModule.getIdentifier());
            boolean logWarning = false; // only log for the first class in the module
            if (hasWeldDependencies == null) {
                hasWeldDependencies = canLoadWeldProxies(definingModule); // may be run multiple times but that does not matter
                logWarning = processedStaticModules.putIfAbsent(definingModule.getIdentifier(), hasWeldDependencies) == null;
            }
            if (hasWeldDependencies) {
                // this module declares Weld dependencies - we can use module's classloader to load the proxy class
                // pros: package-private members will work fine
                // cons: proxy classes will remain loaded by the module's classloader after undeployment (nothing else leaks)
                return definingModule;
            } else {
                // no weld dependencies - we use deployment's classloader to load the proxy class
                // pros: proxy classes unloaded with undeployment
                // cons: package-private methods and constructors will yield IllegalAccessException
                if (logWarning) {
                    WeldLogger.ROOT_LOGGER.loadingProxiesUsingDeploymentClassLoader(definingModule.getIdentifier(), Arrays.toString(REQUIRED_WELD_DEPENDENCIES));
                }
                return this.module;
            }
        }
    }

    private static boolean canLoadWeldProxies(Module module) {
        for (String weldClass : WELD_CLASSES) {
            if (!Reflections.isAccessible(weldClass, module.getClassLoader())) {
                return false;
            }
        }
        return true;
    }

    public void cleanup() {
        processedStaticModules.clear();
    }

    public Class<?> loadBeanClass(final String className) {
        try {
            return (Class<?>) AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                public Object run() throws Exception {
                    return Class.forName(className, true, getClassLoader(this.getClass()));
                }
            });
        } catch (PrivilegedActionException pae) {
            throw BeanLogger.LOG.cannotLoadClass(className, pae.getException());
        }
    }

    @Override
    public Class<?> defineClass(Class<?> originalClass, String s, byte[] bytes, int i, int i1) throws ClassFormatError {
        return defineClass(originalClass, s, bytes, i, i1, getProtectionDomain(originalClass));
    }

    @Override
    public Class<?> defineClass(Class<?> originalClass, String s, byte[] bytes, int i, int i1, ProtectionDomain protectionDomain) throws ClassFormatError {
        return classDefiner.defineClass(getModule(originalClass), s, protectionDomain, bytes, i, i1);
    }

    @Override
    public Class<?> loadClass(Class<?> originalClass, String classBinaryName) throws ClassNotFoundException {
        Module module = getModule(originalClass);
        if (module == null) {
            throw new IllegalArgumentException("Original " + originalClass + " does not have a module");
        }
        return module.getClassLoader().loadClass(classBinaryName);
    }

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

    // copied over from ClassDefiner as it's missing a method that would allow to define class based on module but
    // without ProtectionDomain
    private ProtectionDomain getProtectionDomain(final Class<?> clazz) {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            return doPrivileged((PrivilegedAction<ProtectionDomain>) clazz::getProtectionDomain);
        } else {
            return clazz.getProtectionDomain();
        }
    }
}