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 }