/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services 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. * * Granite Data Services 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.tide.seam; import static org.jboss.seam.annotations.Install.FRAMEWORK; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.granite.tide.annotations.BypassTideInterceptor; import org.granite.tide.async.AsyncPublisher; import org.granite.tide.data.DataEnabled; import org.granite.tide.seam.async.TideAsynchronousInterceptor; import org.jboss.seam.Component; import org.jboss.seam.Namespace; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.Startup; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.annotations.intercept.InterceptorType; import org.jboss.seam.async.AsynchronousInterceptor; import org.jboss.seam.contexts.Context; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.core.Init; import org.jboss.seam.core.Init.FactoryExpression; import org.jboss.seam.core.Init.FactoryMethod; import org.jboss.seam.init.Initialization; import org.jboss.seam.intercept.Interceptor; import org.jboss.seam.log.Log; import org.jboss.seam.util.Reflections; /** * Particular initializer for Tide which instruments all Seam components * - Tide bijection/invocation interceptor is added * - Tide asynchronous interceptor is added * * @author William DRAI */ @Scope(ScopeType.APPLICATION) @Name("org.granite.tide.seam.init") @Install(precedence=FRAMEWORK) @Startup @BypassInterceptors public class TideInit { @Logger private Log log; private Map<Component, Set<FactoryVariable>> factoryComponents = new HashMap<Component, Set<FactoryVariable>>(); public TideInit() { Context context = Contexts.getApplicationContext(); for (String name : context.getNames()) { if (!name.endsWith(Initialization.COMPONENT_SUFFIX)) continue; Object object = context.get(name); if (object instanceof Component) { Component component = (Component) object; if (component.isInterceptionEnabled() && component.getScope() != ScopeType.APPLICATION) instrumentComponent(component); } } Init.instance().importNamespace("org.granite.tide"); scanFactoryComponents(); } public static Set<FactoryVariable> getFactoredVariables(Component component) { TideInit init = (TideInit)Contexts.getApplicationContext().get(TideInit.class); return init.factoryComponents.get(component); } @SuppressWarnings("unchecked") private void scanFactoryComponents() { try { Init init = Init.instance(); Field field = init.getClass().getDeclaredField("factories"); field.setAccessible(true); Map<String, FactoryMethod> factories = (Map<String, FactoryMethod>)field.get(init); for (Map.Entry<String, FactoryMethod> factory : factories.entrySet()) { String componentName = factory.getValue().getComponent().getName(); addFactoryVariable(componentName, factory.getKey(), factory.getValue().getScope()); } field = init.getClass().getDeclaredField("factoryMethodExpressions"); field.setAccessible(true); Map<String, FactoryExpression> factoryExpressions = (Map<String, FactoryExpression>)field.get(init); for (Map.Entry<String, FactoryExpression> factoryExpression : factoryExpressions.entrySet()) { String expressionString = factoryExpression.getValue().getMethodBinding().getExpressionString(); String componentName = resolveComponentName(expressionString); if (componentName != null) addFactoryVariable(componentName, factoryExpression.getKey(), factoryExpression.getValue().getScope()); } field = init.getClass().getDeclaredField("factoryValueExpressions"); field.setAccessible(true); factoryExpressions = (Map<String, FactoryExpression>)field.get(init); for (Map.Entry<String, FactoryExpression> factoryExpression : factoryExpressions.entrySet()) { String expressionString = factoryExpression.getValue().getMethodBinding().getExpressionString(); String componentName = resolveComponentName(expressionString); if (componentName != null) addFactoryVariable(componentName, factoryExpression.getKey(), factoryExpression.getValue().getScope()); } } catch (Exception e) { log.error("Could not initialize factory components map correctly", e); } } private String resolveComponentName(String expressionString) { Init init = Init.instance(); String expr = expressionString.startsWith("#{") ? expressionString.substring(2, expressionString.length()-1) : expressionString; int idx = expr.indexOf('.'); String componentName = idx >= 0 ? expr.substring(0, idx) : expr; Component component = lookupComponent(componentName); if (component != null) return componentName; if (idx < 0) return null; Namespace ns = init.getRootNamespace().getChild(componentName); while (ns != null) { expr = expr.substring(idx+1); idx = expr.indexOf('.'); String name = idx >= 0 ? expr.substring(0, idx) : expr; if (ns.hasChild(name)) ns = ns.getChild(name); else { try { // Must use this hack to get the right name as all methods are private in Seam Namespace object Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class); m.setAccessible(true); componentName = (String)Reflections.invoke(m, ns, name); component = Component.forName(componentName); return component != null ? componentName : null; } catch (Exception e) { // Ignore } } } return null; } private void addFactoryVariable(String componentName, String variableName, ScopeType scope) { Component component = lookupComponent(componentName); Set<FactoryVariable> variables = factoryComponents.get(component); if (variables == null) { variables = new HashSet<FactoryVariable>(); factoryComponents.put(component, variables); } variables.add(new FactoryVariable(variableName, scope)); } public static final class FactoryVariable { private final String variableName; private final ScopeType scope; public FactoryVariable(String variableName, ScopeType scope) { this.variableName = variableName; this.scope = scope; } public String getVariableName() { return variableName; } public ScopeType getScope() { return scope; } @Override public boolean equals(Object obj) { if (!(obj instanceof FactoryVariable)) return false; return ((FactoryVariable)obj).variableName.equals(variableName) && ((FactoryVariable)obj).scope.equals(scope); } @Override public int hashCode() { return (variableName + "@" + scope).hashCode(); } } /** * Implementation of component lookup for Seam * Uses a hack to handle imports and fully qualified names * * @param componentName name of the component * @return Component object */ static Component lookupComponent(String componentName) { Init init = Init.instance(); Component component = Component.forName(componentName); if (component != null) return component; // Look for the component in the imported namespaces (always give precedence to org.granite.tide overriden components) for (Namespace ns : init.getGlobalImports()) { try { // Must use this hack to get the right name as all methods are private in Seam Namespace object Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class); m.setAccessible(true); String nsName = (String)Reflections.invoke(m, ns, componentName); if (!nsName.startsWith("org.granite.tide")) continue; component = Component.forName(nsName); if (component != null) return component; } catch (Exception e) { // Ignore } } for (Namespace ns : init.getGlobalImports()) { try { // Must use this hack to get the right name as all methods are private in Seam Namespace object Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class); m.setAccessible(true); String nsName = (String)Reflections.invoke(m, ns, componentName); component = Component.forName(nsName); if (component != null) return component; } catch (Exception e) { // Ignore } } return null; } /** * Implementation of factory lookup for Seam * Uses a hack to manage imports and fully qualified names * * @param componentName name of the factored component * @return FactoryMethod object */ static FactoryMethod lookupFactory(String componentName) { Init init = Init.instance(); FactoryMethod factoryMethod = init.getFactory(componentName); if (factoryMethod != null) return factoryMethod; // Look for the factory in the imported namespaces (always give precedence to org.granite.tide overriden components) for (Namespace ns : init.getGlobalImports()) { try { // Must use this hack to get the right name as all methods are private in Seam Namespace object Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class); m.setAccessible(true); String nsName = (String)Reflections.invoke(m, ns, componentName); if (!nsName.startsWith("org.granite.tide")) continue; factoryMethod = init.getFactory(nsName); if (factoryMethod != null) return factoryMethod; } catch (Exception e) { // Ignore } } for (Namespace ns : init.getGlobalImports()) { try { // Must use this hack to get the right name as all methods are private in Seam Namespace object Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class); m.setAccessible(true); String nsName = (String)Reflections.invoke(m, ns, componentName); factoryMethod = init.getFactory(nsName); if (factoryMethod != null) return factoryMethod; } catch (Exception e) { // Ignore } } return null; } /** * Implementation of factory expression lookup for Seam * Uses a hack to manage imports and fully qualified names * * @param componentName name of the factored component * @return FactoryMethod object */ static FactoryExpression lookupFactoryExpression(String componentName) { Init init = Init.instance(); FactoryExpression factoryExpression = init.getFactoryMethodExpression(componentName); if (factoryExpression != null) return factoryExpression; factoryExpression = init.getFactoryValueExpression(componentName); if (factoryExpression != null) return factoryExpression; // Look for the factory expression in the imported namespaces (always give precedence to org.granite.tide overriden components) for (Namespace ns : init.getGlobalImports()) { try { // Must use this hack to get the right name as all methods are private in Seam Namespace object Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class); m.setAccessible(true); String nsName = (String)Reflections.invoke(m, ns, componentName); if (!nsName.startsWith("org.granite.tide")) continue; factoryExpression = init.getFactoryMethodExpression(nsName); if (factoryExpression != null) return factoryExpression; factoryExpression = init.getFactoryValueExpression(nsName); if (factoryExpression != null) return factoryExpression; } catch (Exception e) { // Ignore } } for (Namespace ns : init.getGlobalImports()) { try { // Must use this hack to get the right name as all methods are private in Seam Namespace object Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class); m.setAccessible(true); String nsName = (String)Reflections.invoke(m, ns, componentName); factoryExpression = init.getFactoryMethodExpression(nsName); if (factoryExpression != null) return factoryExpression; factoryExpression = init.getFactoryValueExpression(nsName); if (factoryExpression != null) return factoryExpression; } catch (Exception e) { // Ignore } } return null; } private void instrumentComponent(Component component) { if (component.getBeanClass().isAnnotationPresent(BypassTideInterceptor.class)) return; List<Interceptor> li = component.getInterceptors(InterceptorType.ANY); boolean newSortServer = false, newSortClient = false; boolean found = false; for (Interceptor i : li) { if (i.getUserInterceptorClass().equals(TideInterceptor.class)) { found = true; break; } } if (!found) { component.addInterceptor(new Interceptor(new TideInterceptor(), component)); newSortServer = true; } if (component.beanClassHasAnnotation(DataEnabled.class) && component.getBeanClass().getAnnotation(DataEnabled.class).useInterceptor()) { found = false; for (Interceptor i : li) { if (i.getUserInterceptorClass().equals(TideDataPublishingInterceptor.class)) { found = true; break; } } if (!found) { component.addInterceptor(new Interceptor(new TideDataPublishingInterceptor(), component)); newSortServer = true; } } // Check if AsyncPublisher installed AsyncPublisher asyncPublisher = (AsyncPublisher)Component.getInstance("org.granite.tide.seam.async.publisher"); if (asyncPublisher != null) { boolean async = false; li = component.getClientSideInterceptors(); for (Iterator<Interceptor> i = li.iterator(); i.hasNext(); ) { Interceptor in = i.next(); if (in.getUserInterceptorClass().equals(AsynchronousInterceptor.class)) { async = true; break; } } if (async) { component.addInterceptor(new Interceptor(new TideAsynchronousInterceptor(), component)); newSortClient = true; } } if (newSortServer || newSortClient) { // Force correct sorting of interceptors: hack because newSort is private try { Method m = component.getClass().getDeclaredMethod("newSort", List.class); m.setAccessible(true); if (newSortServer) m.invoke(component, component.getInterceptors(InterceptorType.SERVER)); if (newSortClient) m.invoke(component, component.getInterceptors(InterceptorType.CLIENT)); } catch (Exception e) { // Ignore all } } } }