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
021 package org.granite.tide;
022
023 import java.lang.reflect.Constructor;
024 import java.lang.reflect.InvocationTargetException;
025 import java.lang.reflect.Method;
026 import java.util.Map;
027 import java.util.Set;
028
029 import javax.servlet.http.HttpSession;
030
031 import org.granite.config.flex.Destination;
032 import org.granite.context.GraniteContext;
033 import org.granite.logging.Logger;
034 import org.granite.messaging.amf.io.convert.Converters;
035 import org.granite.messaging.service.ServiceException;
036 import org.granite.messaging.service.ServiceFactory;
037 import org.granite.messaging.service.ServiceInvocationContext;
038 import org.granite.messaging.service.ServiceInvoker;
039 import org.granite.messaging.service.security.SecurityServiceException;
040 import org.granite.messaging.webapp.HttpGraniteContext;
041 import org.granite.tide.data.DataContext;
042 import org.granite.tide.data.DataEnabled;
043 import org.granite.tide.data.DataMergeContext;
044 import org.granite.tide.data.DataEnabled.PublishMode;
045 import org.granite.tide.validators.EntityValidator;
046 import org.granite.tide.validators.InvalidValue;
047 import org.granite.util.ClassUtil;
048
049 import 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 */
058 public 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 = ClassUtil.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 = ClassUtil.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 = ClassUtil.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 = ClassUtil.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 = ClassUtil.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 }