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.spring;
022
023 import java.lang.reflect.Method;
024 import java.util.ArrayList;
025 import java.util.Enumeration;
026 import java.util.HashMap;
027 import java.util.Hashtable;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.Set;
031
032 import javax.servlet.ServletRequest;
033 import javax.servlet.http.HttpServletRequest;
034 import javax.servlet.http.HttpServletRequestWrapper;
035 import javax.servlet.http.HttpServletResponse;
036
037 import org.granite.context.GraniteContext;
038 import org.granite.logging.Logger;
039 import org.granite.messaging.amf.io.convert.Converter;
040 import org.granite.messaging.amf.io.util.ClassGetter;
041 import org.granite.messaging.service.ServiceException;
042 import org.granite.messaging.service.ServiceInvocationContext;
043 import org.granite.messaging.webapp.HttpGraniteContext;
044 import org.granite.tide.IInvocationCall;
045 import org.granite.tide.IInvocationResult;
046 import org.granite.tide.TidePersistenceManager;
047 import org.granite.tide.annotations.BypassTideMerge;
048 import org.granite.tide.data.DataContext;
049 import org.granite.tide.invocation.ContextUpdate;
050 import org.granite.tide.invocation.InvocationCall;
051 import org.granite.tide.invocation.InvocationResult;
052 import org.granite.util.ClassUtil;
053 import org.springframework.beans.TypeMismatchException;
054 import org.springframework.context.ApplicationContext;
055 import org.springframework.core.MethodParameter;
056 import org.springframework.web.bind.ServletRequestDataBinder;
057 import org.springframework.web.context.request.WebRequestInterceptor;
058 import org.springframework.web.servlet.HandlerAdapter;
059 import org.springframework.web.servlet.HandlerInterceptor;
060 import org.springframework.web.servlet.ModelAndView;
061 import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
062 import org.springframework.web.servlet.mvc.Controller;
063 import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
064 import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
065
066
067 /**
068 * @author William DRAI
069 */
070 public class SpringMVCServiceContext extends SpringServiceContext {
071
072 private static final long serialVersionUID = 1L;
073
074 private static final String REQUEST_VALUE = "__REQUEST_VALUE__";
075
076 private static final Logger log = Logger.getLogger(SpringMVCServiceContext.class);
077
078
079 public SpringMVCServiceContext() throws ServiceException {
080 super();
081 }
082
083 public SpringMVCServiceContext(ApplicationContext springContext) throws ServiceException {
084 super(springContext);
085 }
086
087
088 @Override
089 public Object adjustInvokee(Object instance, String componentName, Set<Class<?>> componentClasses) {
090 for (Class<?> componentClass : componentClasses) {
091 if (componentClass.isAnnotationPresent(org.springframework.stereotype.Controller.class)) {
092 return new AnnotationMethodHandlerAdapter() {
093 @Override
094 protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName) throws Exception {
095 return new ControllerRequestDataBinder(request, target, objectName);
096 }
097 };
098 }
099 }
100 if (Controller.class.isInstance(instance) || (componentName != null && componentName.endsWith("Controller")))
101 return new SimpleControllerHandlerAdapter();
102
103 return instance;
104 }
105
106
107 private static final String SPRINGMVC_BINDING_ATTR = "__SPRINGMVC_LOCAL_BINDING__";
108
109 @Override
110 public Object[] beforeMethodSearch(Object instance, String methodName, Object[] args) {
111 if (instance instanceof HandlerAdapter) {
112 boolean grails = getSpringContext().getClass().getName().indexOf("Grails") > 0;
113
114 String componentName = (String)args[0];
115 String componentClassName = (String)args[1];
116 Class<?> componentClass = null;
117 try {
118 if (componentClassName != null)
119 componentClass = ClassUtil.forName(componentClassName);
120 }
121 catch (ClassNotFoundException e) {
122 throw new ServiceException("Component class not found " + componentClassName, e);
123 }
124 Object component = findComponent(componentName, componentClass);
125 Set<Class<?>> componentClasses = findComponentClasses(componentName, componentClass);
126 Object handler = component;
127 if (grails && componentName.endsWith("Controller")) {
128 // Special handling for Grails controllers
129 handler = springContext.getBean("mainSimpleController");
130 }
131 HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
132 @SuppressWarnings("unchecked")
133 Map<String, Object> requestMap = (args[3] != null && args[3] instanceof Object[] && ((Object[])args[3]).length >= 1 && ((Object[])args[3]).length <= 2 && ((Object[])args[3])[0] instanceof Map)
134 ? (Map<String, Object>)((Object[])args[3])[0]
135 : null;
136 boolean localBinding = false;
137 if (args[3] != null && args[3] instanceof Object[] && ((Object[])args[3]).length == 2
138 && ((Object[])args[3])[0] instanceof Map<?, ?> && ((Object[])args[3])[1] instanceof Boolean)
139 localBinding = (Boolean)((Object[])args[3])[1];
140 context.getRequestMap().put(SPRINGMVC_BINDING_ATTR, localBinding);
141
142 Map<String, Object> valueMap = null;
143 if (args[4] instanceof InvocationCall) {
144 valueMap = new HashMap<String, Object>();
145 for (ContextUpdate u : ((InvocationCall)args[4]).getUpdates())
146 valueMap.put(u.getComponentName() + (u.getExpression() != null ? "." + u.getExpression() : ""), u.getValue());
147 }
148
149 if (grails) {
150 // Special handling for Grails controllers
151 try {
152 for (Class<?> cClass : componentClasses) {
153 if (cClass.isInterface())
154 continue;
155 Method m = cClass.getDeclaredMethod("getProperty", String.class);
156 @SuppressWarnings("unchecked")
157 Map<String, Object> map = (Map<String, Object>)m.invoke(component, "params");
158 if (requestMap != null)
159 map.putAll(requestMap);
160 if (valueMap != null)
161 map.putAll(valueMap);
162 }
163 }
164 catch (Exception e) {
165 // Ignore, probably not a Grails controller
166 }
167 }
168 ControllerRequestWrapper rw = new ControllerRequestWrapper(grails, context.getRequest(), componentName, (String)args[2], requestMap, valueMap);
169 return new Object[] { "handle", new Object[] { rw, context.getResponse(), handler }};
170 }
171
172 return super.beforeMethodSearch(instance, methodName, args);
173 }
174
175
176 @Override
177 public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) {
178 super.prepareCall(context, c, componentName, componentClass);
179
180 if (componentName == null)
181 return;
182
183 Object component = findComponent(componentName, componentClass);
184
185 if (context.getBean() instanceof HandlerAdapter) {
186 // In case of Spring controllers, call interceptors
187 ApplicationContext webContext = getSpringContext();
188 String[] interceptorNames = webContext.getBeanNamesForType(HandlerInterceptor.class);
189 String[] webRequestInterceptors = webContext.getBeanNamesForType(WebRequestInterceptor.class);
190 HandlerInterceptor[] interceptors = new HandlerInterceptor[interceptorNames.length+webRequestInterceptors.length];
191
192 int j = 0;
193 for (int i = 0; i < webRequestInterceptors.length; i++)
194 interceptors[j++] = new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor)webContext.getBean(webRequestInterceptors[i]));
195 for (int i = 0; i < interceptorNames.length; i++)
196 interceptors[j++] = (HandlerInterceptor)webContext.getBean(interceptorNames[i]);
197
198 HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
199
200 graniteContext.getRequestMap().put(HandlerInterceptor.class.getName(), interceptors);
201
202 try {
203 for (int i = 0; i < interceptors.length; i++) {
204 HandlerInterceptor interceptor = interceptors[i];
205 interceptor.preHandle((HttpServletRequest)context.getParameters()[0], graniteContext.getResponse(), component);
206 }
207 }
208 catch (Exception e) {
209 throw new ServiceException(e.getMessage(), e);
210 }
211 }
212 }
213
214
215 @Override
216 @SuppressWarnings("unchecked")
217 public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) {
218 List<ContextUpdate> results = null;
219
220 Object component = null;
221 if (componentName != null && context.getBean() instanceof HandlerAdapter) {
222 component = findComponent(componentName, componentClass);
223
224 HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
225
226 Map<String, Object> modelMap = null;
227 if (result instanceof ModelAndView) {
228 ModelAndView modelAndView = (ModelAndView)result;
229 modelMap = modelAndView.getModel();
230 result = modelAndView.getViewName();
231
232 if (context.getBean() instanceof HandlerAdapter) {
233 try {
234 HandlerInterceptor[] interceptors = (HandlerInterceptor[])graniteContext.getRequestMap().get(HandlerInterceptor.class.getName());
235
236 if (interceptors != null) {
237 for (int i = interceptors.length-1; i >= 0; i--) {
238 HandlerInterceptor interceptor = interceptors[i];
239 interceptor.postHandle((HttpServletRequest)context.getParameters()[0], graniteContext.getResponse(), component, modelAndView);
240 }
241
242 triggerAfterCompletion(component, interceptors.length-1, interceptors, graniteContext.getRequest(), graniteContext.getResponse(), null);
243 }
244 }
245 catch (Exception e) {
246 throw new ServiceException(e.getMessage(), e);
247 }
248 }
249 }
250
251 if (modelMap != null) {
252 Boolean localBinding = (Boolean)graniteContext.getRequestMap().get(SPRINGMVC_BINDING_ATTR);
253
254 results = new ArrayList<ContextUpdate>();
255 for (Map.Entry<String, Object> me : modelMap.entrySet()) {
256 if (me.getKey().toString().startsWith("org.springframework.validation.")
257 || (me.getValue() != null && (
258 me.getValue().getClass().getName().startsWith("groovy.lang.ExpandoMetaClass")
259 || me.getValue().getClass().getName().indexOf("$_closure") > 0
260 || me.getValue() instanceof Class)))
261 continue;
262 String variableName = me.getKey().toString();
263 if (Boolean.TRUE.equals(localBinding))
264 results.add(new ContextUpdate(componentName, variableName, me.getValue(), 3, false));
265 else
266 results.add(new ContextUpdate(variableName, null, me.getValue(), 3, false));
267 }
268 }
269
270 boolean grails = getSpringContext().getClass().getName().indexOf("Grails") > 0;
271 if (grails) {
272 // Special handling for Grails controllers: get flash content
273 try {
274 Set<Class<?>> componentClasses = findComponentClasses(componentName, componentClass);
275 for (Class<?> cClass : componentClasses) {
276 if (cClass.isInterface())
277 continue;
278 Method m = cClass.getDeclaredMethod("getProperty", String.class);
279 Map<String, Object> map = (Map<String, Object>)m.invoke(component, "flash");
280 if (results == null)
281 results = new ArrayList<ContextUpdate>();
282 for (Map.Entry<String, Object> me : map.entrySet()) {
283 Object value = me.getValue();
284 if (value != null && value.getClass().getName().startsWith("org.codehaus.groovy.runtime.GString"))
285 value = value.toString();
286 results.add(new ContextUpdate("flash", me.getKey(), value, 3, false));
287 }
288 }
289 }
290 catch (Exception e) {
291 throw new ServiceException("Flash scope retrieval failed", e);
292 }
293 }
294 }
295
296 DataContext dataContext = DataContext.get();
297 Object[][] updates = dataContext != null ? dataContext.getUpdates() : null;
298
299 InvocationResult ires = new InvocationResult(result, results);
300 if (component == null)
301 component = context.getBean();
302 if (component.getClass().isAnnotationPresent(BypassTideMerge.class))
303 ires.setMerge(false);
304 else if (!(context.getParameters().length > 0 && context.getParameters()[0] instanceof ControllerRequestWrapper)) {
305 try {
306 Method m = component.getClass().getMethod(context.getMethod().getName(), context.getMethod().getParameterTypes());
307 if (m.isAnnotationPresent(BypassTideMerge.class))
308 ires.setMerge(false);
309 }
310 catch (Exception e) {
311 log.warn("Could not find bean method", e);
312 }
313 }
314
315 ires.setUpdates(updates);
316
317 return ires;
318 }
319
320 @Override
321 public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
322 if (componentName != null && context.getBean() instanceof HandlerAdapter) {
323 HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
324
325 Object component = findComponent(componentName, componentClass);
326
327 HandlerInterceptor[] interceptors = (HandlerInterceptor[])graniteContext.getRequestMap().get(HandlerInterceptor.class.getName());
328
329 triggerAfterCompletion(component, interceptors.length-1, interceptors,
330 graniteContext.getRequest(), graniteContext.getResponse(),
331 t instanceof Exception ? (Exception)t : null);
332 }
333
334 super.postCallFault(context, t, componentName, componentClass);
335 }
336
337
338 private void triggerAfterCompletion(Object component, int interceptorIndex, HandlerInterceptor[] interceptors, HttpServletRequest request, HttpServletResponse response, Exception ex) {
339 for (int i = interceptorIndex; i >= 0; i--) {
340 HandlerInterceptor interceptor = interceptors[i];
341 try {
342 interceptor.afterCompletion(request, response, component, ex);
343 }
344 catch (Throwable ex2) {
345 log.error("HandlerInterceptor.afterCompletion threw exception", ex2);
346 }
347 }
348 }
349
350
351
352 private class ControllerRequestWrapper extends HttpServletRequestWrapper {
353 private String componentName = null;
354 private String methodName = null;
355 private Map<String, Object> requestMap = null;
356 private Map<String, Object> valueMap = null;
357
358 public ControllerRequestWrapper(boolean grails, HttpServletRequest request, String componentName, String methodName, Map<String, Object> requestMap, Map<String, Object> valueMap) {
359 super(request);
360 this.componentName = componentName.substring(0, componentName.length()-"Controller".length());
361 if (this.componentName.indexOf(".") > 0)
362 this.componentName = this.componentName.substring(this.componentName.lastIndexOf(".")+1);
363 if (grails)
364 this.componentName = this.componentName.substring(0, 1).toLowerCase() + this.componentName.substring(1);
365 this.methodName = methodName;
366 this.requestMap = requestMap;
367 this.valueMap = valueMap;
368 }
369
370 @Override
371 public String getRequestURI() {
372 return getContextPath() + "/" + componentName + "/" + methodName;
373 }
374
375 @Override
376 public String getServletPath() {
377 return "/" + componentName + "/" + methodName;
378 }
379
380 public Object getRequestValue(String key) {
381 return requestMap != null ? requestMap.get(key) : null;
382 }
383
384 public Object getBindValue(String key) {
385 return valueMap != null ? valueMap.get(key) : null;
386 }
387
388 @Override
389 public String getParameter(String name) {
390 return requestMap != null && requestMap.containsKey(name) ? REQUEST_VALUE : null;
391 }
392
393 @Override
394 public String[] getParameterValues(String name) {
395 return requestMap != null && requestMap.containsKey(name) ? new String[] { REQUEST_VALUE } : null;
396 }
397
398 @Override
399 @SuppressWarnings({ "unchecked", "rawtypes" })
400 public Map getParameterMap() {
401 Map<String, Object> pmap = new HashMap<String, Object>();
402 if (requestMap != null) {
403 for (String name : requestMap.keySet())
404 pmap.put(name, REQUEST_VALUE);
405 }
406 return pmap;
407 }
408
409 @Override
410 @SuppressWarnings({ "unchecked", "rawtypes" })
411 public Enumeration getParameterNames() {
412 Hashtable ht = new Hashtable();
413 if (requestMap != null)
414 ht.putAll(requestMap);
415 return ht.keys();
416 }
417 }
418
419
420 private class ControllerRequestDataBinder extends ServletRequestDataBinder {
421
422 private ControllerRequestWrapper wrapper = null;
423 private Object target = null;
424
425 public ControllerRequestDataBinder(ServletRequest request, Object target, String objectName) {
426 super(target, objectName);
427 this.wrapper = (ControllerRequestWrapper)request;
428 this.target = target;
429 }
430
431 private Object getBindValue(boolean request, Class<?> requiredType) {
432 GraniteContext context = GraniteContext.getCurrentInstance();
433 TidePersistenceManager pm = SpringMVCServiceContext.this.getTidePersistenceManager(true);
434 ClassGetter classGetter = context.getGraniteConfig().getClassGetter();
435 Object value = request ? wrapper.getRequestValue(getObjectName()) : wrapper.getBindValue(getObjectName());
436 if (requiredType != null) {
437 Converter converter = context.getGraniteConfig().getConverters().getConverter(value, requiredType);
438 if (converter != null)
439 value = converter.convert(value, requiredType);
440 }
441 if (value != null && !request)
442 return SpringMVCServiceContext.this.mergeExternal(pm, classGetter, value, null, null, null);
443 return value;
444 }
445
446 @Override
447 public void bind(ServletRequest request) {
448 Object value = getBindValue(false, null);
449 if (value != null)
450 target = value;
451 }
452
453 @Override
454 public Object getTarget() {
455 return target;
456 }
457
458 @SuppressWarnings({ "rawtypes", "unchecked" })
459 @Override
460 public Object convertIfNecessary(Object value, Class requiredType, MethodParameter methodParam) throws TypeMismatchException {
461 if (target == null && value == REQUEST_VALUE || (value instanceof String[] && ((String[])value)[0] == REQUEST_VALUE))
462 return getBindValue(true, requiredType);
463 return super.convertIfNecessary(value, requiredType, methodParam);
464 }
465 }
466 }