refactoring taxon controllers, reducing code duplication
[cdmlib.git] / cdmlib-remote / src / main / java / eu / etaxonomy / cdm / remote / controller / BaseController.java
1 // $Id$
2 /**
3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10
11 package eu.etaxonomy.cdm.remote.controller;
12
13 import java.beans.PropertyDescriptor;
14 import java.io.IOException;
15 import java.lang.reflect.InvocationTargetException;
16 import java.lang.reflect.Method;
17 import java.lang.reflect.ParameterizedType;
18 import java.lang.reflect.Type;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.List;
23 import java.util.UUID;
24
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
27
28 import org.apache.commons.beanutils.PropertyUtils;
29 import org.apache.commons.io.FilenameUtils;
30 import org.apache.commons.lang.StringUtils;
31 import org.hibernate.mapping.Map;
32 import org.springframework.web.bind.WebDataBinder;
33 import org.springframework.web.bind.annotation.InitBinder;
34 import org.springframework.web.bind.annotation.PathVariable;
35 import org.springframework.web.bind.annotation.RequestMapping;
36 import org.springframework.web.bind.annotation.RequestMethod;
37 import org.springframework.web.bind.annotation.RequestParam;
38 import org.springframework.web.servlet.ModelAndView;
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 import eu.etaxonomy.cdm.remote.exception.NoRecordsMatchException;
49
50 /**
51 * based on org.cateproject.controller.common
52 * @author b.clark
53 * @author a.kohlbecker
54 *
55 * @param <T>
56 * @param <SERVICE>
57 */
58
59 public abstract class BaseController<T extends CdmBase, SERVICE extends IService<T>> extends AbstractController<T, SERVICE> {
60
61 /* protected SERVICE service;
62
63 public abstract void setService(SERVICE service);*/
64
65 protected Class<T> baseClass;
66
67 public BaseController (){
68
69 Type superClass = this.getClass().getGenericSuperclass();
70 if(superClass instanceof ParameterizedType){
71 ParameterizedType parametrizedSuperClass = (ParameterizedType) superClass;
72 Type[] typeArguments = parametrizedSuperClass.getActualTypeArguments();
73
74 if(typeArguments.length > 1 && typeArguments[0] instanceof Class<?>){
75 baseClass = (Class<T>) typeArguments[0];
76 } else {
77 logger.error("unable to find baseClass");
78 }
79 }
80 }
81
82 @InitBinder
83 public void initBinder(WebDataBinder binder) {
84 binder.registerCustomEditor(UUID.class, new UUIDPropertyEditor());
85 }
86
87 //TODO implement bulk version of this method
88 @RequestMapping(method = RequestMethod.GET)
89 public T doGet(@PathVariable("uuid") UUID uuid,
90 HttpServletRequest request,
91 HttpServletResponse response) throws IOException {
92 if(request != null) {
93 logger.info("doGet() " + request.getRequestURI());
94 }
95 T obj = getCdmBaseInstance(uuid, response, initializationStrategy);
96 return obj;
97 }
98
99 /**
100 * @param uuid
101 * @param request
102 * @param response
103 * @return
104 * @throws IOException
105 *
106 * TODO implement bulk version of this method
107 */
108 @RequestMapping(value = "*", method = RequestMethod.GET)
109 public ModelAndView doGetMethod(
110 @PathVariable("uuid") UUID uuid,
111 // doPage request parametes
112 @RequestParam(value = "pageNumber", required = false) Integer pageNumber,
113 @RequestParam(value = "pageSize", required = false) Integer pageSize,
114 // doList request parametes
115 @RequestParam(value = "start", required = false) Integer start,
116 @RequestParam(value = "limit", required = false) Integer limit,
117 HttpServletRequest request,
118 HttpServletResponse response) throws IOException {
119
120 ModelAndView modelAndView = new ModelAndView();
121
122 String servletPath = request.getServletPath();
123 String baseName = FilenameUtils.getBaseName(servletPath);
124
125 if(request != null) {
126 logger.info("doGetMethod()[doGet" + StringUtils.capitalize(baseName) + "] " + request.getRequestURI());
127 }
128
129 // <CUT
130 // T instance = getCdmBaseInstance(uuid, response, (List<String>)null);
131
132 //Class<?> propertyClass = propertyClass(instance, baseName);
133
134 Object objectFromProperty = getCdmBaseProperty(uuid, baseName, response);// invokeProperty(instance, baseName, response);
135
136 // CUT>
137
138 if(objectFromProperty != null){
139
140 if( Collection.class.isAssignableFrom(objectFromProperty.getClass())){
141 // Map types cannot be returned as list or in a pager!
142
143 Collection c = (Collection)objectFromProperty;
144 if(start != null){
145 // return list
146 limit = (limit == null ? DEFAULT_PAGE_SIZE : limit);
147 Collection sub_c = subCollection(c, start, limit);
148 modelAndView.addObject(sub_c);
149
150 } else {
151 //FIXME use real paging mechanism of according service class instead of subCollection()
152 //FIXME use BaseListController.normalizeAndValidatePagerParameters(pageNumber, pageSize, response);
153 PagerParameters pagerParameters = new PagerParameters(pageSize, pageNumber);
154 pagerParameters.normalizeAndValidate(response);
155
156 start = pagerParameters.getPageIndex() * pagerParameters.getPageSize();
157 List sub_c = subCollection(c, start, pagerParameters.getPageSize());
158 Pager p = new DefaultPagerImpl(pageNumber, c.size(), pagerParameters.getPageSize(), sub_c);
159 modelAndView.addObject(p);
160 }
161
162 } else {
163 modelAndView.addObject(objectFromProperty);
164 }
165
166 }
167
168 if(modelAndView.isEmpty()){
169 return null;
170 } else {
171
172 return modelAndView;
173 }
174 }
175
176 public Object getCdmBaseProperty(UUID uuid, String property, HttpServletResponse response) throws IOException{
177
178 T instance = (T) HibernateProxyHelper.deproxy(getCdmBaseInstance(uuid, response, property));
179
180 Object objectFromProperty = invokeProperty(instance, property, response);
181
182 return objectFromProperty;
183 }
184
185 private Class<?> propertyClass(T instance, String baseName) {
186 PropertyDescriptor propertyDescriptor = null;
187 Class<?> c = null;
188 try {
189 propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);
190 if(propertyDescriptor != null){
191 c = propertyDescriptor.getClass();
192 }
193 } catch (IllegalAccessException e) {
194 // TODO Auto-generated catch block
195 e.printStackTrace();
196 } catch (InvocationTargetException e) {
197 // TODO Auto-generated catch block
198 e.printStackTrace();
199 } catch (NoSuchMethodException e) {
200 // TODO Auto-generated catch block
201 e.printStackTrace();
202 }
203 return c;
204 }
205
206 /**
207 * @param <SUB_T>
208 * @param clazz
209 * @param uuid
210 * @param response
211 * @param pathProperties
212 * @return
213 * @throws IOException
214 */
215 @SuppressWarnings("unchecked")
216 protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, List<String> pathProperties)
217 throws IOException {
218
219 CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperties);
220 if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){
221 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
222 }
223 return (SUB_T) cdmBaseObject;
224 }
225
226 /**
227 * @param <SUB_T>
228 * @param clazz
229 * @param uuid
230 * @param response
231 * @param pathProperty
232 * @return
233 * @throws IOException
234 */
235 @SuppressWarnings("unchecked")
236 protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, String pathProperty)
237 throws IOException {
238
239 CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperty);
240 if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){
241 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
242 }
243 return (SUB_T) cdmBaseObject;
244 }
245
246 /**
247 * @param uuid
248 * @param response
249 * @param pathProperty
250 * @return
251 * @throws IOException
252 */
253 protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, String pathProperty)
254 throws IOException {
255 return getCdmBaseInstance(baseClass, uuid, response, Arrays
256 .asList(new String[] { pathProperty }));
257 }
258
259
260 /**
261 * @param uuid
262 * @param response
263 * @param pathProperties
264 * @return
265 * @throws IOException
266 */
267 protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, List<String> pathProperties)
268 throws IOException {
269 return getCdmBaseInstance(baseClass, service, uuid, response, pathProperties);
270 }
271
272 /**
273 * @param <CDM_BASE>
274 * @param clazz
275 * @param service
276 * @param uuid
277 * @param response
278 * @param pathProperties
279 * @return
280 * @throws IOException
281 */
282 protected final <CDM_BASE extends CdmBase> CDM_BASE getCdmBaseInstance(Class<CDM_BASE> clazz, IService<CDM_BASE> service, UUID uuid, HttpServletResponse response, List<String> pathProperties)
283 throws IOException {
284
285 CDM_BASE cdmBaseObject = service.load(uuid, pathProperties);
286 if (cdmBaseObject == null) {
287 HttpStatusMessage.UUID_NOT_FOUND.send(response);
288 throw new NoRecordsMatchException("No instance found for UUID " + uuid.toString());
289 }
290 return cdmBaseObject;
291 }
292
293 /**
294 * @param instance
295 * @param baseName
296 * @param response
297 * @return
298 * @throws IOException
299 */
300 private final Object invokeProperty(T instance,
301 String baseName, HttpServletResponse response) throws IOException {
302
303 Object result = null;
304 try {
305 PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);
306 if(propertyDescriptor == null){
307 throw new NoSuchMethodException("No such method: " + instance.getClass().getSimpleName() + ".get" + baseName);
308 }
309 Method method = propertyDescriptor.getReadMethod();
310
311 Class<?> returnType = method.getReturnType();
312
313 if(CdmBase.class.isAssignableFrom(returnType)
314 || Collection.class.isAssignableFrom(returnType)
315 || Map.class.isAssignableFrom(returnType)
316 || INomenclaturalReference.class.isAssignableFrom(returnType)){
317
318 result = method.invoke(instance, (Object[])null);
319
320 result = HibernateProxyHelper.deproxy(result);
321
322 }else{
323 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
324 }
325 } catch (SecurityException e) {
326 logger.error("SecurityException: ", e);
327 HttpStatusMessage.INTERNAL_ERROR.send(response);
328 } catch (NoSuchMethodException e) {
329 HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
330 } catch (IllegalArgumentException e) {
331 HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
332 } catch (IllegalAccessException e) {
333 HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
334 } catch (InvocationTargetException e) {
335 HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
336 }
337 return result;
338 }
339
340 private <E> List<E> subCollection(Collection<? extends E> c, Integer start, Integer length){
341 List<E> sub_c = new ArrayList<E>(length);
342 if(c.size() > length){
343 E[] a = (E[]) c.toArray();
344 for(int i = start; i < start + length; i++){
345 sub_c.add(a[i]);
346 }
347 } else {
348 sub_c.addAll(c);
349 }
350 return sub_c;
351
352 }
353
354
355 /* TODO implement
356
357 private Validator validator;
358
359 private javax.validation.Validator javaxValidator;
360
361 @RequestMapping(method = RequestMethod.PUT, headers="content-type=multipart/form-data")
362 public T doPutForm(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
363 object.setUuid(uuid);
364 validator.validate(object, result);
365 if (result.hasErrors()) {
366 throw new Error();
367 // set http status code depending upon what happened, possibly return
368 // the put object and errors so that they can be rendered into a suitable error response
369 } else {
370 // requires merging detached object ?gilead?
371 service.save(object);
372 }
373
374 return object;
375 }
376
377 @RequestMapping(method = RequestMethod.PUT, headers="content-type=text/json")
378 public T doPutJSON(@PathVariable(value = "uuid") UUID uuid, @RequestBody String jsonMessage) {
379 JSONObject jsonObject = JSONObject.fromObject(jsonMessage);
380 T object = (T)JSONObject.toBean(jsonObject, this.getClass());
381
382
383 Set<ConstraintViolation<T>> constraintViolations = javaxValidator.validate(object);
384 if (!constraintViolations.isEmpty()) {
385 throw new Error();
386 // set http status code depending upon what happened, possibly return
387 // the put object and errors so that they can be rendered into a suitable error response
388 } else {
389 // requires merging detached object ?gilead?
390 service.save(object);
391 }
392
393 return object;
394 }
395
396 @RequestMapping(method = RequestMethod.PUT) // the cdm-server may not allow clients to specify the uuid for resources
397 public T doPut(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
398 validator.validate(object, result);
399 if (result.hasErrors()) {
400 // set http status code depending upon what happened, possibly return
401 // the put object and errors so that they can be rendered into a suitable error response
402 } else {
403 service.save(object);
404 }
405 }
406
407 @RequestMapping(method = RequestMethod.DELETE)
408 public void doDelete(@PathVariable(value = "uuid") UUID uuid) {
409 T object = service.find(uuid);
410 // provided the object exists
411 service.delete(uuid);
412 // might return 204 or 200
413 }
414 }
415 */
416
417
418 }