3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
11 package eu
.etaxonomy
.cdm
.remote
.controller
;
13 import java
.beans
.PropertyDescriptor
;
14 import java
.io
.IOException
;
15 import java
.lang
.reflect
.InvocationTargetException
;
16 import java
.lang
.reflect
.Method
;
17 import java
.lang
.reflect
.ParameterizedType
;
18 import java
.lang
.reflect
.Type
;
19 import java
.util
.ArrayList
;
20 import java
.util
.Arrays
;
21 import java
.util
.Collection
;
22 import java
.util
.List
;
23 import java
.util
.UUID
;
25 import javax
.servlet
.http
.HttpServletRequest
;
26 import javax
.servlet
.http
.HttpServletResponse
;
28 import org
.apache
.commons
.beanutils
.PropertyUtils
;
29 import org
.apache
.commons
.io
.FilenameUtils
;
30 import org
.apache
.commons
.lang
.StringUtils
;
31 import org
.hibernate
.mapping
.Map
;
32 import org
.springframework
.web
.bind
.WebDataBinder
;
33 import org
.springframework
.web
.bind
.annotation
.InitBinder
;
34 import org
.springframework
.web
.bind
.annotation
.PathVariable
;
35 import org
.springframework
.web
.bind
.annotation
.RequestMapping
;
36 import org
.springframework
.web
.bind
.annotation
.RequestMethod
;
37 import org
.springframework
.web
.bind
.annotation
.RequestParam
;
38 import org
.springframework
.web
.servlet
.ModelAndView
;
40 import eu
.etaxonomy
.cdm
.api
.service
.IService
;
41 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
42 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
43 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
44 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
45 import eu
.etaxonomy
.cdm
.model
.reference
.INomenclaturalReference
;
46 import eu
.etaxonomy
.cdm
.remote
.controller
.util
.PagerParameters
;
47 import eu
.etaxonomy
.cdm
.remote
.editor
.UUIDPropertyEditor
;
48 import eu
.etaxonomy
.cdm
.remote
.exception
.NoRecordsMatchException
;
51 * based on org.cateproject.controller.common
53 * @author a.kohlbecker
59 public abstract class BaseController
<T
extends CdmBase
, SERVICE
extends IService
<T
>> extends AbstractController
<T
, SERVICE
> {
61 /* protected SERVICE service;
63 public abstract void setService(SERVICE service);*/
65 protected Class
<T
> baseClass
;
67 public BaseController (){
69 Type superClass
= this.getClass().getGenericSuperclass();
70 if(superClass
instanceof ParameterizedType
){
71 ParameterizedType parametrizedSuperClass
= (ParameterizedType
) superClass
;
72 Type
[] typeArguments
= parametrizedSuperClass
.getActualTypeArguments();
74 if(typeArguments
.length
> 1 && typeArguments
[0] instanceof Class
<?
>){
75 baseClass
= (Class
<T
>) typeArguments
[0];
77 logger
.error("unable to find baseClass");
83 public void initBinder(WebDataBinder binder
) {
84 binder
.registerCustomEditor(UUID
.class, new UUIDPropertyEditor());
87 //TODO implement bulk version of this method
88 @RequestMapping(method
= RequestMethod
.GET
)
89 public T
doGet(@PathVariable("uuid") UUID uuid
,
90 HttpServletRequest request
,
91 HttpServletResponse response
) throws IOException
{
93 logger
.info("doGet() " + request
.getRequestURI());
95 T obj
= getCdmBaseInstance(uuid
, response
, initializationStrategy
);
104 * @throws IOException
106 * TODO implement bulk version of this method
108 @RequestMapping(value
= "*", method
= RequestMethod
.GET
)
109 public ModelAndView
doGetMethod(
110 @PathVariable("uuid") UUID uuid
,
111 // doPage request parametes
112 @RequestParam(value
= "pageNumber", required
= false) Integer pageNumber
,
113 @RequestParam(value
= "pageSize", required
= false) Integer pageSize
,
114 // doList request parametes
115 @RequestParam(value
= "start", required
= false) Integer start
,
116 @RequestParam(value
= "limit", required
= false) Integer limit
,
117 HttpServletRequest request
,
118 HttpServletResponse response
) throws IOException
{
120 ModelAndView modelAndView
= new ModelAndView();
122 String servletPath
= request
.getServletPath();
123 String baseName
= FilenameUtils
.getBaseName(servletPath
);
125 if(request
!= null) {
126 logger
.info("doGetMethod()[doGet" + StringUtils
.capitalize(baseName
) + "] " + request
.getRequestURI());
130 // T instance = getCdmBaseInstance(uuid, response, (List<String>)null);
132 //Class<?> propertyClass = propertyClass(instance, baseName);
134 Object objectFromProperty
= getCdmBaseProperty(uuid
, baseName
, response
);// invokeProperty(instance, baseName, response);
138 if(objectFromProperty
!= null){
140 if( Collection
.class.isAssignableFrom(objectFromProperty
.getClass())){
141 // Map types cannot be returned as list or in a pager!
143 Collection c
= (Collection
)objectFromProperty
;
146 limit
= (limit
== null ? DEFAULT_PAGE_SIZE
: limit
);
147 Collection sub_c
= subCollection(c
, start
, limit
);
148 modelAndView
.addObject(sub_c
);
151 //FIXME use real paging mechanism of according service class instead of subCollection()
152 //FIXME use BaseListController.normalizeAndValidatePagerParameters(pageNumber, pageSize, response);
153 PagerParameters pagerParameters
= new PagerParameters(pageSize
, pageNumber
);
154 pagerParameters
.normalizeAndValidate(response
);
156 start
= pagerParameters
.getPageIndex() * pagerParameters
.getPageSize();
157 List sub_c
= subCollection(c
, start
, pagerParameters
.getPageSize());
158 Pager p
= new DefaultPagerImpl(pageNumber
, c
.size(), pagerParameters
.getPageSize(), sub_c
);
159 modelAndView
.addObject(p
);
163 modelAndView
.addObject(objectFromProperty
);
168 if(modelAndView
.isEmpty()){
176 public Object
getCdmBaseProperty(UUID uuid
, String property
, HttpServletResponse response
) throws IOException
{
178 T instance
= (T
) HibernateProxyHelper
.deproxy(getCdmBaseInstance(uuid
, response
, property
));
180 Object objectFromProperty
= invokeProperty(instance
, property
, response
);
182 return objectFromProperty
;
185 private Class
<?
> propertyClass(T instance
, String baseName
) {
186 PropertyDescriptor propertyDescriptor
= null;
189 propertyDescriptor
= PropertyUtils
.getPropertyDescriptor(instance
, baseName
);
190 if(propertyDescriptor
!= null){
191 c
= propertyDescriptor
.getClass();
193 } catch (IllegalAccessException e
) {
194 // TODO Auto-generated catch block
196 } catch (InvocationTargetException e
) {
197 // TODO Auto-generated catch block
199 } catch (NoSuchMethodException e
) {
200 // TODO Auto-generated catch block
211 * @param pathProperties
213 * @throws IOException
215 @SuppressWarnings("unchecked")
216 protected final <SUB_T
extends T
> SUB_T
getCdmBaseInstance(Class
<SUB_T
> clazz
, UUID uuid
, HttpServletResponse response
, List
<String
> pathProperties
)
219 CdmBase cdmBaseObject
= getCdmBaseInstance(uuid
, response
, pathProperties
);
220 if(!clazz
.isAssignableFrom(cdmBaseObject
.getClass())){
221 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
223 return (SUB_T
) cdmBaseObject
;
231 * @param pathProperty
233 * @throws IOException
235 @SuppressWarnings("unchecked")
236 protected final <SUB_T
extends T
> SUB_T
getCdmBaseInstance(Class
<SUB_T
> clazz
, UUID uuid
, HttpServletResponse response
, String pathProperty
)
239 CdmBase cdmBaseObject
= getCdmBaseInstance(uuid
, response
, pathProperty
);
240 if(!clazz
.isAssignableFrom(cdmBaseObject
.getClass())){
241 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
243 return (SUB_T
) cdmBaseObject
;
249 * @param pathProperty
251 * @throws IOException
253 protected final T
getCdmBaseInstance(UUID uuid
, HttpServletResponse response
, String pathProperty
)
255 return getCdmBaseInstance(baseClass
, uuid
, response
, Arrays
256 .asList(new String
[] { pathProperty
}));
263 * @param pathProperties
265 * @throws IOException
267 protected final T
getCdmBaseInstance(UUID uuid
, HttpServletResponse response
, List
<String
> pathProperties
)
269 return getCdmBaseInstance(baseClass
, service
, uuid
, response
, pathProperties
);
278 * @param pathProperties
280 * @throws IOException
282 protected final <CDM_BASE
extends CdmBase
> CDM_BASE
getCdmBaseInstance(Class
<CDM_BASE
> clazz
, IService
<CDM_BASE
> service
, UUID uuid
, HttpServletResponse response
, List
<String
> pathProperties
)
285 CDM_BASE cdmBaseObject
= service
.load(uuid
, pathProperties
);
286 if (cdmBaseObject
== null) {
287 HttpStatusMessage
.UUID_NOT_FOUND
.send(response
);
288 throw new NoRecordsMatchException("No instance found for UUID " + uuid
.toString());
290 return cdmBaseObject
;
298 * @throws IOException
300 private final Object
invokeProperty(T instance
,
301 String baseName
, HttpServletResponse response
) throws IOException
{
303 Object result
= null;
305 PropertyDescriptor propertyDescriptor
= PropertyUtils
.getPropertyDescriptor(instance
, baseName
);
306 if(propertyDescriptor
== null){
307 throw new NoSuchMethodException("No such method: " + instance
.getClass().getSimpleName() + ".get" + baseName
);
309 Method method
= propertyDescriptor
.getReadMethod();
311 Class
<?
> returnType
= method
.getReturnType();
313 if(CdmBase
.class.isAssignableFrom(returnType
)
314 || Collection
.class.isAssignableFrom(returnType
)
315 || Map
.class.isAssignableFrom(returnType
)
316 || INomenclaturalReference
.class.isAssignableFrom(returnType
)){
318 result
= method
.invoke(instance
, (Object
[])null);
320 result
= HibernateProxyHelper
.deproxy(result
);
323 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
325 } catch (SecurityException e
) {
326 logger
.error("SecurityException: ", e
);
327 HttpStatusMessage
.INTERNAL_ERROR
.send(response
);
328 } catch (NoSuchMethodException e
) {
329 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
330 } catch (IllegalArgumentException e
) {
331 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
332 } catch (IllegalAccessException e
) {
333 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
334 } catch (InvocationTargetException e
) {
335 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
340 private <E
> List
<E
> subCollection(Collection
<?
extends E
> c
, Integer start
, Integer length
){
341 List
<E
> sub_c
= new ArrayList
<E
>(length
);
342 if(c
.size() > length
){
343 E
[] a
= (E
[]) c
.toArray();
344 for(int i
= start
; i
< start
+ length
; i
++){
357 private Validator validator;
359 private javax.validation.Validator javaxValidator;
361 @RequestMapping(method = RequestMethod.PUT, headers="content-type=multipart/form-data")
362 public T doPutForm(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
363 object.setUuid(uuid);
364 validator.validate(object, result);
365 if (result.hasErrors()) {
367 // set http status code depending upon what happened, possibly return
368 // the put object and errors so that they can be rendered into a suitable error response
370 // requires merging detached object ?gilead?
371 service.save(object);
377 @RequestMapping(method = RequestMethod.PUT, headers="content-type=text/json")
378 public T doPutJSON(@PathVariable(value = "uuid") UUID uuid, @RequestBody String jsonMessage) {
379 JSONObject jsonObject = JSONObject.fromObject(jsonMessage);
380 T object = (T)JSONObject.toBean(jsonObject, this.getClass());
383 Set<ConstraintViolation<T>> constraintViolations = javaxValidator.validate(object);
384 if (!constraintViolations.isEmpty()) {
386 // set http status code depending upon what happened, possibly return
387 // the put object and errors so that they can be rendered into a suitable error response
389 // requires merging detached object ?gilead?
390 service.save(object);
396 @RequestMapping(method = RequestMethod.PUT) // the cdm-server may not allow clients to specify the uuid for resources
397 public T doPut(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
398 validator.validate(object, result);
399 if (result.hasErrors()) {
400 // set http status code depending upon what happened, possibly return
401 // the put object and errors so that they can be rendered into a suitable error response
403 service.save(object);
407 @RequestMapping(method = RequestMethod.DELETE)
408 public void doDelete(@PathVariable(value = "uuid") UUID uuid) {
409 T object = service.find(uuid);
410 // provided the object exists
411 service.delete(uuid);
412 // might return 204 or 200