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
.apache
.log4j
.Logger
;
33 import org
.hibernate
.mapping
.Map
;
34 import org
.springframework
.web
.bind
.WebDataBinder
;
35 import org
.springframework
.web
.bind
.annotation
.InitBinder
;
36 import org
.springframework
.web
.bind
.annotation
.PathVariable
;
37 import org
.springframework
.web
.bind
.annotation
.RequestMapping
;
38 import org
.springframework
.web
.bind
.annotation
.RequestMethod
;
39 import org
.springframework
.web
.bind
.annotation
.RequestParam
;
41 import eu
.etaxonomy
.cdm
.api
.service
.IService
;
42 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
43 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
44 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
45 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
46 import eu
.etaxonomy
.cdm
.model
.common
.IPublishable
;
47 import eu
.etaxonomy
.cdm
.model
.reference
.INomenclaturalReference
;
48 import eu
.etaxonomy
.cdm
.remote
.controller
.util
.PagerParameters
;
49 import eu
.etaxonomy
.cdm
.remote
.editor
.UUIDPropertyEditor
;
52 * based on org.cateproject.controller.common
54 * @author a.kohlbecker
60 public abstract class BaseController
<T
extends CdmBase
, SERVICE
extends IService
<T
>> extends AbstractController
<T
, SERVICE
> {
62 private static final Logger logger
= Logger
.getLogger(BaseController
.class);
65 /* protected SERVICE service;
67 public abstract void setService(SERVICE service);*/
69 protected Class
<T
> baseClass
;
71 @SuppressWarnings("unchecked")
72 public BaseController (){
74 Type superClass
= this.getClass().getGenericSuperclass();
76 if(superClass
instanceof ParameterizedType
){
77 ParameterizedType parametrizedSuperClass
= (ParameterizedType
) superClass
;
78 Type
[] typeArguments
= parametrizedSuperClass
.getActualTypeArguments();
80 if(typeArguments
.length
> 1 && typeArguments
[0] instanceof Class
<?
>){
81 baseClass
= (Class
<T
>) typeArguments
[0];
83 logger
.error("unable to find baseClass");
86 } else if(superClass
instanceof Class
<?
>){
87 superClass
= ((Class
<?
>) superClass
).getGenericSuperclass();
89 // no point digging deeper if neither Class or ParameterizedType
90 logger
.error("unable to find baseClass");
97 public void initBinder(WebDataBinder binder
) {
98 binder
.registerCustomEditor(UUID
.class, new UUIDPropertyEditor());
101 //TODO implement bulk version of this method
102 @RequestMapping(method
= RequestMethod
.GET
)
103 public T
doGet(@PathVariable("uuid") UUID uuid
,
104 HttpServletRequest request
,
105 HttpServletResponse response
) throws IOException
{
106 if(request
!= null) {
107 logger
.info("doGet() " + request
.getRequestURI());
109 T obj
= getCdmBaseInstance(uuid
, response
, initializationStrategy
);
110 if (obj
instanceof IPublishable
){
111 obj
= (T
)checkExistsAndAccess((IPublishable
)obj
, NO_UNPUBLISHED
, response
);
121 * @throws IOException
123 * TODO implement bulk version of this method
125 @RequestMapping(value
= "*", method
= RequestMethod
.GET
)
126 public Object
doGetMethod(
127 @PathVariable("uuid") UUID uuid
,
128 // doPage request parametes
129 @RequestParam(value
= "pageNumber", required
= false) Integer pageNumber
,
130 @RequestParam(value
= "pageSize", required
= false) Integer pageSize
,
131 // doList request parametes
132 @RequestParam(value
= "start", required
= false) Integer start
,
133 @RequestParam(value
= "limit", required
= false) Integer limit
,
134 HttpServletRequest request
,
135 HttpServletResponse response
) throws IOException
{
137 String servletPath
= request
.getServletPath();
138 String propertyName
= FilenameUtils
.getBaseName(servletPath
);
140 logger
.info("doGetMethod()[doGet" + StringUtils
.capitalize(propertyName
) + "] " + requestPathAndQuery(request
));
143 // T instance = getCdmBaseInstance(uuid, response, (List<String>)null);
144 //Class<?> propertyClass = propertyClass(instance, baseName);
145 Object objectFromProperty
= getCdmBaseProperty(uuid
, propertyName
, response
);// invokeProperty(instance, baseName, response);
147 if(objectFromProperty
!= null){
148 if( Collection
.class.isAssignableFrom(objectFromProperty
.getClass())){
149 // Map types cannot be returned as list or in a pager!
150 return pageFromCollection((Collection
<CdmBase
>)objectFromProperty
, pageNumber
, pageSize
, start
, limit
, response
);
152 return objectFromProperty
;
159 * Returns a sub-collection of <code>c</code>. A pager object will be returned if the <code>pageNumber</code> and
160 * <code>pageSize</code> are given. Otherwise a <code>List</code> in case of <code>start</code> and <code>limit</code>.
167 * @param objectFromProperty
168 * @return either a List or Pager depending on the parameter combination.
170 * @throws IOException
172 protected Object
pageFromCollection(Collection
<?
extends CdmBase
> c
, Integer pageNumber
, Integer pageSize
, Integer start
,
173 Integer limit
, HttpServletResponse response
) throws IOException
{
175 if(c
instanceof Set
){
176 // sets need to be sorted to have a defined order
177 List
<CdmBase
> list
= new ArrayList
<>(c
);
178 java
.util
.Collections
.sort(list
, new Comparator
<CdmBase
>() {
181 public int compare(CdmBase o1
, CdmBase o2
) {
182 if (o1
== null && o2
== null){
184 }else if (o1
== null){
186 }else if (o2
== null){
189 return Integer
.compare(o1
.getId(), o2
.getId());
197 limit
= (limit
== null ? DEFAULT_PAGE_SIZE
: limit
);
198 Collection
<CdmBase
> sub_c
= subCollection(c
, start
, limit
);
201 //FIXME use real paging mechanism of according service class instead of subCollection()
202 //FIXME use BaseListController.normalizeAndValidatePagerParameters(pageNumber, pageSize, response);
203 PagerParameters pagerParameters
= new PagerParameters(pageSize
, pageNumber
);
204 pagerParameters
.normalizeAndValidate(response
);
206 start
= pagerParameters
.getPageIndex() * pagerParameters
.getPageSize();
207 List
<?
extends CdmBase
> sub_c
= subCollection(c
, start
, pagerParameters
.getPageSize());
208 Pager
<?
extends CdmBase
> p
= new DefaultPagerImpl
<>(pageNumber
, c
.size(), pagerParameters
.getPageSize(), sub_c
);
213 public Object
getCdmBaseProperty(UUID uuid
, String property
, HttpServletResponse response
) throws IOException
{
215 T instance
= HibernateProxyHelper
.deproxy(getCdmBaseInstance(uuid
, response
, property
));
217 Object objectFromProperty
= invokeProperty(instance
, property
, response
);
219 return objectFromProperty
;
222 private Class
<?
> propertyClass(T instance
, String baseName
) {
223 PropertyDescriptor propertyDescriptor
= null;
226 propertyDescriptor
= PropertyUtils
.getPropertyDescriptor(instance
, baseName
);
227 if(propertyDescriptor
!= null){
228 c
= propertyDescriptor
.getClass();
230 } catch (IllegalAccessException e
) {
231 // TODO Auto-generated catch block
233 } catch (InvocationTargetException e
) {
234 // TODO Auto-generated catch block
236 } catch (NoSuchMethodException e
) {
237 // TODO Auto-generated catch block
248 * @param pathProperties
250 * @throws IOException
252 @SuppressWarnings("unchecked")
253 protected final <SUB_T
extends T
> SUB_T
getCdmBaseInstance(Class
<SUB_T
> clazz
, UUID uuid
, HttpServletResponse response
, List
<String
> pathProperties
)
256 CdmBase cdmBaseObject
= getCdmBaseInstance(uuid
, response
, pathProperties
);
257 if(!clazz
.isAssignableFrom(cdmBaseObject
.getClass())){
258 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
260 return (SUB_T
) cdmBaseObject
;
268 * @param pathProperty
270 * @throws IOException
272 @SuppressWarnings("unchecked")
273 protected final <SUB_T
extends T
> SUB_T
getCdmBaseInstance(Class
<SUB_T
> clazz
, UUID uuid
, HttpServletResponse response
, String pathProperty
)
276 CdmBase cdmBaseObject
= getCdmBaseInstance(uuid
, response
, pathProperty
);
277 if(!clazz
.isAssignableFrom(cdmBaseObject
.getClass())){
278 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
280 return (SUB_T
) cdmBaseObject
;
286 * @param pathProperty
288 * @throws IOException
290 protected final T
getCdmBaseInstance(UUID uuid
, HttpServletResponse response
, String pathProperty
)
292 return getCdmBaseInstance(baseClass
, uuid
, response
, Arrays
293 .asList(new String
[] { pathProperty
}));
300 * @param pathProperties
302 * @throws IOException
304 protected final T
getCdmBaseInstance(UUID uuid
, HttpServletResponse response
, List
<String
> pathProperties
)
306 return getCdmBaseInstance(baseClass
, service
, uuid
, response
, pathProperties
);
315 * @param pathProperties
317 * @throws IOException
319 protected final <CDM_BASE
extends CdmBase
> CDM_BASE
getCdmBaseInstance(
320 Class
<CDM_BASE
> clazz
, IService
<CDM_BASE
> service
, UUID uuid
,
321 HttpServletResponse response
, List
<String
> pathProperties
)
324 boolean includeUnpublished
= NO_UNPUBLISHED
;
325 CDM_BASE cdmBaseObject
;
326 // if (service instanceof IPublishableService){
327 // cdmBaseObject = ((IPublishableService<CDM_BASE>)service).load(uuid, includeUnpublished, pathProperties);
329 cdmBaseObject
= service
.load(uuid
, pathProperties
);
331 if (cdmBaseObject
== null) {
332 HttpStatusMessage
.UUID_NOT_FOUND
.send(response
);
334 return cdmBaseObject
;
342 * @throws IOException
344 private final Object
invokeProperty(T instance
,
345 String baseName
, HttpServletResponse response
) throws IOException
{
347 Object result
= null;
349 PropertyDescriptor propertyDescriptor
= PropertyUtils
.getPropertyDescriptor(instance
, baseName
);
350 if(propertyDescriptor
== null){
351 throw new NoSuchMethodException("No such method: " + instance
.getClass().getSimpleName() + ".get" + baseName
);
353 Method method
= propertyDescriptor
.getReadMethod();
355 Class
<?
> returnType
= method
.getReturnType();
357 if(CdmBase
.class.isAssignableFrom(returnType
)
358 || Collection
.class.isAssignableFrom(returnType
)
359 || Map
.class.isAssignableFrom(returnType
)
360 || INomenclaturalReference
.class.isAssignableFrom(returnType
)){
362 result
= method
.invoke(instance
, (Object
[])null);
364 result
= HibernateProxyHelper
.deproxy(result
);
367 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
369 } catch (SecurityException e
) {
370 logger
.error("SecurityException: ", e
);
371 HttpStatusMessage
.INTERNAL_ERROR
.send(response
);
372 } catch (NoSuchMethodException e
) {
373 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
374 } catch (IllegalArgumentException e
) {
375 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
376 } catch (IllegalAccessException e
) {
377 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
378 } catch (InvocationTargetException e
) {
379 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
384 private <E
> List
<E
> subCollection(Collection
<?
extends E
> c
, Integer start
, Integer length
){
385 List
<E
> sub_c
= new ArrayList
<E
>(length
);
386 if(c
.size() > length
){
387 E
[] a
= (E
[]) c
.toArray();
388 for(int i
= start
; i
< start
+ length
; i
++){
399 * Checks if an {@link IPublishable} was found and if it is publish.
400 * If not the according {@link HttpStatusMessage http messages} are added to response.
402 * @param includeUnpublished
405 * @throws IOException
407 protected <T
extends IPublishable
> T
checkExistsAndAccess(T publishable
, boolean includeUnpublished
,
408 HttpServletResponse response
) throws IOException
{
409 if (publishable
== null){
410 HttpStatusMessage
.UUID_NOT_FOUND
.send(response
);
411 }else if (!includeUnpublished
&& !publishable
.isPublish()){
412 HttpStatusMessage
.ACCESS_DENIED
.send(response
);
421 private Validator validator;
423 private javax.validation.Validator javaxValidator;
425 @RequestMapping(method = RequestMethod.PUT, headers="content-type=multipart/form-data")
426 public T doPutForm(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
427 object.setUuid(uuid);
428 validator.validate(object, result);
429 if (result.hasErrors()) {
431 // set http status code depending upon what happened, possibly return
432 // the put object and errors so that they can be rendered into a suitable error response
434 // requires merging detached object ?gilead?
435 service.save(object);
441 @RequestMapping(method = RequestMethod.PUT, headers="content-type=text/json")
442 public T doPutJSON(@PathVariable(value = "uuid") UUID uuid, @RequestBody String jsonMessage) {
443 JSONObject jsonObject = JSONObject.fromObject(jsonMessage);
444 T object = (T)JSONObject.toBean(jsonObject, this.getClass());
447 Set<ConstraintViolation<T>> constraintViolations = javaxValidator.validate(object);
448 if (!constraintViolations.isEmpty()) {
450 // set http status code depending upon what happened, possibly return
451 // the put object and errors so that they can be rendered into a suitable error response
453 // requires merging detached object ?gilead?
454 service.save(object);
460 @RequestMapping(method = RequestMethod.PUT) // the cdm-server may not allow clients to specify the uuid for resources
461 public T doPut(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
462 validator.validate(object, result);
463 if (result.hasErrors()) {
464 // set http status code depending upon what happened, possibly return
465 // the put object and errors so that they can be rendered into a suitable error response
467 service.save(object);
471 @RequestMapping(method = RequestMethod.DELETE)
472 public void doDelete(@PathVariable(value = "uuid") UUID uuid) {
473 T object = service.find(uuid);
474 // provided the object exists
475 service.delete(uuid);
476 // might return 204 or 200