001    /**
002     *   GRANITE DATA SERVICES
003     *   Copyright (C) 2006-2013 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     */
022    package org.granite.tide.seam;
023    
024    import static org.jboss.seam.annotations.Install.FRAMEWORK;
025    
026    import java.lang.reflect.Field;
027    import java.lang.reflect.Method;
028    import java.util.HashMap;
029    import java.util.HashSet;
030    import java.util.Iterator;
031    import java.util.List;
032    import java.util.Map;
033    import java.util.Set;
034    
035    import org.granite.tide.annotations.BypassTideInterceptor;
036    import org.granite.tide.async.AsyncPublisher;
037    import org.granite.tide.data.DataEnabled;
038    import org.granite.tide.seam.async.TideAsynchronousInterceptor;
039    import org.jboss.seam.Component;
040    import org.jboss.seam.Namespace;
041    import org.jboss.seam.ScopeType;
042    import org.jboss.seam.annotations.Install;
043    import org.jboss.seam.annotations.Logger;
044    import org.jboss.seam.annotations.Name;
045    import org.jboss.seam.annotations.Scope;
046    import org.jboss.seam.annotations.Startup;
047    import org.jboss.seam.annotations.intercept.BypassInterceptors;
048    import org.jboss.seam.annotations.intercept.InterceptorType;
049    import org.jboss.seam.async.AsynchronousInterceptor;
050    import org.jboss.seam.contexts.Context;
051    import org.jboss.seam.contexts.Contexts;
052    import org.jboss.seam.core.Init;
053    import org.jboss.seam.core.Init.FactoryExpression;
054    import org.jboss.seam.core.Init.FactoryMethod;
055    import org.jboss.seam.init.Initialization;
056    import org.jboss.seam.intercept.Interceptor;
057    import org.jboss.seam.log.Log;
058    import 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
073    public 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    }