2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.remote
.controller
;
12 import java
.beans
.PropertyDescriptor
;
13 import java
.io
.IOException
;
14 import java
.lang
.reflect
.InvocationTargetException
;
15 import java
.lang
.reflect
.Method
;
16 import java
.lang
.reflect
.ParameterizedType
;
17 import java
.lang
.reflect
.Type
;
18 import java
.util
.ArrayList
;
19 import java
.util
.Arrays
;
20 import java
.util
.Collection
;
21 import java
.util
.Comparator
;
22 import java
.util
.List
;
24 import java
.util
.UUID
;
26 import javax
.servlet
.http
.HttpServletRequest
;
27 import javax
.servlet
.http
.HttpServletResponse
;
29 import org
.apache
.commons
.beanutils
.PropertyUtils
;
30 import org
.apache
.commons
.io
.FilenameUtils
;
31 import org
.apache
.commons
.lang
.StringUtils
;
32 import org
.hibernate
.mapping
.Map
;
33 import org
.springframework
.web
.bind
.WebDataBinder
;
34 import org
.springframework
.web
.bind
.annotation
.InitBinder
;
35 import org
.springframework
.web
.bind
.annotation
.PathVariable
;
36 import org
.springframework
.web
.bind
.annotation
.RequestMapping
;
37 import org
.springframework
.web
.bind
.annotation
.RequestMethod
;
38 import org
.springframework
.web
.bind
.annotation
.RequestParam
;
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
;
50 * based on org.cateproject.controller.common
52 * @author a.kohlbecker
58 public abstract class BaseController
<T
extends CdmBase
, SERVICE
extends IService
<T
>> extends AbstractController
<T
, SERVICE
> {
60 /* protected SERVICE service;
62 public abstract void setService(SERVICE service);*/
64 protected Class
<T
> baseClass
;
66 @SuppressWarnings("unchecked")
67 public BaseController (){
69 Type superClass
= this.getClass().getGenericSuperclass();
71 if(superClass
instanceof ParameterizedType
){
72 ParameterizedType parametrizedSuperClass
= (ParameterizedType
) superClass
;
73 Type
[] typeArguments
= parametrizedSuperClass
.getActualTypeArguments();
75 if(typeArguments
.length
> 1 && typeArguments
[0] instanceof Class
<?
>){
76 baseClass
= (Class
<T
>) typeArguments
[0];
78 logger
.error("unable to find baseClass");
81 } else if(superClass
instanceof Class
<?
>){
82 superClass
= ((Class
<?
>) superClass
).getGenericSuperclass();
84 // no point digging deeper if neither Class or ParameterizedType
85 logger
.error("unable to find baseClass");
92 public void initBinder(WebDataBinder binder
) {
93 binder
.registerCustomEditor(UUID
.class, new UUIDPropertyEditor());
96 //TODO implement bulk version of this method
97 @RequestMapping(method
= RequestMethod
.GET
)
98 public T
doGet(@PathVariable("uuid") UUID uuid
,
99 HttpServletRequest request
,
100 HttpServletResponse response
) throws IOException
{
101 if(request
!= null) {
102 logger
.info("doGet() " + request
.getRequestURI());
104 T obj
= getCdmBaseInstance(uuid
, response
, initializationStrategy
);
113 * @throws IOException
115 * TODO implement bulk version of this method
117 @RequestMapping(value
= "*", method
= RequestMethod
.GET
)
118 public Object
doGetMethod(
119 @PathVariable("uuid") UUID uuid
,
120 // doPage request parametes
121 @RequestParam(value
= "pageNumber", required
= false) Integer pageNumber
,
122 @RequestParam(value
= "pageSize", required
= false) Integer pageSize
,
123 // doList request parametes
124 @RequestParam(value
= "start", required
= false) Integer start
,
125 @RequestParam(value
= "limit", required
= false) Integer limit
,
126 HttpServletRequest request
,
127 HttpServletResponse response
) throws IOException
{
129 String servletPath
= request
.getServletPath();
130 String baseName
= FilenameUtils
.getBaseName(servletPath
);
132 if(request
!= null) {
133 logger
.info("doGetMethod()[doGet" + StringUtils
.capitalize(baseName
) + "] " + requestPathAndQuery(request
));
137 // T instance = getCdmBaseInstance(uuid, response, (List<String>)null);
138 //Class<?> propertyClass = propertyClass(instance, baseName);
139 Object objectFromProperty
= getCdmBaseProperty(uuid
, baseName
, response
);// invokeProperty(instance, baseName, response);
141 if(objectFromProperty
!= null){
142 if( Collection
.class.isAssignableFrom(objectFromProperty
.getClass())){
143 // Map types cannot be returned as list or in a pager!
144 return pageFromCollection((Collection
<CdmBase
>)objectFromProperty
, pageNumber
, pageSize
, start
, limit
, response
);
146 return objectFromProperty
;
153 * Returns a sub-collection of <code>c</code>. A pager object will be returned if the <code>pageNumber</code> and
154 * <code>pageSize</code> are given. Otherwise a <code>List</code> in case of <code>start</code> and <code>limit</code>.
161 * @param objectFromProperty
162 * @return either a List or Pager depending on the parameter combination.
164 * @throws IOException
166 protected Object
pageFromCollection(Collection
<?
extends CdmBase
> c
, Integer pageNumber
, Integer pageSize
, Integer start
,
167 Integer limit
, HttpServletResponse response
) throws IOException
{
169 if(c
instanceof Set
){
170 // sets need to be sorted to have a defined order
171 List
<CdmBase
> list
= new ArrayList
<>(c
);
172 java
.util
.Collections
.sort(list
, new Comparator
<CdmBase
>() {
175 public int compare(CdmBase o1
, CdmBase o2
) {
176 if (o1
== null && o2
== null){
178 }else if (o1
== null){
180 }else if (o2
== null){
183 return Integer
.compare(o1
.getId(), o2
.getId());
191 limit
= (limit
== null ? DEFAULT_PAGE_SIZE
: limit
);
192 Collection
<CdmBase
> sub_c
= subCollection(c
, start
, limit
);
195 //FIXME use real paging mechanism of according service class instead of subCollection()
196 //FIXME use BaseListController.normalizeAndValidatePagerParameters(pageNumber, pageSize, response);
197 PagerParameters pagerParameters
= new PagerParameters(pageSize
, pageNumber
);
198 pagerParameters
.normalizeAndValidate(response
);
200 start
= pagerParameters
.getPageIndex() * pagerParameters
.getPageSize();
201 List sub_c
= subCollection(c
, start
, pagerParameters
.getPageSize());
202 Pager p
= new DefaultPagerImpl(pageNumber
, c
.size(), pagerParameters
.getPageSize(), sub_c
);
207 public Object
getCdmBaseProperty(UUID uuid
, String property
, HttpServletResponse response
) throws IOException
{
209 T instance
= HibernateProxyHelper
.deproxy(getCdmBaseInstance(uuid
, response
, property
));
211 Object objectFromProperty
= invokeProperty(instance
, property
, response
);
213 return objectFromProperty
;
216 private Class
<?
> propertyClass(T instance
, String baseName
) {
217 PropertyDescriptor propertyDescriptor
= null;
220 propertyDescriptor
= PropertyUtils
.getPropertyDescriptor(instance
, baseName
);
221 if(propertyDescriptor
!= null){
222 c
= propertyDescriptor
.getClass();
224 } catch (IllegalAccessException e
) {
225 // TODO Auto-generated catch block
227 } catch (InvocationTargetException e
) {
228 // TODO Auto-generated catch block
230 } catch (NoSuchMethodException e
) {
231 // TODO Auto-generated catch block
242 * @param pathProperties
244 * @throws IOException
246 @SuppressWarnings("unchecked")
247 protected final <SUB_T
extends T
> SUB_T
getCdmBaseInstance(Class
<SUB_T
> clazz
, UUID uuid
, HttpServletResponse response
, List
<String
> pathProperties
)
250 CdmBase cdmBaseObject
= getCdmBaseInstance(uuid
, response
, pathProperties
);
251 if(!clazz
.isAssignableFrom(cdmBaseObject
.getClass())){
252 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
254 return (SUB_T
) cdmBaseObject
;
262 * @param pathProperty
264 * @throws IOException
266 @SuppressWarnings("unchecked")
267 protected final <SUB_T
extends T
> SUB_T
getCdmBaseInstance(Class
<SUB_T
> clazz
, UUID uuid
, HttpServletResponse response
, String pathProperty
)
270 CdmBase cdmBaseObject
= getCdmBaseInstance(uuid
, response
, pathProperty
);
271 if(!clazz
.isAssignableFrom(cdmBaseObject
.getClass())){
272 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
274 return (SUB_T
) cdmBaseObject
;
280 * @param pathProperty
282 * @throws IOException
284 protected final T
getCdmBaseInstance(UUID uuid
, HttpServletResponse response
, String pathProperty
)
286 return getCdmBaseInstance(baseClass
, uuid
, response
, Arrays
287 .asList(new String
[] { pathProperty
}));
294 * @param pathProperties
296 * @throws IOException
298 protected final T
getCdmBaseInstance(UUID uuid
, HttpServletResponse response
, List
<String
> pathProperties
)
300 return getCdmBaseInstance(baseClass
, service
, uuid
, response
, pathProperties
);
309 * @param pathProperties
311 * @throws IOException
313 protected final <CDM_BASE
extends CdmBase
> CDM_BASE
getCdmBaseInstance(Class
<CDM_BASE
> clazz
, IService
<CDM_BASE
> service
, UUID uuid
, HttpServletResponse response
, List
<String
> pathProperties
)
316 CDM_BASE cdmBaseObject
= service
.load(uuid
, pathProperties
);
317 if (cdmBaseObject
== null) {
318 HttpStatusMessage
.UUID_NOT_FOUND
.send(response
);
320 return cdmBaseObject
;
328 * @throws IOException
330 private final Object
invokeProperty(T instance
,
331 String baseName
, HttpServletResponse response
) throws IOException
{
333 Object result
= null;
335 PropertyDescriptor propertyDescriptor
= PropertyUtils
.getPropertyDescriptor(instance
, baseName
);
336 if(propertyDescriptor
== null){
337 throw new NoSuchMethodException("No such method: " + instance
.getClass().getSimpleName() + ".get" + baseName
);
339 Method method
= propertyDescriptor
.getReadMethod();
341 Class
<?
> returnType
= method
.getReturnType();
343 if(CdmBase
.class.isAssignableFrom(returnType
)
344 || Collection
.class.isAssignableFrom(returnType
)
345 || Map
.class.isAssignableFrom(returnType
)
346 || INomenclaturalReference
.class.isAssignableFrom(returnType
)){
348 result
= method
.invoke(instance
, (Object
[])null);
350 result
= HibernateProxyHelper
.deproxy(result
);
353 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
355 } catch (SecurityException e
) {
356 logger
.error("SecurityException: ", e
);
357 HttpStatusMessage
.INTERNAL_ERROR
.send(response
);
358 } catch (NoSuchMethodException e
) {
359 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
360 } catch (IllegalArgumentException e
) {
361 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
362 } catch (IllegalAccessException e
) {
363 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
364 } catch (InvocationTargetException e
) {
365 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
370 private <E
> List
<E
> subCollection(Collection
<?
extends E
> c
, Integer start
, Integer length
){
371 List
<E
> sub_c
= new ArrayList
<E
>(length
);
372 if(c
.size() > length
){
373 E
[] a
= (E
[]) c
.toArray();
374 for(int i
= start
; i
< start
+ length
; i
++){
387 private Validator validator;
389 private javax.validation.Validator javaxValidator;
391 @RequestMapping(method = RequestMethod.PUT, headers="content-type=multipart/form-data")
392 public T doPutForm(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
393 object.setUuid(uuid);
394 validator.validate(object, result);
395 if (result.hasErrors()) {
397 // set http status code depending upon what happened, possibly return
398 // the put object and errors so that they can be rendered into a suitable error response
400 // requires merging detached object ?gilead?
401 service.save(object);
407 @RequestMapping(method = RequestMethod.PUT, headers="content-type=text/json")
408 public T doPutJSON(@PathVariable(value = "uuid") UUID uuid, @RequestBody String jsonMessage) {
409 JSONObject jsonObject = JSONObject.fromObject(jsonMessage);
410 T object = (T)JSONObject.toBean(jsonObject, this.getClass());
413 Set<ConstraintViolation<T>> constraintViolations = javaxValidator.validate(object);
414 if (!constraintViolations.isEmpty()) {
416 // set http status code depending upon what happened, possibly return
417 // the put object and errors so that they can be rendered into a suitable error response
419 // requires merging detached object ?gilead?
420 service.save(object);
426 @RequestMapping(method = RequestMethod.PUT) // the cdm-server may not allow clients to specify the uuid for resources
427 public T doPut(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
428 validator.validate(object, result);
429 if (result.hasErrors()) {
430 // set http status code depending upon what happened, possibly return
431 // the put object and errors so that they can be rendered into a suitable error response
433 service.save(object);
437 @RequestMapping(method = RequestMethod.DELETE)
438 public void doDelete(@PathVariable(value = "uuid") UUID uuid) {
439 T object = service.find(uuid);
440 // provided the object exists
441 service.delete(uuid);
442 // might return 204 or 200