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; 022 023import java.lang.reflect.Constructor; 024import java.lang.reflect.InvocationTargetException; 025import java.lang.reflect.Method; 026import java.util.Map; 027import java.util.Set; 028 029import javax.servlet.http.HttpSession; 030 031import org.granite.config.flex.Destination; 032import org.granite.context.GraniteContext; 033import org.granite.logging.Logger; 034import org.granite.messaging.amf.io.convert.Converters; 035import org.granite.messaging.service.ServiceException; 036import org.granite.messaging.service.ServiceFactory; 037import org.granite.messaging.service.ServiceInvocationContext; 038import org.granite.messaging.service.ServiceInvoker; 039import org.granite.messaging.service.security.SecurityServiceException; 040import org.granite.messaging.webapp.HttpGraniteContext; 041import org.granite.tide.data.DataContext; 042import org.granite.tide.data.DataEnabled; 043import org.granite.tide.data.DataMergeContext; 044import org.granite.tide.data.DataEnabled.PublishMode; 045import org.granite.tide.validators.EntityValidator; 046import org.granite.tide.validators.InvalidValue; 047import org.granite.util.TypeUtil; 048 049import flex.messaging.messages.RemotingMessage; 050 051 052/** 053 * Base class for Tide service invokers 054 * Adapts the Tide invocation model with Granite 055 * 056 * @author William DRAI 057 */ 058public class TideServiceInvoker<T extends ServiceFactory> extends ServiceInvoker<T> { 059 060 private static final Logger log = Logger.getLogger(TideServiceInvoker.class); 061 062 public static final String VALIDATOR_KEY = "org.granite.tide.validator.key"; 063 public static final String VALIDATOR_NOT_AVAILABLE = "org.granite.tide.validator.notAvailable"; 064 065 public static final String VALIDATOR_NAME = "validator-name"; 066 public static final String VALIDATOR_CLASS_NAME = "validator-class-name"; 067 068 /** 069 * Current tide context 070 */ 071 private TideServiceContext tideContext = null; 072 073 private transient EntityValidator validator = null; 074 075 076 public TideServiceInvoker(Destination destination, T factory) throws ServiceException { 077 super(destination, factory); 078 this.invokee = this; 079 this.tideContext = lookupContext(); 080 this.tideContext.initCall(); 081 initValidator(); 082 } 083 084 public TideServiceInvoker(Destination destination, T factory, TideServiceContext tideContext) throws ServiceException { 085 super(destination, factory); 086 this.invokee = this; 087 this.tideContext = tideContext; 088 this.tideContext.initCall(); 089 initValidator(); 090 } 091 092 093 public Object initializeObject(Object parent, String[] propertyNames) { 094 return tideContext.lazyInitialize(parent, propertyNames); 095 } 096 097 098 private static final InvalidValue[] EMPTY_INVALID_VALUES = new InvalidValue[0]; 099 100 protected void initValidator() { 101 Map<String, Object> applicationMap = GraniteContext.getCurrentInstance().getApplicationMap(); 102 Boolean validatorNotAvailable = (Boolean)applicationMap.get(VALIDATOR_NOT_AVAILABLE); 103 validator = (EntityValidator)applicationMap.get(VALIDATOR_KEY); 104 105 if (validator != null || Boolean.TRUE.equals(validatorNotAvailable)) 106 return; 107 108 String className = this.destination.getProperties().get(VALIDATOR_CLASS_NAME); 109 if (className != null) { 110 initValidatorWithClassName(className, null); 111 if (validator == null) { 112 log.warn("Validator class " + className + " not found: validation not enabled"); 113 applicationMap.put(VALIDATOR_NOT_AVAILABLE, Boolean.TRUE); 114 } 115 else { 116 log.info("Validator class " + className + " initialized"); 117 applicationMap.put(VALIDATOR_KEY, validator); 118 } 119 } 120 else { 121 String name = this.destination.getProperties().get(VALIDATOR_NAME); 122 if (name != null) { 123 try { 124 validator = (EntityValidator)tideContext.findComponent(name, EntityValidator.class); 125 } 126 catch (ServiceException e) { 127 name = null; 128 } 129 } 130 131 if (validator == null) { 132 className = "org.granite.tide.validation.BeanValidation"; 133 initValidatorWithClassName(className, "javax.validation.ValidatorFactory"); 134 } 135 136 if (validator == null) { 137 if (name != null) 138 log.warn("Validator component " + name + " not found: validation not enabled"); 139 else 140 log.warn("Validator class " + className + " not found: validation not enabled"); 141 142 applicationMap.put(VALIDATOR_NOT_AVAILABLE, Boolean.TRUE); 143 } 144 else { 145 log.info("Validator class " + validator.getClass().getName() + " initialized"); 146 applicationMap.put(VALIDATOR_KEY, validator); 147 } 148 } 149 } 150 151 private void initValidatorWithClassName(String className, String constructorArgClassName) { 152 try { 153 Object constructorArg = null; 154 Class<?> constructorArgClass = null; 155 if (constructorArgClassName != null) { 156 try { 157 constructorArgClass = TypeUtil.forName(constructorArgClassName); 158 constructorArg = tideContext.findComponent(null, constructorArgClass); 159 } 160 catch (Exception e) { 161 // Constructor arg not found 162 } 163 } 164 165 Class<?> validatorClass = Thread.currentThread().getContextClassLoader().loadClass(className); 166 try { 167 Constructor<?> c = validatorClass.getConstructor(constructorArgClass); 168 validator = (EntityValidator)c.newInstance(constructorArg); 169 } 170 catch (NoSuchMethodException e) { 171 validator = (EntityValidator)validatorClass.newInstance(); 172 } 173 catch (InvocationTargetException e) { 174 log.error(e, "Could not initialize Tide validator " + className + " with argument of type " + constructorArgClassName); 175 } 176 } 177 catch (ClassNotFoundException e) { 178 // Ignore: Hibernate Validator not present 179 } 180 catch (NoClassDefFoundError e) { 181 // Ignore: Hibernate Validator not present 182 } 183 catch (IllegalAccessException e) { 184 // Ignore: Hibernate Validator not present 185 } 186 catch (InstantiationException e) { 187 // Ignore: Hibernate Validator not present 188 } 189 } 190 191 192 public InvalidValue[] validateObject(Object entity, String propertyName, Object value) { 193 initValidator(); 194 if (entity != null && validator != null) 195 return validator.getPotentialInvalidValues(entity.getClass(), propertyName, value); 196 197 return EMPTY_INVALID_VALUES; 198 } 199 200 201 public void login() { 202 } 203 204 public void logout() { 205 HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance(); 206 HttpSession session = context.getSession(false); 207 if (session != null) 208 session.invalidate(); 209 } 210 211 public void resyncContext() { 212 } 213 214 215 protected TideServiceContext lookupContext() { 216 return null; 217 } 218 219 protected TideServiceContext getTideContext() { 220 return tideContext; 221 } 222 223 224 225 @Override 226 protected Object adjustInvokee(RemotingMessage request, String methodName, Object[] args) throws ServiceException { 227 if ("invokeComponent".equals(methodName)) { 228 String componentName = (String)args[0]; 229 String componentClassName = (String)args[1]; 230 Class<?> componentClass = null; 231 try { 232 if (componentClassName != null) 233 componentClass = TypeUtil.forName(componentClassName); 234 } 235 catch (ClassNotFoundException e) { 236 throw new ServiceException("Component class not found " + componentClassName, e); 237 } 238 log.debug("Setting invokee to %s", componentName); 239 240 Object instance = tideContext.findComponent(componentName, componentClass); 241 Set<Class<?>> componentClasses = instance != null ? tideContext.findComponentClasses(componentName, componentClass) : null; 242 243 GraniteContext context = GraniteContext.getCurrentInstance(); 244 if (instance != null && componentClasses != null && context.getGraniteConfig().isComponentTideEnabled(componentName, componentClasses, instance)) 245 return tideContext.adjustInvokee(instance, componentName, componentClasses); 246 247 if (instance != null) 248 log.error("SECURITY CHECK: Remote call refused to a non Tide-enabled component: " + componentName + "." + args[1] + ", class: " + componentClasses + ", instance: " + instance); 249 throw SecurityServiceException.newAccessDeniedException("Component [" + componentName + (componentClassName != null ? " of class " + componentClassName : "") + "] not found"); 250 } 251 252 return super.adjustInvokee(request, methodName, args); 253 } 254 255 256 @Override 257 protected Object[] beforeMethodSearch(Object invokee, String methodName, Object[] args) { 258 if ("invokeComponent".equals(methodName)) { 259 return tideContext.beforeMethodSearch(invokee, methodName, args); 260 } 261 else if ("initializeObject".equals(methodName)) { 262 return new Object[] { methodName, new Object[] { args[0], args[1] } }; 263 } 264 else if ("validateObject".equals(methodName)) { 265 return new Object[] { methodName, new Object[] { args[0], args[1], args[2] } }; 266 } 267 268 return new Object[] { methodName, new Object[] {} }; 269 } 270 271 272 private static final String DATAENABLED_HANDLED = "org.granite.tide.invoker.dataEnabled"; 273 274 @Override 275 protected void beforeInvocation(ServiceInvocationContext context) { 276 RemotingMessage message = (RemotingMessage)context.getMessage(); 277 GraniteContext graniteContext = GraniteContext.getCurrentInstance(); 278 279 Object[] originArgs = (Object[])message.getBody(); 280 IInvocationCall call = (IInvocationCall)originArgs[originArgs.length-1]; 281 282 String operation = message.getOperation(); 283 String componentName = "invokeComponent".equals(operation) ? (String)originArgs[0] : null; 284 String componentClassName = "invokeComponent".equals(operation) ? (String)originArgs[1] : null; 285 Class<?> componentClass = null; 286 try { 287 if (componentClassName != null) 288 componentClass = TypeUtil.forName(componentClassName); 289 } 290 catch (ClassNotFoundException e) { 291 throw new ServiceException("Component class not found " + componentClassName, e); 292 } 293 294 graniteContext.getRequestMap().put(TideServiceInvoker.class.getName(), this); 295 296 if (componentName != null || componentClass != null) { 297 Converters converters = graniteContext.getGraniteConfig().getConverters(); 298 299 Set<Class<?>> componentClasses = tideContext.findComponentClasses(componentName, componentClass); 300 for (Class<?> cClass : componentClasses) { 301 try { 302 Method m = cClass.getMethod(context.getMethod().getName(), context.getMethod().getParameterTypes()); 303 for (int i = 0; i < m.getGenericParameterTypes().length; i++) 304 context.getParameters()[i] = converters.convert(context.getParameters()[i], m.getGenericParameterTypes()[i]); 305 306 break; 307 } 308 catch (NoSuchMethodException e) { 309 } 310 } 311 312 for (Class<?> cClass : componentClasses) { 313 DataEnabled dataEnabled = cClass.getAnnotation(DataEnabled.class); 314 if (dataEnabled != null && !dataEnabled.useInterceptor()) { 315 GraniteContext.getCurrentInstance().getRequestMap().put(DATAENABLED_HANDLED, true); 316 DataContext.init(dataEnabled.topic(), dataEnabled.params(), dataEnabled.publish()); 317 prepareDataObserver(dataEnabled); 318 break; 319 } 320 } 321 } 322 323 Throwable error = null; 324 try { 325 tideContext.prepareCall(context, call, componentName, componentClass); 326 } 327 catch (ServiceException e) { 328 error = e; 329 } 330 catch (Throwable e) { 331 if (e instanceof InvocationTargetException) 332 error = ((InvocationTargetException)e).getTargetException(); 333 else 334 error = e; 335 } 336 finally { 337 if (error != null) 338 throw factory.getServiceExceptionHandler().handleInvocationException(context, error); 339 } 340 } 341 342 protected void prepareDataObserver(DataEnabled dataEnabled) { 343 DataContext.observe(); 344 } 345 346 347 @Override 348 protected Object afterInvocation(ServiceInvocationContext context, Object result) { 349 Object res = null; 350 351 String componentName = null; 352 Class<?> componentClass = null; 353 try { 354 Object[] originArgs = (Object[])context.getMessage().getBody(); 355 String operation = ((RemotingMessage)context.getMessage()).getOperation(); 356 componentName = "invokeComponent".equals(operation) ? (String)originArgs[0] : null; 357 String componentClassName = "invokeComponent".equals(operation) ? (String)originArgs[1] : null; 358 try { 359 if (componentClassName != null) 360 componentClass = TypeUtil.forName(componentClassName); 361 } 362 catch (ClassNotFoundException e) { 363 throw new ServiceException("Component class not found " + componentClassName, e); 364 } 365 } 366 finally { 367 Throwable error = null; 368 try { 369 res = tideContext.postCall(context, result, componentName, componentClass); 370 } 371 catch (ServiceException e) { 372 error = e; 373 } 374 catch (Throwable e) { 375 if (e instanceof InvocationTargetException) 376 error = ((InvocationTargetException)e).getTargetException(); 377 else 378 error = e; 379 } 380 finally { 381 if (error != null) 382 throw factory.getServiceExceptionHandler().handleInvocationException(context, error); 383 } 384 } 385 386 DataMergeContext.remove(); 387 388 // DataContext has been setup by beforeInvocation 389 if (GraniteContext.getCurrentInstance().getRequestMap().get(DATAENABLED_HANDLED) != null) 390 publishDataUpdates(); 391 392 DataContext.remove(); 393 394 return res; 395 } 396 397 protected void publishDataUpdates() { 398 DataContext.publish(PublishMode.ON_SUCCESS); 399 } 400 401 402 @Override 403 protected void afterInvocationError(ServiceInvocationContext context, Throwable error) { 404 String componentName = null; 405 Class<?> componentClass = null; 406 try { 407 Object[] originArgs = (Object[])context.getMessage().getBody(); 408 String operation = ((RemotingMessage)context.getMessage()).getOperation(); 409 componentName = "invokeComponent".equals(operation) ? (String)originArgs[0] : null; 410 String componentClassName = "invokeComponent".equals(operation) ? (String)originArgs[1] : null; 411 try { 412 if (componentClassName != null) 413 componentClass = TypeUtil.forName(componentClassName); 414 } 415 catch (ClassNotFoundException e) { 416 throw new ServiceException("Component class not found " + componentClassName, e); 417 } 418 } 419 finally { 420 tideContext.postCallFault(context, error, componentName, componentClass); 421 } 422 423 DataMergeContext.remove(); 424 DataContext.remove(); 425 } 426}