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