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