001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2014 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022package org.granite.tide.seam;
023
024import static org.jboss.seam.annotations.Install.FRAMEWORK;
025
026import java.lang.reflect.Field;
027import java.lang.reflect.Method;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034
035import org.granite.tide.annotations.BypassTideInterceptor;
036import org.granite.tide.async.AsyncPublisher;
037import org.granite.tide.data.DataEnabled;
038import org.granite.tide.seam.async.TideAsynchronousInterceptor;
039import org.jboss.seam.Component;
040import org.jboss.seam.Namespace;
041import org.jboss.seam.ScopeType;
042import org.jboss.seam.annotations.Install;
043import org.jboss.seam.annotations.Logger;
044import org.jboss.seam.annotations.Name;
045import org.jboss.seam.annotations.Scope;
046import org.jboss.seam.annotations.Startup;
047import org.jboss.seam.annotations.intercept.BypassInterceptors;
048import org.jboss.seam.annotations.intercept.InterceptorType;
049import org.jboss.seam.async.AsynchronousInterceptor;
050import org.jboss.seam.contexts.Context;
051import org.jboss.seam.contexts.Contexts;
052import org.jboss.seam.core.Init;
053import org.jboss.seam.core.Init.FactoryExpression;
054import org.jboss.seam.core.Init.FactoryMethod;
055import org.jboss.seam.init.Initialization;
056import org.jboss.seam.intercept.Interceptor;
057import org.jboss.seam.log.Log;
058import org.jboss.seam.util.Reflections;
059
060
061/**
062 * Particular initializer for Tide which instruments all Seam components
063 * - Tide bijection/invocation interceptor is added
064 * - Tide asynchronous interceptor is added
065 * 
066 * @author William DRAI
067 */
068@Scope(ScopeType.APPLICATION)
069@Name("org.granite.tide.seam.init")
070@Install(precedence=FRAMEWORK)
071@Startup
072@BypassInterceptors
073public class TideInit {
074        
075        @Logger
076        private Log log;
077        
078        private Map<Component, Set<FactoryVariable>> factoryComponents = new HashMap<Component, Set<FactoryVariable>>();
079        
080    
081    public TideInit() {
082        Context context = Contexts.getApplicationContext();
083        for (String name : context.getNames()) {
084            if (!name.endsWith(Initialization.COMPONENT_SUFFIX))
085                continue;
086            Object object = context.get(name);
087            if (object instanceof Component) {
088                Component component = (Component) object;
089                if (component.isInterceptionEnabled() && component.getScope() != ScopeType.APPLICATION)
090                    instrumentComponent(component);
091            }
092        }
093        
094        Init.instance().importNamespace("org.granite.tide");
095        
096        scanFactoryComponents();
097    }
098    
099    
100    public static Set<FactoryVariable> getFactoredVariables(Component component) {
101        TideInit init = (TideInit)Contexts.getApplicationContext().get(TideInit.class);         
102        return init.factoryComponents.get(component);
103    }
104    
105    
106    @SuppressWarnings("unchecked")
107    private void scanFactoryComponents() {
108        try {
109                Init init = Init.instance();
110                
111                Field field = init.getClass().getDeclaredField("factories");
112                field.setAccessible(true);
113                Map<String, FactoryMethod> factories = (Map<String, FactoryMethod>)field.get(init);         
114                for (Map.Entry<String, FactoryMethod> factory : factories.entrySet()) {
115                        String componentName = factory.getValue().getComponent().getName();
116                        addFactoryVariable(componentName, factory.getKey(), factory.getValue().getScope());
117                }
118                
119                field = init.getClass().getDeclaredField("factoryMethodExpressions");
120                field.setAccessible(true);
121                Map<String, FactoryExpression> factoryExpressions = (Map<String, FactoryExpression>)field.get(init);                
122                for (Map.Entry<String, FactoryExpression> factoryExpression : factoryExpressions.entrySet()) {
123                        String expressionString = factoryExpression.getValue().getMethodBinding().getExpressionString();
124                        String componentName = resolveComponentName(expressionString);
125                        if (componentName != null)
126                                addFactoryVariable(componentName, factoryExpression.getKey(), factoryExpression.getValue().getScope());
127                }
128                
129                field = init.getClass().getDeclaredField("factoryValueExpressions");
130                field.setAccessible(true);
131                factoryExpressions = (Map<String, FactoryExpression>)field.get(init);             
132                for (Map.Entry<String, FactoryExpression> factoryExpression : factoryExpressions.entrySet()) {
133                        String expressionString = factoryExpression.getValue().getMethodBinding().getExpressionString();
134                        String componentName = resolveComponentName(expressionString);
135                        if (componentName != null)
136                                addFactoryVariable(componentName, factoryExpression.getKey(), factoryExpression.getValue().getScope());
137                }
138        }
139        catch (Exception e) {
140                log.error("Could not initialize factory components map correctly", e);
141        }
142    }
143    
144    
145    private String resolveComponentName(String expressionString) {
146        Init init = Init.instance();
147        
148        String expr = expressionString.startsWith("#{") ? expressionString.substring(2, expressionString.length()-1) : expressionString;
149                int idx = expr.indexOf('.');
150                String componentName = idx >= 0 ? expr.substring(0, idx) : expr;
151                Component component = lookupComponent(componentName);
152                if (component != null)
153                        return componentName;
154        
155                if (idx < 0)
156                        return null;
157                
158        Namespace ns = init.getRootNamespace().getChild(componentName);         
159        while (ns != null) {
160                expr = expr.substring(idx+1);
161                idx = expr.indexOf('.');
162                String name = idx >= 0 ? expr.substring(0, idx) : expr;
163                
164                if (ns.hasChild(name))
165                        ns = ns.getChild(name);
166                else {
167                        try {
168                                // Must use this hack to get the right name as all methods are private in Seam Namespace object
169                                Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class);
170                                m.setAccessible(true);
171                                componentName = (String)Reflections.invoke(m, ns, name);
172                                component = Component.forName(componentName);
173                                return component != null ? componentName : null;
174                        }
175                        catch (Exception e) {
176                                // Ignore
177                        }
178                }
179        }
180        
181        return null;
182    }
183    
184    
185    private void addFactoryVariable(String componentName, String variableName, ScopeType scope) {
186        Component component = lookupComponent(componentName);
187                Set<FactoryVariable> variables = factoryComponents.get(component);
188                if (variables == null) {
189                        variables = new HashSet<FactoryVariable>();
190                        factoryComponents.put(component, variables);
191                }
192                variables.add(new FactoryVariable(variableName, scope));
193    }
194    
195    
196    public static final class FactoryVariable {
197        private final String variableName;
198        private final ScopeType scope;
199        
200        public FactoryVariable(String variableName, ScopeType scope) {
201                this.variableName = variableName;
202                this.scope = scope;
203        }
204        
205        public String getVariableName() {
206                return variableName;
207        }
208        
209        public ScopeType getScope() {
210                return scope;
211        }
212        
213        @Override
214        public boolean equals(Object obj) {
215                if (!(obj instanceof FactoryVariable))
216                        return false;
217                return ((FactoryVariable)obj).variableName.equals(variableName) && ((FactoryVariable)obj).scope.equals(scope);    
218        }
219        
220        @Override
221        public int hashCode() {
222                return (variableName + "@" + scope).hashCode();
223        }
224    }
225    
226    
227    /**
228     * Implementation of component lookup for Seam
229     * Uses a hack to handle imports and fully qualified names
230     * 
231     * @param componentName name of the component
232     * @return Component object
233     */
234    static Component lookupComponent(String componentName) {
235        Init init = Init.instance();
236        
237        Component component = Component.forName(componentName);
238        if (component != null)
239                return component;
240        
241        // Look for the component in the imported namespaces (always give precedence to org.granite.tide overriden components)
242        for (Namespace ns : init.getGlobalImports()) {
243            try {
244                // Must use this hack to get the right name as all methods are private in Seam Namespace object
245                Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class);
246                m.setAccessible(true);
247                String nsName = (String)Reflections.invoke(m, ns, componentName);
248                if (!nsName.startsWith("org.granite.tide"))
249                        continue;
250                component = Component.forName(nsName);
251                if (component != null)
252                        return component;
253            }
254            catch (Exception e) {
255                // Ignore
256            }
257        }
258        
259        for (Namespace ns : init.getGlobalImports()) {
260                try {
261                        // Must use this hack to get the right name as all methods are private in Seam Namespace object
262                        Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class);
263                        m.setAccessible(true);
264                        String nsName = (String)Reflections.invoke(m, ns, componentName);
265                        component = Component.forName(nsName);
266                        if (component != null)
267                                return component;
268                }
269                catch (Exception e) {
270                        // Ignore
271                }
272        }
273        return null;
274    }
275    
276    
277    /**
278     * Implementation of factory lookup for Seam
279     * Uses a hack to manage imports and fully qualified names
280     * 
281     * @param componentName name of the factored component
282     * @return FactoryMethod object
283     */
284    static FactoryMethod lookupFactory(String componentName) {
285        Init init = Init.instance();
286        
287        FactoryMethod factoryMethod = init.getFactory(componentName);
288        if (factoryMethod != null)
289                return factoryMethod;
290        
291        // Look for the factory in the imported namespaces (always give precedence to org.granite.tide overriden components)
292        for (Namespace ns : init.getGlobalImports()) {
293                try {
294                        // Must use this hack to get the right name as all methods are private in Seam Namespace object
295                        Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class);
296                        m.setAccessible(true);
297                        String nsName = (String)Reflections.invoke(m, ns, componentName);
298                        if (!nsName.startsWith("org.granite.tide"))
299                                continue;
300                        factoryMethod = init.getFactory(nsName);
301                        if (factoryMethod != null)
302                                return factoryMethod;
303                }
304                catch (Exception e) {
305                        // Ignore
306                }
307        }
308        
309        for (Namespace ns : init.getGlobalImports()) {
310                try {
311                        // Must use this hack to get the right name as all methods are private in Seam Namespace object
312                        Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class);
313                        m.setAccessible(true);
314                        String nsName = (String)Reflections.invoke(m, ns, componentName);
315                        factoryMethod = init.getFactory(nsName);
316                        if (factoryMethod != null)
317                                return factoryMethod;
318                }
319                catch (Exception e) {
320                        // Ignore
321                }
322        }
323        
324        return null;
325    }
326    
327    /**
328     * Implementation of factory expression lookup for Seam
329     * Uses a hack to manage imports and fully qualified names
330     * 
331     * @param componentName name of the factored component
332     * @return FactoryMethod object
333     */
334    static FactoryExpression lookupFactoryExpression(String componentName) {
335        Init init = Init.instance();
336        
337        FactoryExpression factoryExpression = init.getFactoryMethodExpression(componentName);
338        if (factoryExpression != null)
339                return factoryExpression;
340        factoryExpression = init.getFactoryValueExpression(componentName);
341        if (factoryExpression != null)
342                return factoryExpression;
343        
344        // Look for the factory expression in the imported namespaces (always give precedence to org.granite.tide overriden components)
345        for (Namespace ns : init.getGlobalImports()) {
346                try {
347                        // Must use this hack to get the right name as all methods are private in Seam Namespace object
348                        Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class);
349                        m.setAccessible(true);
350                        String nsName = (String)Reflections.invoke(m, ns, componentName);
351                        if (!nsName.startsWith("org.granite.tide"))
352                                continue;
353                        
354                factoryExpression = init.getFactoryMethodExpression(nsName);
355                if (factoryExpression != null)
356                        return factoryExpression;
357                factoryExpression = init.getFactoryValueExpression(nsName);
358                if (factoryExpression != null)
359                        return factoryExpression;
360                }
361                catch (Exception e) {
362                        // Ignore
363                }
364        }
365        for (Namespace ns : init.getGlobalImports()) {
366                try {
367                        // Must use this hack to get the right name as all methods are private in Seam Namespace object
368                        Method m = ns.getClass().getDeclaredMethod("qualifyName", String.class);
369                        m.setAccessible(true);
370                        String nsName = (String)Reflections.invoke(m, ns, componentName);
371                        
372                factoryExpression = init.getFactoryMethodExpression(nsName);
373                if (factoryExpression != null)
374                        return factoryExpression;
375                factoryExpression = init.getFactoryValueExpression(nsName);
376                if (factoryExpression != null)
377                        return factoryExpression;
378                }
379                catch (Exception e) {
380                        // Ignore
381                }
382        }
383        
384        return null;
385    }
386    
387    
388    private void instrumentComponent(Component component) {
389        if (component.getBeanClass().isAnnotationPresent(BypassTideInterceptor.class))
390                return;
391        
392        List<Interceptor> li = component.getInterceptors(InterceptorType.ANY);
393        
394        boolean newSortServer = false, newSortClient = false;
395                
396        boolean found = false;
397        for (Interceptor i : li) {
398            if (i.getUserInterceptorClass().equals(TideInterceptor.class)) {
399                found = true;
400                break;
401            }
402        }
403        if (!found) {
404            component.addInterceptor(new Interceptor(new TideInterceptor(), component));
405            newSortServer = true;
406        }
407        
408        if (component.beanClassHasAnnotation(DataEnabled.class) && component.getBeanClass().getAnnotation(DataEnabled.class).useInterceptor()) {
409                found = false;
410                for (Interceptor i : li) {
411                    if (i.getUserInterceptorClass().equals(TideDataPublishingInterceptor.class)) {
412                        found = true;
413                        break;
414                    }
415                }
416                if (!found) {
417                    component.addInterceptor(new Interceptor(new TideDataPublishingInterceptor(), component));
418                    newSortServer = true;
419                }
420        }
421 
422        // Check if AsyncPublisher installed
423        AsyncPublisher asyncPublisher = (AsyncPublisher)Component.getInstance("org.granite.tide.seam.async.publisher");
424        if (asyncPublisher != null) {
425            boolean async = false;
426            li = component.getClientSideInterceptors();
427            for (Iterator<Interceptor> i = li.iterator(); i.hasNext(); ) {
428                Interceptor in = i.next();
429                if (in.getUserInterceptorClass().equals(AsynchronousInterceptor.class)) {
430                    async = true;
431                    break;
432                }
433            }
434            if (async) {
435                component.addInterceptor(new Interceptor(new TideAsynchronousInterceptor(), component));
436                newSortClient = true;
437            }
438        }
439        
440        if (newSortServer || newSortClient) {
441            // Force correct sorting of interceptors: hack because newSort is private
442            try {
443                Method m = component.getClass().getDeclaredMethod("newSort", List.class);
444                m.setAccessible(true);
445                if (newSortServer)
446                    m.invoke(component, component.getInterceptors(InterceptorType.SERVER));
447                if (newSortClient)
448                    m.invoke(component, component.getInterceptors(InterceptorType.CLIENT));
449            }
450            catch (Exception e) {
451                // Ignore all
452            }
453        }
454    }
455}