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