Merge branch 'release/5.1.0'
[cdmlib.git] / cdmlib-remote / src / main / java / eu / etaxonomy / cdm / remote / controller / BaseController.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
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.
8 */
9
10 package eu.etaxonomy.cdm.remote.controller;
11
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;
23 import java.util.Set;
24 import java.util.UUID;
25
26 import javax.servlet.http.HttpServletRequest;
27 import javax.servlet.http.HttpServletResponse;
28
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;
40
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;
50
51 /**
52 * based on org.cateproject.controller.common
53 * @author b.clark
54 * @author a.kohlbecker
55 *
56 * @param <T>
57 * @param <SERVICE>
58 */
59
60 public abstract class BaseController<T extends CdmBase, SERVICE extends IService<T>> extends AbstractController<T, SERVICE> {
61
62 private static final Logger logger = Logger.getLogger(BaseController.class);
63
64
65 /* protected SERVICE service;
66
67 public abstract void setService(SERVICE service);*/
68
69 protected Class<T> baseClass;
70
71 @SuppressWarnings("unchecked")
72 public BaseController (){
73
74 Type superClass = this.getClass().getGenericSuperclass();
75 while(true){
76 if(superClass instanceof ParameterizedType){
77 ParameterizedType parametrizedSuperClass = (ParameterizedType) superClass;
78 Type[] typeArguments = parametrizedSuperClass.getActualTypeArguments();
79
80 if(typeArguments.length > 1 && typeArguments[0] instanceof Class<?>){
81 baseClass = (Class<T>) typeArguments[0];
82 } else {
83 logger.error("unable to find baseClass");
84 }
85 break;
86 } else if(superClass instanceof Class<?>){
87 superClass = ((Class<?>) superClass).getGenericSuperclass();
88 } else {
89 // no point digging deeper if neither Class or ParameterizedType
90 logger.error("unable to find baseClass");
91 break;
92 }
93 }
94 }
95
96 @InitBinder
97 public void initBinder(WebDataBinder binder) {
98 binder.registerCustomEditor(UUID.class, new UUIDPropertyEditor());
99 }
100
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());
108 }
109 T obj = getCdmBaseInstance(uuid, response, initializationStrategy);
110 if (obj instanceof IPublishable){
111 obj = (T)checkExistsAndAccess((IPublishable)obj, NO_UNPUBLISHED, response);
112 }
113 return obj;
114 }
115
116 /**
117 * @param uuid
118 * @param request
119 * @param response
120 * @return
121 * @throws IOException
122 *
123 * TODO implement bulk version of this method
124 */
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 {
136
137 String servletPath = request.getServletPath();
138 String propertyName = FilenameUtils.getBaseName(servletPath);
139
140 logger.info("doGetMethod()[doGet" + StringUtils.capitalize(propertyName) + "] " + requestPathAndQuery(request));
141
142 // <CUT
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);
146 // CUT>
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);
151 } else {
152 return objectFromProperty;
153 }
154 }
155 return null;
156 }
157
158 /**
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>.
161 *
162 * @param pageNumber
163 * @param pageSize
164 * @param start
165 * @param limit
166 * @param response
167 * @param objectFromProperty
168 * @return either a List or Pager depending on the parameter combination.
169 *
170 * @throws IOException
171 */
172 protected Object pageFromCollection(Collection<? extends CdmBase> c, Integer pageNumber, Integer pageSize, Integer start,
173 Integer limit, HttpServletResponse response) throws IOException {
174
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>() {
179
180 @Override
181 public int compare(CdmBase o1, CdmBase o2) {
182 if (o1 == null && o2 == null){
183 return 0;
184 }else if (o1 == null){
185 return -1;
186 }else if (o2 == null){
187 return 1;
188 }
189 return Integer.compare(o1.getId(), o2.getId());
190 }
191 });
192 c = list;
193 }
194
195 if(start != null){
196 // return list
197 limit = (limit == null ? DEFAULT_PAGE_SIZE : limit);
198 Collection<CdmBase> sub_c = subCollection(c, start, limit);
199 return sub_c;
200 } else {
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);
205
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);
209 return p;
210 }
211 }
212
213 public Object getCdmBaseProperty(UUID uuid, String property, HttpServletResponse response) throws IOException{
214
215 T instance = HibernateProxyHelper.deproxy(getCdmBaseInstance(uuid, response, property));
216
217 Object objectFromProperty = invokeProperty(instance, property, response);
218
219 return objectFromProperty;
220 }
221
222 private Class<?> propertyClass(T instance, String baseName) {
223 PropertyDescriptor propertyDescriptor = null;
224 Class<?> c = null;
225 try {
226 propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);
227 if(propertyDescriptor != null){
228 c = propertyDescriptor.getClass();
229 }
230 } catch (IllegalAccessException e) {
231 // TODO Auto-generated catch block
232 e.printStackTrace();
233 } catch (InvocationTargetException e) {
234 // TODO Auto-generated catch block
235 e.printStackTrace();
236 } catch (NoSuchMethodException e) {
237 // TODO Auto-generated catch block
238 e.printStackTrace();
239 }
240 return c;
241 }
242
243 /**
244 * @param <SUB_T>
245 * @param clazz
246 * @param uuid
247 * @param response
248 * @param pathProperties
249 * @return
250 * @throws IOException
251 */
252 @SuppressWarnings("unchecked")
253 protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, List<String> pathProperties)
254 throws IOException {
255
256 CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperties);
257 if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){
258 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
259 }
260 return (SUB_T) cdmBaseObject;
261 }
262
263 /**
264 * @param <SUB_T>
265 * @param clazz
266 * @param uuid
267 * @param response
268 * @param pathProperty
269 * @return
270 * @throws IOException
271 */
272 @SuppressWarnings("unchecked")
273 protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, String pathProperty)
274 throws IOException {
275
276 CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperty);
277 if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){
278 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
279 }
280 return (SUB_T) cdmBaseObject;
281 }
282
283 /**
284 * @param uuid
285 * @param response
286 * @param pathProperty
287 * @return
288 * @throws IOException
289 */
290 protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, String pathProperty)
291 throws IOException {
292 return getCdmBaseInstance(baseClass, uuid, response, Arrays
293 .asList(new String[] { pathProperty }));
294 }
295
296
297 /**
298 * @param uuid
299 * @param response
300 * @param pathProperties
301 * @return
302 * @throws IOException
303 */
304 protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, List<String> pathProperties)
305 throws IOException {
306 return getCdmBaseInstance(baseClass, service, uuid, response, pathProperties);
307 }
308
309 /**
310 * @param <CDM_BASE>
311 * @param clazz
312 * @param service
313 * @param uuid
314 * @param response
315 * @param pathProperties
316 * @return
317 * @throws IOException
318 */
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)
322 throws IOException {
323
324 boolean includeUnpublished = NO_UNPUBLISHED;
325 CDM_BASE cdmBaseObject;
326 // if (service instanceof IPublishableService){
327 // cdmBaseObject = ((IPublishableService<CDM_BASE>)service).load(uuid, includeUnpublished, pathProperties);
328 // }else{
329 cdmBaseObject = service.load(uuid, pathProperties);
330 // }
331 if (cdmBaseObject == null) {
332 HttpStatusMessage.UUID_NOT_FOUND.send(response);
333 }
334 return cdmBaseObject;
335 }
336
337 /**
338 * @param instance
339 * @param baseName
340 * @param response
341 * @return
342 * @throws IOException
343 */
344 private final Object invokeProperty(T instance,
345 String baseName, HttpServletResponse response) throws IOException {
346
347 Object result = null;
348 try {
349 PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);
350 if(propertyDescriptor == null){
351 throw new NoSuchMethodException("No such method: " + instance.getClass().getSimpleName() + ".get" + baseName);
352 }
353 Method method = propertyDescriptor.getReadMethod();
354
355 Class<?> returnType = method.getReturnType();
356
357 if(CdmBase.class.isAssignableFrom(returnType)
358 || Collection.class.isAssignableFrom(returnType)
359 || Map.class.isAssignableFrom(returnType)
360 || INomenclaturalReference.class.isAssignableFrom(returnType)){
361
362 result = method.invoke(instance, (Object[])null);
363
364 result = HibernateProxyHelper.deproxy(result);
365
366 }else{
367 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
368 }
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);
380 }
381 return result;
382 }
383
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++){
389 sub_c.add(a[i]);
390 }
391 } else {
392 sub_c.addAll(c);
393 }
394 return sub_c;
395
396 }
397
398 /**
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.
401 * @param publishable
402 * @param includeUnpublished
403 * @param response
404 * @return
405 * @throws IOException
406 */
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);
413 publishable = null;
414 }
415 return publishable;
416 }
417
418
419 /* TODO implement
420
421 private Validator validator;
422
423 private javax.validation.Validator javaxValidator;
424
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()) {
430 throw new Error();
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
433 } else {
434 // requires merging detached object ?gilead?
435 service.save(object);
436 }
437
438 return object;
439 }
440
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());
445
446
447 Set<ConstraintViolation<T>> constraintViolations = javaxValidator.validate(object);
448 if (!constraintViolations.isEmpty()) {
449 throw new Error();
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
452 } else {
453 // requires merging detached object ?gilead?
454 service.save(object);
455 }
456
457 return object;
458 }
459
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
466 } else {
467 service.save(object);
468 }
469 }
470
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
477 }
478 }
479 */
480
481
482 }