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