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.
9 package eu
.etaxonomy
.cdm
.remote
.controller
;
11 import java
.beans
.PropertyDescriptor
;
12 import java
.io
.IOException
;
13 import java
.lang
.reflect
.InvocationTargetException
;
14 import java
.lang
.reflect
.Method
;
15 import java
.lang
.reflect
.ParameterizedType
;
16 import java
.lang
.reflect
.Type
;
17 import java
.util
.ArrayList
;
18 import java
.util
.Arrays
;
19 import java
.util
.Collection
;
20 import java
.util
.List
;
22 import java
.util
.UUID
;
24 import javax
.servlet
.http
.HttpServletRequest
;
25 import javax
.servlet
.http
.HttpServletResponse
;
27 import org
.apache
.commons
.beanutils
.PropertyUtils
;
28 import org
.apache
.commons
.io
.FilenameUtils
;
29 import org
.apache
.commons
.lang3
.StringUtils
;
30 import org
.apache
.logging
.log4j
.LogManager
;
31 import org
.apache
.logging
.log4j
.Logger
;
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
.IClassificationService
;
41 import eu
.etaxonomy
.cdm
.api
.service
.IService
;
42 import eu
.etaxonomy
.cdm
.api
.service
.ITaxonNodeService
;
43 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
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
.model
.taxon
.Classification
;
49 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
50 import eu
.etaxonomy
.cdm
.remote
.editor
.UUIDPropertyEditor
;
53 * based on org.cateproject.controller.common
55 * @author a.kohlbecker
57 public abstract class BaseController
<T
extends CdmBase
, SERVICE
extends IService
<T
>>
58 extends AbstractController
<T
, SERVICE
> {
60 private static final Logger logger
= LogManager
.getLogger();
62 protected Class
<T
> baseClass
;
64 @SuppressWarnings("unchecked")
65 public BaseController (){
67 //define base class //TODO can't we do this more straight forward e.g.
68 //by an abstract method returning the class?
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
);
105 if (obj
instanceof IPublishable
){
106 obj
= (T
)checkExistsAndAccess((IPublishable
)obj
, NO_UNPUBLISHED
, response
);
116 * @throws IOException
118 * TODO implement bulk version of this method
120 @RequestMapping(value
= "*", method
= RequestMethod
.GET
)
121 public Object
doGetMethod(
122 @PathVariable("uuid") UUID uuid
,
123 // doPage request parameters
124 @RequestParam(value
= "pageIndex", required
= false) Integer pageIndex
,
125 @RequestParam(value
= "pageSize", required
= false) Integer pageSize
,
126 // doList request parameters
127 @RequestParam(value
= "start", required
= false) Integer start
,
128 @RequestParam(value
= "limit", required
= false) Integer limit
,
129 HttpServletRequest request
,
130 HttpServletResponse response
) throws IOException
{
132 String servletPath
= request
.getServletPath();
133 String propertyName
= FilenameUtils
.getBaseName(servletPath
);
135 logger
.info("doGetMethod()[doGet" + StringUtils
.capitalize(propertyName
) + "] " + requestPathAndQuery(request
));
138 // T instance = getCdmBaseInstance(uuid, response, (List<String>)null);
139 //Class<?> propertyClass = propertyClass(instance, baseName);
140 Object objectFromProperty
= getCdmBaseProperty(uuid
, propertyName
, response
);// invokeProperty(instance, baseName, response);
142 if(objectFromProperty
!= null){
143 if( Collection
.class.isAssignableFrom(objectFromProperty
.getClass())){
144 // Map types cannot be returned as list or in a pager!
145 return pageFromCollection((Collection
<CdmBase
>)objectFromProperty
, pageIndex
, pageSize
, start
, limit
, response
);
147 return objectFromProperty
;
154 * Returns a sub-collection of <code>c</code>. A pager object will be returned if the <code>pageNumber</code> and
155 * <code>pageSize</code> are given. Otherwise a <code>List</code> in case of <code>start</code> and <code>limit</code>.
162 * @param objectFromProperty
163 * @return either a List or Pager depending on the parameter combination.
165 * @throws IOException
167 protected Object
pageFromCollection(Collection
<?
extends CdmBase
> c
, Integer pageNumber
, Integer pageSize
, Integer start
,
168 Integer limit
, HttpServletResponse response
) throws IOException
{
170 if(c
instanceof Set
){
171 // sets need to be sorted to have a defined order
172 List
<CdmBase
> list
= new ArrayList
<>(c
);
173 java
.util
.Collections
.sort(list
, (o1
, o2
)-> {
174 if (o1
== null && o2
== null){
176 }else if (o1
== null){
178 }else if (o2
== null){
181 return Integer
.compare(o1
.getId(), o2
.getId());
189 limit
= (limit
== null ? DEFAULT_PAGE_SIZE
: limit
);
190 Collection
<CdmBase
> sub_c
= subCollection(c
, start
, limit
);
193 //FIXME use real paging mechanism of according service class instead of subCollection()
194 //FIXME use BaseListController.normalizeAndValidatePagerParameters(pageNumber, pageSize, response);
195 Pager
<?
extends CdmBase
> p
= pagerForSubCollectionOf(c
, pageNumber
, pageSize
, response
);
200 public Object
getCdmBaseProperty(UUID uuid
, String property
, HttpServletResponse response
) throws IOException
{
202 T instance
= HibernateProxyHelper
.deproxy(getCdmBaseInstance(uuid
, response
, property
));
204 Object objectFromProperty
= invokeProperty(instance
, property
, response
);
206 return objectFromProperty
;
209 @SuppressWarnings("unchecked")
210 protected final <SUB_T
extends T
> SUB_T
getCdmBaseInstance(Class
<SUB_T
> clazz
,
211 UUID uuid
, HttpServletResponse response
, List
<String
> pathProperties
)
214 CdmBase cdmBaseObject
= getCdmBaseInstance(uuid
, response
, pathProperties
);
215 if(!clazz
.isAssignableFrom(cdmBaseObject
.getClass())){
216 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
218 return (SUB_T
) cdmBaseObject
;
221 @SuppressWarnings("unchecked")
222 protected final <SUB_T
extends T
> SUB_T
getCdmBaseInstance(Class
<SUB_T
> clazz
, UUID uuid
, HttpServletResponse response
, String pathProperty
)
225 CdmBase cdmBaseObject
= getCdmBaseInstance(uuid
, response
, pathProperty
);
226 if(!clazz
.isAssignableFrom(cdmBaseObject
.getClass())){
227 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
229 return (SUB_T
) cdmBaseObject
;
232 protected final T
getCdmBaseInstance(UUID uuid
, HttpServletResponse response
, String pathProperty
)
234 return getCdmBaseInstance(baseClass
, uuid
, response
, Arrays
235 .asList(new String
[] { pathProperty
}));
238 protected final T
getCdmBaseInstance(UUID uuid
, HttpServletResponse response
, List
<String
> pathProperties
)
240 return getCdmBaseInstance(baseClass
, service
, uuid
, response
, pathProperties
);
243 protected final <CDM_BASE
extends CdmBase
> CDM_BASE
getCdmBaseInstance(
244 Class
<CDM_BASE
> clazz
, IService
<CDM_BASE
> service
, UUID uuid
,
245 HttpServletResponse response
, List
<String
> pathProperties
)
248 @SuppressWarnings("unused")
249 boolean includeUnpublished
= NO_UNPUBLISHED
;
250 CDM_BASE cdmBaseObject
;
251 // if (service instanceof IPublishableService){
252 // cdmBaseObject = ((IPublishableService<CDM_BASE>)service).load(uuid, includeUnpublished, pathProperties);
254 pathProperties
= complementInitStrategy(clazz
, pathProperties
);
255 cdmBaseObject
= service
.load(uuid
, pathProperties
);
257 if (cdmBaseObject
== null) {
258 HttpStatusMessage
.UUID_NOT_FOUND
.send(response
);
260 return cdmBaseObject
;
264 * Implementations of the BaseController can override this method to
265 * extend the <code>pathProperties</code> to for example avoid
266 * <code>LazyInitializationExceptions</code> which can happen when
267 * {@link #doGetMethod(UUID, Integer, Integer, Integer, Integer, HttpServletRequest, HttpServletResponse)} is being used.
270 * @param pathProperties
272 protected <CDM_BASE
extends CdmBase
> List
<String
> complementInitStrategy(@SuppressWarnings("unused") Class
<CDM_BASE
> clazz
, List
<String
> pathProperties
) {
273 return pathProperties
;
276 private final Object
invokeProperty(T instance
,
277 String baseName
, HttpServletResponse response
) throws IOException
{
279 Object result
= null;
281 PropertyDescriptor propertyDescriptor
= PropertyUtils
.getPropertyDescriptor(instance
, baseName
);
282 if(propertyDescriptor
== null){
283 throw new NoSuchMethodException("No such method: " + instance
.getClass().getSimpleName() + ".get" + baseName
);
285 Method method
= propertyDescriptor
.getReadMethod();
287 Class
<?
> returnType
= method
.getReturnType();
289 if(CdmBase
.class.isAssignableFrom(returnType
)
290 || Collection
.class.isAssignableFrom(returnType
)
291 || Map
.class.isAssignableFrom(returnType
)
292 || INomenclaturalReference
.class.isAssignableFrom(returnType
)){
294 result
= method
.invoke(instance
, (Object
[])null);
296 result
= HibernateProxyHelper
.deproxy(result
);
299 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
301 } catch (SecurityException e
) {
302 logger
.error("SecurityException: ", e
);
303 HttpStatusMessage
.INTERNAL_ERROR
.send(response
);
304 } catch (NoSuchMethodException e
) {
305 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
306 } catch (IllegalArgumentException e
) {
307 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
308 } catch (IllegalAccessException e
) {
309 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
310 } catch (InvocationTargetException e
) {
311 HttpStatusMessage
.PROPERTY_NOT_FOUND
.send(response
);
317 * Checks if an {@link IPublishable} was found and if it is publish.
318 * If not the according {@link HttpStatusMessage http messages} are added to response.
320 * @param includeUnpublished
323 * @throws IOException
325 protected <S
extends IPublishable
> S
checkExistsAndAccess(S publishable
, boolean includeUnpublished
,
326 HttpServletResponse response
) throws IOException
{
327 if (publishable
== null){
328 HttpStatusMessage
.UUID_NOT_FOUND
.send(response
);
329 }else if (!includeUnpublished
&& !publishable
.isPublish()){
330 HttpStatusMessage
.ACCESS_DENIED
.send(response
);
336 protected <S
extends IPublishable
> S
checkExistsAccessType(IPublishable publishable
, boolean includeUnpublished
,
337 Class
<S
> clazz
, HttpServletResponse response
) throws IOException
{
338 IPublishable result
= this.checkExistsAndAccess(publishable
, includeUnpublished
, response
);
339 if (clazz
!= null && !clazz
.isAssignableFrom(result
.getClass())){
340 HttpStatusMessage
.UUID_REFERENCES_WRONG_TYPE
.send(response
);
346 protected TaxonNode
getSubtreeOrError(UUID subtreeUuid
, ITaxonNodeService taxonNodeService
, HttpServletResponse response
) throws IOException
{
347 TaxonNode subtree
= null;
348 if (subtreeUuid
!= null){
349 subtree
= taxonNodeService
.find(subtreeUuid
);
350 if(subtree
== null) {
351 response
.sendError(404 , "TaxonNode not found using " + subtreeUuid
);
359 protected Classification
getClassificationOrError(UUID classificationUuid
,
360 IClassificationService classificationService
, HttpServletResponse response
) throws IOException
{
361 Classification classification
= null;
362 if (classificationUuid
!= null){
363 classification
= classificationService
.find(classificationUuid
);
364 if(classification
== null) {
365 response
.sendError(404 , "Classification not found: " + classificationUuid
);
370 return classification
;
375 private Validator validator;
377 private javax.validation.Validator javaxValidator;
379 @RequestMapping(method = RequestMethod.PUT, headers="content-type=multipart/form-data")
380 public T doPutForm(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
381 object.setUuid(uuid);
382 validator.validate(object, result);
383 if (result.hasErrors()) {
385 // set http status code depending upon what happened, possibly return
386 // the put object and errors so that they can be rendered into a suitable error response
388 // requires merging detached object ?gilead?
389 service.save(object);
395 @RequestMapping(method = RequestMethod.PUT, headers="content-type=text/json")
396 public T doPutJSON(@PathVariable(value = "uuid") UUID uuid, @RequestBody String jsonMessage) {
397 JSONObject jsonObject = JSONObject.fromObject(jsonMessage);
398 T object = (T)JSONObject.toBean(jsonObject, this.getClass());
401 Set<ConstraintViolation<T>> constraintViolations = javaxValidator.validate(object);
402 if (!constraintViolations.isEmpty()) {
404 // set http status code depending upon what happened, possibly return
405 // the put object and errors so that they can be rendered into a suitable error response
407 // requires merging detached object ?gilead?
408 service.save(object);
414 @RequestMapping(method = RequestMethod.PUT) // the cdm-server may not allow clients to specify the uuid for resources
415 public T doPut(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
416 validator.validate(object, result);
417 if (result.hasErrors()) {
418 // set http status code depending upon what happened, possibly return
419 // the put object and errors so that they can be rendered into a suitable error response
421 service.save(object);
425 @RequestMapping(method = RequestMethod.DELETE)
426 public void doDelete(@PathVariable(value = "uuid") UUID uuid) {
427 T object = service.find(uuid);
428 // provided the object exists
429 service.delete(uuid);
430 // might return 204 or 200