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