a more generic BaseController
[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.TypeVariable;
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.Collections;
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.model.common.CdmBase;
44 import eu.etaxonomy.cdm.remote.editor.UUIDPropertyEditor;
45
46 /**
47 * based on org.cateproject.controller.common
48 * @author b.clark
49 * @author a.kohlbecker
50 *
51 * @param <T>
52 * @param <SERVICE>
53 */
54
55 public abstract class BaseController<T extends CdmBase, SERVICE extends IService<T>> extends AbstractController {
56
57 protected SERVICE service;
58
59 protected Class<T> baseClass;
60
61 public abstract void setService(SERVICE service);
62
63 @InitBinder
64 public void initBinder(WebDataBinder binder) {
65 binder.registerCustomEditor(UUID.class, new UUIDPropertyEditor());
66 }
67
68 //TODO implement bulk version of this method
69 @RequestMapping(method = RequestMethod.GET)
70 public T doGet(@PathVariable("uuid") UUID uuid,
71 HttpServletRequest request,
72 HttpServletResponse response) throws IOException {
73 logger.info("doGet() " + request.getServletPath());
74 T obj = (T) getCdmBaseInstance(uuid, response, initializationStrategy);
75 return obj;
76 }
77
78 /**
79 * @param uuid
80 * @param request
81 * @param response
82 * @return
83 * @throws IOException
84 *
85 * TODO implement bulk version of this method
86 */
87 @RequestMapping(value = "*", method = RequestMethod.GET)
88 public ModelAndView doGetMethod(
89 @PathVariable("uuid") UUID uuid,
90 // doPage request parametes
91 @RequestParam(value = "pageNumber", required = false) Integer pageNumber,
92 @RequestParam(value = "pageSize", required = false) Integer pageSize,
93 // doList request parametes
94 @RequestParam(value = "start", required = false) Integer start,
95 @RequestParam(value = "limit", required = false) Integer limit,
96 HttpServletRequest request,
97 HttpServletResponse response) throws IOException {
98
99 ModelAndView modelAndView = null;
100
101 String servletPath = request.getServletPath();
102 String baseName = FilenameUtils.getBaseName(servletPath);
103
104 logger.info("doGetMethod()[doGet" + StringUtils.capitalize(baseName) + "] " + request.getServletPath());
105
106 T instance = getCdmBaseInstance(uuid, response, Arrays.asList(new String[]{baseName + ".titleCache"}));
107
108 Object objectFromProperty = invokeProperty(instance, baseName, response);
109
110 if(objectFromProperty != null){
111
112 modelAndView = new ModelAndView();
113
114 if( Collection.class.isAssignableFrom(objectFromProperty.getClass())){
115 // Map types cannot be returend as list or in a pager!
116 Collection c = (Collection)objectFromProperty;
117 if(start != null){
118 // return list
119 limit = (limit == null ? DEFAULT_PAGE_SIZE : limit);
120 Collection sub_c = subCollection(c, start, limit);
121 modelAndView.addObject(sub_c);
122
123 } else {
124 pageSize = (pageSize == null ? DEFAULT_PAGE_SIZE : pageSize);
125 pageNumber = (pageNumber == null ? 0 : pageNumber);
126 start = pageNumber * pageSize;
127 List sub_c = subCollection(c, start, pageSize);
128 Pager p = new DefaultPagerImpl(pageNumber, c.size(), pageSize, sub_c);
129 modelAndView.addObject(p);
130 }
131
132 } else {
133 modelAndView.addObject(objectFromProperty);
134 }
135
136 }
137
138 return modelAndView;
139 }
140
141 /**
142 * @param <SUB_T>
143 * @param clazz
144 * @param uuid
145 * @param response
146 * @param pathProperties
147 * @return
148 * @throws IOException
149 */
150 @SuppressWarnings("unchecked")
151 protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, List<String> pathProperties)
152 throws IOException {
153
154 CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperties);
155 if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){
156 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
157 }
158 return (SUB_T) cdmBaseObject;
159 }
160
161 /**
162 * @param <SUB_T>
163 * @param clazz
164 * @param uuid
165 * @param response
166 * @param pathProperty
167 * @return
168 * @throws IOException
169 */
170 @SuppressWarnings("unchecked")
171 protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, String pathProperty)
172 throws IOException {
173
174 CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperty);
175 if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){
176 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
177 }
178 return (SUB_T) cdmBaseObject;
179 }
180
181 /**
182 * @param uuid
183 * @param response
184 * @param pathProperty
185 * @return
186 * @throws IOException
187 */
188 protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, String pathProperty)
189 throws IOException {
190 return getCdmBaseInstance(baseClass, uuid, response, Arrays
191 .asList(new String[] { pathProperty }));
192 }
193
194
195 /**
196 * @param uuid
197 * @param response
198 * @param pathProperties
199 * @return
200 * @throws IOException
201 */
202 protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, List<String> pathProperties)
203 throws IOException {
204 return getCdmBaseInstance(baseClass, service, uuid, response, pathProperties);
205 }
206
207 /**
208 * @param <CDM_BASE>
209 * @param clazz
210 * @param service
211 * @param uuid
212 * @param response
213 * @param pathProperties
214 * @return
215 * @throws IOException
216 */
217 protected final <CDM_BASE extends CdmBase> CDM_BASE getCdmBaseInstance(Class<CDM_BASE> clazz, IService<CDM_BASE> service, UUID uuid, HttpServletResponse response, List<String> pathProperties)
218 throws IOException {
219
220 CDM_BASE cdmBaseObject = service.load(uuid, pathProperties);
221 if (cdmBaseObject == null) {
222 HttpStatusMessage.UUID_NOT_FOUND.send(response);
223 }
224 return cdmBaseObject;
225 }
226
227 /**
228 * @param instance
229 * @param baseName
230 * @param response
231 * @return
232 * @throws IOException
233 */
234 private final Object invokeProperty(T instance,
235 String baseName, HttpServletResponse response) throws IOException {
236
237 Object result = null;
238 try {
239 PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);
240 Method method = propertyDescriptor.getReadMethod();
241
242 Class<?> returnType = method.getReturnType();
243
244 if(CdmBase.class.isAssignableFrom(returnType)
245 || Collection.class.isAssignableFrom(returnType)
246 || Map.class.isAssignableFrom(returnType)){
247 result = method.invoke(instance, (Object[])null);
248 }else{
249 HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
250 }
251 } catch (SecurityException e) {
252 logger.error("SecurityException: ", e);
253 HttpStatusMessage.INTERNAL_ERROR.send(response);
254 } catch (NoSuchMethodException e) {
255 HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
256 } catch (IllegalArgumentException e) {
257 HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
258 } catch (IllegalAccessException e) {
259 HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
260 } catch (InvocationTargetException e) {
261 HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
262 }
263 return result;
264 }
265
266 private <E> List<E> subCollection(Collection<? extends E> c, Integer start, Integer length){
267 List<E> sub_c = new ArrayList<E>(length);
268 if(c.size() > length){
269 E[] a = (E[]) c.toArray();
270 for(int i = start; i < start + length; i++){
271 sub_c.add(a[i]);
272 }
273 } else {
274 sub_c.addAll(c);
275 }
276 return sub_c;
277
278 }
279
280
281 /* TODO implement
282
283 private Validator validator;
284
285 private javax.validation.Validator javaxValidator;
286
287 @RequestMapping(method = RequestMethod.PUT, headers="content-type=multipart/form-data")
288 public T doPutForm(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
289 object.setUuid(uuid);
290 validator.validate(object, result);
291 if (result.hasErrors()) {
292 throw new Error();
293 // set http status code depending upon what happened, possibly return
294 // the put object and errors so that they can be rendered into a suitable error response
295 } else {
296 // requires merging detached object ?gilead?
297 service.save(object);
298 }
299
300 return object;
301 }
302
303 @RequestMapping(method = RequestMethod.PUT, headers="content-type=text/json")
304 public T doPutJSON(@PathVariable(value = "uuid") UUID uuid, @RequestBody String jsonMessage) {
305 JSONObject jsonObject = JSONObject.fromObject(jsonMessage);
306 T object = (T)JSONObject.toBean(jsonObject, this.getClass());
307
308
309 Set<ConstraintViolation<T>> constraintViolations = javaxValidator.validate(object);
310 if (!constraintViolations.isEmpty()) {
311 throw new Error();
312 // set http status code depending upon what happened, possibly return
313 // the put object and errors so that they can be rendered into a suitable error response
314 } else {
315 // requires merging detached object ?gilead?
316 service.save(object);
317 }
318
319 return object;
320 }
321
322 @RequestMapping(method = RequestMethod.PUT) // the cdm-server may not allow clients to specify the uuid for resources
323 public T doPut(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
324 validator.validate(object, result);
325 if (result.hasErrors()) {
326 // set http status code depending upon what happened, possibly return
327 // the put object and errors so that they can be rendered into a suitable error response
328 } else {
329 service.save(object);
330 }
331 }
332
333 @RequestMapping(method = RequestMethod.DELETE)
334 public void doDelete(@PathVariable(value = "uuid") UUID uuid) {
335 T object = service.find(uuid);
336 // provided the object exists
337 service.delete(uuid);
338 // might return 204 or 200
339 }
340 }
341 */
342
343
344 }