cleanup
[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.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;
39
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
49 /**
50 * based on org.cateproject.controller.common
51 * @author b.clark
52 * @author a.kohlbecker
53 *
54 * @param <T>
55 * @param <SERVICE>
56 */
57
58 public abstract class BaseController<T extends CdmBase, SERVICE extends IService<T>> extends AbstractController<T, SERVICE> {
59
60 /* protected SERVICE service;
61
62 public abstract void setService(SERVICE service);*/
63
64 protected Class<T> baseClass;
65
66 @SuppressWarnings("unchecked")
67 public BaseController (){
68
69 Type superClass = this.getClass().getGenericSuperclass();
70 while(true){
71 if(superClass instanceof ParameterizedType){
72 ParameterizedType parametrizedSuperClass = (ParameterizedType) superClass;
73 Type[] typeArguments = parametrizedSuperClass.getActualTypeArguments();
74
75 if(typeArguments.length > 1 && typeArguments[0] instanceof Class<?>){
76 baseClass = (Class<T>) typeArguments[0];
77 } else {
78 logger.error("unable to find baseClass");
79 }
80 break;
81 } else if(superClass instanceof Class<?>){
82 superClass = ((Class<?>) superClass).getGenericSuperclass();
83 } else {
84 // no point digging deeper if neither Class or ParameterizedType
85 logger.error("unable to find baseClass");
86 break;
87 }
88 }
89 }
90
91 @InitBinder
92 public void initBinder(WebDataBinder binder) {
93 binder.registerCustomEditor(UUID.class, new UUIDPropertyEditor());
94 }
95
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());
103 }
104 T obj = getCdmBaseInstance(uuid, response, initializationStrategy);
105 return obj;
106 }
107
108 /**
109 * @param uuid
110 * @param request
111 * @param response
112 * @return
113 * @throws IOException
114 *
115 * TODO implement bulk version of this method
116 */
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 {
128
129 String servletPath = request.getServletPath();
130 String baseName = FilenameUtils.getBaseName(servletPath);
131
132 if(request != null) {
133 logger.info("doGetMethod()[doGet" + StringUtils.capitalize(baseName) + "] " + requestPathAndQuery(request));
134 }
135
136 // <CUT
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);
140 // CUT>
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);
145 } else {
146 return objectFromProperty;
147 }
148 }
149 return null;
150 }
151
152 /**
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>.
155 *
156 * @param pageNumber
157 * @param pageSize
158 * @param start
159 * @param limit
160 * @param response
161 * @param objectFromProperty
162 * @return either a List or Pager depending on the parameter combination.
163 *
164 * @throws IOException
165 */
166 protected Object pageFromCollection(Collection<? extends CdmBase> c, Integer pageNumber, Integer pageSize, Integer start,
167 Integer limit, HttpServletResponse response) throws IOException {
168
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>() {
173
174 @Override
175 public int compare(CdmBase o1, CdmBase o2) {
176 if (o1 == null && o2 == null){
177 return 0;
178 }else if (o1 == null){
179 return -1;
180 }else if (o2 == null){
181 return 1;
182 }
183 return Integer.compare(o1.getId(), o2.getId());
184 }
185 });
186 c = list;
187 }
188
189 if(start != null){
190 // return list
191 limit = (limit == null ? DEFAULT_PAGE_SIZE : limit);
192 Collection<CdmBase> sub_c = subCollection(c, start, limit);
193 return sub_c;
194 } else {
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);
199
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);
203 return p;
204 }
205 }
206
207 public Object getCdmBaseProperty(UUID uuid, String property, HttpServletResponse response) throws IOException{
208
209 T instance = HibernateProxyHelper.deproxy(getCdmBaseInstance(uuid, response, property));
210
211 Object objectFromProperty = invokeProperty(instance, property, response);
212
213 return objectFromProperty;
214 }
215
216 private Class<?> propertyClass(T instance, String baseName) {
217 PropertyDescriptor propertyDescriptor = null;
218 Class<?> c = null;
219 try {
220 propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);
221 if(propertyDescriptor != null){
222 c = propertyDescriptor.getClass();
223 }
224 } catch (IllegalAccessException e) {
225 // TODO Auto-generated catch block
226 e.printStackTrace();
227 } catch (InvocationTargetException e) {
228 // TODO Auto-generated catch block
229 e.printStackTrace();
230 } catch (NoSuchMethodException e) {
231 // TODO Auto-generated catch block
232 e.printStackTrace();
233 }
234 return c;
235 }
236
237 /**
238 * @param <SUB_T>
239 * @param clazz
240 * @param uuid
241 * @param response
242 * @param pathProperties
243 * @return
244 * @throws IOException
245 */
246 @SuppressWarnings("unchecked")
247 protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, List<String> pathProperties)
248 throws IOException {
249
250 CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperties);
251 if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){
252 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
253 }
254 return (SUB_T) cdmBaseObject;
255 }
256
257 /**
258 * @param <SUB_T>
259 * @param clazz
260 * @param uuid
261 * @param response
262 * @param pathProperty
263 * @return
264 * @throws IOException
265 */
266 @SuppressWarnings("unchecked")
267 protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, String pathProperty)
268 throws IOException {
269
270 CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperty);
271 if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){
272 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
273 }
274 return (SUB_T) cdmBaseObject;
275 }
276
277 /**
278 * @param uuid
279 * @param response
280 * @param pathProperty
281 * @return
282 * @throws IOException
283 */
284 protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, String pathProperty)
285 throws IOException {
286 return getCdmBaseInstance(baseClass, uuid, response, Arrays
287 .asList(new String[] { pathProperty }));
288 }
289
290
291 /**
292 * @param uuid
293 * @param response
294 * @param pathProperties
295 * @return
296 * @throws IOException
297 */
298 protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, List<String> pathProperties)
299 throws IOException {
300 return getCdmBaseInstance(baseClass, service, uuid, response, pathProperties);
301 }
302
303 /**
304 * @param <CDM_BASE>
305 * @param clazz
306 * @param service
307 * @param uuid
308 * @param response
309 * @param pathProperties
310 * @return
311 * @throws IOException
312 */
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)
314 throws IOException {
315
316 CDM_BASE cdmBaseObject = service.load(uuid, pathProperties);
317 if (cdmBaseObject == null) {
318 HttpStatusMessage.UUID_NOT_FOUND.send(response);
319 }
320 return cdmBaseObject;
321 }
322
323 /**
324 * @param instance
325 * @param baseName
326 * @param response
327 * @return
328 * @throws IOException
329 */
330 private final Object invokeProperty(T instance,
331 String baseName, HttpServletResponse response) throws IOException {
332
333 Object result = null;
334 try {
335 PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);
336 if(propertyDescriptor == null){
337 throw new NoSuchMethodException("No such method: " + instance.getClass().getSimpleName() + ".get" + baseName);
338 }
339 Method method = propertyDescriptor.getReadMethod();
340
341 Class<?> returnType = method.getReturnType();
342
343 if(CdmBase.class.isAssignableFrom(returnType)
344 || Collection.class.isAssignableFrom(returnType)
345 || Map.class.isAssignableFrom(returnType)
346 || INomenclaturalReference.class.isAssignableFrom(returnType)){
347
348 result = method.invoke(instance, (Object[])null);
349
350 result = HibernateProxyHelper.deproxy(result);
351
352 }else{
353 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
354 }
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);
366 }
367 return result;
368 }
369
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++){
375 sub_c.add(a[i]);
376 }
377 } else {
378 sub_c.addAll(c);
379 }
380 return sub_c;
381
382 }
383
384
385 /* TODO implement
386
387 private Validator validator;
388
389 private javax.validation.Validator javaxValidator;
390
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()) {
396 throw new Error();
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
399 } else {
400 // requires merging detached object ?gilead?
401 service.save(object);
402 }
403
404 return object;
405 }
406
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());
411
412
413 Set<ConstraintViolation<T>> constraintViolations = javaxValidator.validate(object);
414 if (!constraintViolations.isEmpty()) {
415 throw new Error();
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
418 } else {
419 // requires merging detached object ?gilead?
420 service.save(object);
421 }
422
423 return object;
424 }
425
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
432 } else {
433 service.save(object);
434 }
435 }
436
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
443 }
444 }
445 */
446
447
448 }