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.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 }