/* * 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.util.Collections; import java.util.HashMap; 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.utils.DescriptorUtils; import org.jboss.as.naming.ManagedReference; import org.jboss.invocation.Interceptor; import org.jboss.invocation.InterceptorContext; import org.jboss.invocation.InterceptorFactory; import org.jboss.invocation.Interceptors; import org.jboss.invocation.SimpleInterceptorFactoryContext; import org.jboss.invocation.proxy.ProxyFactory; import org.jboss.msc.inject.Injector; import org.jboss.msc.service.Service; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; import org.jboss.msc.value.InjectedValue; import org.wildfly.security.manager.WildFlySecurityManager; import static org.jboss.as.ee.logging.EeLogger.ROOT_LOGGER; /** * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ public final class ViewService implements Service<ComponentView> { private final InjectedValue<Component> componentInjector = new InjectedValue<Component>(); private final Map<Method, InterceptorFactory> viewInterceptorFactories; private final Map<Method, InterceptorFactory> clientInterceptorFactories; private final InterceptorFactory clientPostConstruct; private final InterceptorFactory clientPreDestroy; private final ProxyFactory<?> proxyFactory; private final Class<?> viewClass; private final Set<Method> asyncMethods; private final ViewInstanceFactory viewInstanceFactory; private final Map<Class<?>, Object> privateData; private volatile ComponentView view; private volatile Interceptor clientPostConstructInterceptor; private volatile Interceptor clientPreDestroyInterceptor; private volatile Map<Method, Interceptor> clientInterceptors; public ViewService(final ViewConfiguration viewConfiguration) { viewClass = viewConfiguration.getViewClass(); final ProxyFactory<?> proxyFactory = viewConfiguration.getProxyFactory(); this.proxyFactory = proxyFactory; final List<Method> methods = proxyFactory.getCachedMethods(); final int methodCount = methods.size(); clientPostConstruct = Interceptors.getChainedInterceptorFactory(viewConfiguration.getClientPostConstructInterceptors()); clientPreDestroy = Interceptors.getChainedInterceptorFactory(viewConfiguration.getClientPreDestroyInterceptors()); final IdentityHashMap<Method, InterceptorFactory> viewInterceptorFactories = new IdentityHashMap<Method, InterceptorFactory>(methodCount); final IdentityHashMap<Method, InterceptorFactory> clientInterceptorFactories = new IdentityHashMap<Method, InterceptorFactory>(methodCount); for (final Method method : methods) { if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) { viewInterceptorFactories.put(method, Interceptors.getTerminalInterceptorFactory()); } else { viewInterceptorFactories.put(method, Interceptors.getChainedInterceptorFactory(viewConfiguration.getViewInterceptors(method))); clientInterceptorFactories.put(method, Interceptors.getChainedInterceptorFactory(viewConfiguration.getClientInterceptors(method))); } } this.viewInterceptorFactories = viewInterceptorFactories; this.clientInterceptorFactories = clientInterceptorFactories; this.asyncMethods = viewConfiguration.getAsyncMethods(); if (viewConfiguration.getViewInstanceFactory() == null) { viewInstanceFactory = new DefaultViewInstanceFactory(); } else { viewInstanceFactory = viewConfiguration.getViewInstanceFactory(); } if(viewConfiguration.getPrivateData().isEmpty()) { privateData = Collections.emptyMap(); } else { privateData = viewConfiguration.getPrivateData(); } } public void start(final StartContext context) throws StartException { // Construct the view View view = new View(privateData); view.initializeInterceptors(); this.view = view; final SimpleInterceptorFactoryContext factoryContext = new SimpleInterceptorFactoryContext(); final Component component = view.getComponent(); factoryContext.getContextData().put(Component.class, component); factoryContext.getContextData().put(ComponentView.class, view); clientPostConstructInterceptor = clientPostConstruct.create(factoryContext); clientPreDestroyInterceptor = clientPreDestroy.create(factoryContext); final Map<Method, InterceptorFactory> clientInterceptorFactories = ViewService.this.clientInterceptorFactories; clientInterceptors = new IdentityHashMap<Method, Interceptor>(clientInterceptorFactories.size()); for (Method method : clientInterceptorFactories.keySet()) { clientInterceptors.put(method, clientInterceptorFactories.get(method).create(factoryContext)); } } public void stop(final StopContext context) { view = null; } public Injector<Component> getComponentInjector() { return componentInjector; } public ComponentView getValue() throws IllegalStateException, IllegalArgumentException { return view; } class View implements ComponentView { private final Component component; private final Map<Method, Interceptor> viewInterceptors; private final Map<MethodDescription, Method> methods; private final Map<Class<?>, Object> privateData; View(final Map<Class<?>, Object> privateData) { this.privateData = privateData; component = componentInjector.getValue(); //we need to build the view interceptor chain this.viewInterceptors = new IdentityHashMap<Method, Interceptor>(); this.methods = new HashMap<MethodDescription, Method>(); } void initializeInterceptors() { final SimpleInterceptorFactoryContext factoryContext = new SimpleInterceptorFactoryContext(); final Map<Method, InterceptorFactory> viewInterceptorFactories = ViewService.this.viewInterceptorFactories; final Map<Method, Interceptor> viewEntryPoints = viewInterceptors; factoryContext.getContextData().put(Component.class, component); //we don't have this code in the constructor so we avoid passing around //a half constructed instance factoryContext.getContextData().put(ComponentView.class, this); for (Method method : viewInterceptorFactories.keySet()) { viewEntryPoints.put(method, viewInterceptorFactories.get(method).create(factoryContext)); methods.put(new MethodDescription(method.getName(), DescriptorUtils.methodDescriptor(method)), method); } } public ManagedReference createInstance() throws Exception { return createInstance(Collections.<Object, Object>emptyMap()); } public ManagedReference createInstance(Map<Object, Object> contextData) throws Exception { // view instance creation can lead to instantiating application component classes (like the MDB implementation class // or even the EJB implementation class of a no-interface view exposing bean). Such class initialization needs to // have the TCCL set to the component/application's classloader. @see https://issues.jboss.org/browse/WFLY-3989 final ClassLoader oldTCCL = WildFlySecurityManager.getCurrentContextClassLoaderPrivileged(); try { WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(component.getComponentClass()); return viewInstanceFactory.createViewInstance(this, contextData); } finally { // reset the TCCL WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(oldTCCL); } } @Override public Object invoke(InterceptorContext interceptorContext) throws Exception { if(component instanceof BasicComponent) { ((BasicComponent) component).waitForComponentStart(); } final Method method = interceptorContext.getMethod(); final Interceptor interceptor = viewInterceptors.get(method); return interceptor.processInvocation(interceptorContext); } public Component getComponent() { return component; } @Override public Class<?> getProxyClass() { return proxyFactory.defineClass(); } @Override public Class<?> getViewClass() { return viewClass; } @Override public Set<Method> getViewMethods() { return viewInterceptors.keySet(); } @Override public Method getMethod(final String name, final String descriptor) { Method method = this.methods.get(new MethodDescription(name, descriptor)); if (method == null) { throw EeLogger.ROOT_LOGGER.viewMethodNotFound(name, descriptor, viewClass, component.getComponentClass()); } return method; } @Override public <T> T getPrivateData(final Class<T> clazz) { return (T) privateData.get(clazz); } @Override public boolean isAsynchronous(final Method method) { return asyncMethods.contains(method); } @Override public String toString() { return "Component view " + viewClass + " for component " + component.getComponentClass(); } private final class MethodDescription { private final String name; private final String descriptor; public MethodDescription(final String name, final String descriptor) { this.name = name; this.descriptor = descriptor; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final MethodDescription that = (MethodDescription) o; if (!descriptor.equals(that.descriptor)) return false; if (!name.equals(that.name)) return false; return true; } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + descriptor.hashCode(); return result; } } } private class DefaultViewInstanceFactory implements ViewInstanceFactory { public ManagedReference createViewInstance(final ComponentView componentView, final Map<Object, Object> contextData) throws Exception { final Object proxy; final Component component = componentView.getComponent(); final ComponentClientInstance instance = new ComponentClientInstance(); try { proxy = proxyFactory.newInstance(new ProxyInvocationHandler(clientInterceptors, instance, componentView)); } catch (InstantiationException e) { InstantiationError error = new InstantiationError(e.getMessage()); Throwable cause = e.getCause(); if (cause != null) error.initCause(cause); throw error; } catch (IllegalAccessException e) { IllegalAccessError error = new IllegalAccessError(e.getMessage()); Throwable cause = e.getCause(); if (cause != null) error.initCause(cause); throw error; } InterceptorContext context = new InterceptorContext(); context.putPrivateData(ComponentView.class, componentView); context.putPrivateData(Component.class, component); context.putPrivateData(ComponentClientInstance.class, instance); context.setContextData(new HashMap<String, Object>()); for(Map.Entry<Object, Object> entry : contextData.entrySet()) { context.putPrivateData(entry.getKey(), entry.getValue()); } clientPostConstructInterceptor.processInvocation(context); instance.constructionComplete(); return new ManagedReference() { @Override public void release() { try { InterceptorContext interceptorContext = new InterceptorContext(); interceptorContext.putPrivateData(ComponentView.class, componentView); interceptorContext.putPrivateData(Component.class, component); clientPreDestroyInterceptor.processInvocation(interceptorContext); } catch (Exception e) { ROOT_LOGGER.preDestroyInterceptorFailure(e, component.getComponentClass()); } } @Override public Object getInstance() { return proxy; } }; } } }