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 }