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