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