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