// $Id$\r
/**\r
* Copyright (C) 2007 EDIT\r
-* European Distributed Institute of Taxonomy \r
+* European Distributed Institute of Taxonomy\r
* http://www.e-taxonomy.eu\r
-* \r
+*\r
* The contents of this file are subject to the Mozilla Public License Version 1.1\r
* See LICENSE.TXT at the top of this package for the full license terms.\r
*/\r
\r
package eu.etaxonomy.cdm.remote.controller;\r
\r
+import java.beans.PropertyDescriptor;\r
import java.io.IOException;\r
+import java.lang.reflect.InvocationTargetException;\r
+import java.lang.reflect.Method;\r
+import java.lang.reflect.ParameterizedType;\r
+import java.lang.reflect.Type;\r
+import java.util.ArrayList;\r
import java.util.Arrays;\r
+import java.util.Collection;\r
import java.util.List;\r
import java.util.UUID;\r
-import java.util.regex.Matcher;\r
-import java.util.regex.Pattern;\r
\r
import javax.servlet.http.HttpServletRequest;\r
import javax.servlet.http.HttpServletResponse;\r
\r
-import org.apache.log4j.Logger;\r
-import org.springframework.util.Assert;\r
+import org.apache.commons.beanutils.PropertyUtils;\r
+import org.apache.commons.io.FilenameUtils;\r
+import org.apache.commons.lang.StringUtils;\r
+import org.hibernate.mapping.Map;\r
+import org.springframework.web.bind.WebDataBinder;\r
+import org.springframework.web.bind.annotation.InitBinder;\r
+import org.springframework.web.bind.annotation.PathVariable;\r
import org.springframework.web.bind.annotation.RequestMapping;\r
import org.springframework.web.bind.annotation.RequestMethod;\r
+import org.springframework.web.bind.annotation.RequestParam;\r
+import org.springframework.web.servlet.ModelAndView;\r
\r
import eu.etaxonomy.cdm.api.service.IService;\r
+import eu.etaxonomy.cdm.api.service.pager.Pager;\r
+import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;\r
+import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;\r
import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;\r
+import eu.etaxonomy.cdm.remote.controller.util.PagerParameters;\r
+import eu.etaxonomy.cdm.remote.editor.UUIDPropertyEditor;\r
+import eu.etaxonomy.cdm.remote.exception.NoRecordsMatchException;\r
\r
-//$Id$\r
/**\r
* based on org.cateproject.controller.common\r
* @author b.clark\r
* @param <SERVICE>\r
*/\r
\r
-public abstract class BaseController<T extends CdmBase, SERVICE extends IService<T>> {\r
- \r
- public static final Logger logger = Logger.getLogger(BaseController.class);\r
- \r
- protected static final Integer DEFAULT_PAGE_SIZE = 30;\r
- \r
- protected SERVICE service;\r
- \r
- protected Pattern uuidParameterPattern = null;\r
- \r
- protected List<String> initializationStrategy = null;\r
-\r
- public abstract void setService(SERVICE service);\r
- \r
- protected void setUuidParameterPattern(String pattern){\r
- uuidParameterPattern = Pattern.compile(pattern);\r
- }\r
- \r
- public void setInitializationStrategy(List<String> initializationStrategy) {\r
- this.initializationStrategy = initializationStrategy;\r
- }\r
-\r
- /**@InitBinder\r
+public abstract class BaseController<T extends CdmBase, SERVICE extends IService<T>> extends AbstractController<T, SERVICE> {\r
+\r
+/* protected SERVICE service;\r
+\r
+ public abstract void setService(SERVICE service);*/\r
+\r
+ protected Class<T> baseClass;\r
+\r
+ public BaseController (){\r
+\r
+ Type superClass = this.getClass().getGenericSuperclass();\r
+ if(superClass instanceof ParameterizedType){\r
+ ParameterizedType parametrizedSuperClass = (ParameterizedType) superClass;\r
+ Type[] typeArguments = parametrizedSuperClass.getActualTypeArguments();\r
+\r
+ if(typeArguments.length > 1 && typeArguments[0] instanceof Class<?>){\r
+ baseClass = (Class<T>) typeArguments[0];\r
+ } else {\r
+ logger.error("unable to find baseClass");\r
+ }\r
+ }\r
+ }\r
+\r
+ @InitBinder\r
public void initBinder(WebDataBinder binder) {\r
- binder.registerCustomEditor(UUID.class, new UUIDPropertyEditor());\r
- //TODO do we need this one?: binder.registerCustomEditor(Class.class, new ClassPropertyEditor());\r
- }\r
- */\r
- \r
- @RequestMapping(method = RequestMethod.GET)\r
- public T doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {\r
- T obj = (T) getCdmBase(request, response, initializationStrategy, CdmBase.class);\r
- return obj;\r
- }\r
-\r
- \r
- protected UUID readValueUuid(HttpServletRequest request) {\r
- String path = request.getServletPath();\r
- if(path != null) {\r
- Matcher uuidMatcher = uuidParameterPattern.matcher(path);\r
- if(uuidMatcher.matches() && uuidMatcher.groupCount() > 0){\r
- try {\r
- UUID uuid = UUID.fromString(uuidMatcher.group(1));\r
- return uuid;\r
- } catch (Exception e) {\r
- throw new IllegalArgumentException(HttpStatusMessage.UUID_INVALID.toString());\r
- }\r
- } else {\r
- throw new IllegalArgumentException(HttpStatusMessage.UUID_MISSING.toString());\r
- }\r
- }\r
- return null;\r
- }\r
- \r
-\r
- /**\r
- * @param request\r
- * @param response\r
- * @param obj\r
- * @return\r
- * @throws IOException\r
- */\r
- protected <CDM_BASE> CDM_BASE getCdmBase(HttpServletRequest request, HttpServletResponse response, \r
- List<String> initStrategy, Class<CDM_BASE> clazz) throws IOException {\r
- T obj = null;\r
- try {\r
- UUID uuid = readValueUuid(request);\r
- Assert.notNull(uuid, HttpStatusMessage.UUID_MISSING.toString());\r
- \r
- if(initStrategy != null){\r
- obj = service.load(uuid, initStrategy);\r
- } else { \r
- obj = service.findByUuid(uuid);\r
- }\r
- Assert.notNull(obj, HttpStatusMessage.UUID_NOT_FOUND.toString());\r
- \r
- } catch (IllegalArgumentException iae) {\r
- HttpStatusMessage.fromString(iae.getMessage()).send(response);\r
- }\r
- CDM_BASE t;\r
- try {\r
- t = (CDM_BASE)obj;\r
- return t;\r
- } catch (Exception e) {\r
- HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);\r
- return null;\r
- }\r
- }\r
-\r
- /* TODO implement\r
- * \r
- @RequestMapping(method = RequestMethod.POST)\r
- public T doPost(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {\r
- validator.validate(object, result);\r
- if (result.hasErrors()) {\r
- // set http status code depending upon what happened, possibly return\r
- // the put object and errors so that they can be rendered into a suitable error response\r
- } else {\r
- // requires merging detached object ?gilead?\r
- service.update(object);\r
- }\r
- }\r
-\r
- @RequestMapping(method = RequestMethod.PUT) // the cdm-server may not allow clients to specify the uuid for resources\r
- public T doPut(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {\r
- validator.validate(object, result);\r
- if (result.hasErrors()) {\r
- // set http status code depending upon what happened, possibly return\r
- // the put object and errors so that they can be rendered into a suitable error response\r
- } else {\r
- service.save(object);\r
- }\r
- }\r
-\r
- @RequestMapping(method = RequestMethod.DELETE)\r
- public void doDelete(@PathVariable(value = "uuid") UUID uuid) {\r
- T object = service.find(uuid);\r
- // provided the object exists\r
- service.delete(uuid);\r
- // might return 204 or 200\r
- }\r
- }\r
+ binder.registerCustomEditor(UUID.class, new UUIDPropertyEditor());\r
+ }\r
+\r
+ //TODO implement bulk version of this method\r
+ @RequestMapping(method = RequestMethod.GET)\r
+ public T doGet(@PathVariable("uuid") UUID uuid,\r
+ HttpServletRequest request,\r
+ HttpServletResponse response) throws IOException {\r
+ if(request != null) {\r
+ logger.info("doGet() " + request.getRequestURI());\r
+ }\r
+ T obj = getCdmBaseInstance(uuid, response, initializationStrategy);\r
+ return obj;\r
+ }\r
+\r
+ /**\r
+ * @param uuid\r
+ * @param request\r
+ * @param response\r
+ * @return\r
+ * @throws IOException\r
+ *\r
+ * TODO implement bulk version of this method\r
+ */\r
+ @RequestMapping(value = "*", method = RequestMethod.GET)\r
+ public ModelAndView doGetMethod(\r
+ @PathVariable("uuid") UUID uuid,\r
+ // doPage request parametes\r
+ @RequestParam(value = "pageNumber", required = false) Integer pageNumber,\r
+ @RequestParam(value = "pageSize", required = false) Integer pageSize,\r
+ // doList request parametes\r
+ @RequestParam(value = "start", required = false) Integer start,\r
+ @RequestParam(value = "limit", required = false) Integer limit,\r
+ HttpServletRequest request,\r
+ HttpServletResponse response) throws IOException {\r
+\r
+ ModelAndView modelAndView = new ModelAndView();\r
+\r
+ String servletPath = request.getServletPath();\r
+ String baseName = FilenameUtils.getBaseName(servletPath);\r
+\r
+ if(request != null) {\r
+ logger.info("doGetMethod()[doGet" + StringUtils.capitalize(baseName) + "] " + request.getRequestURI());\r
+ }\r
+\r
+ // <CUT\r
+// T instance = getCdmBaseInstance(uuid, response, (List<String>)null);\r
+\r
+ //Class<?> propertyClass = propertyClass(instance, baseName);\r
+\r
+ Object objectFromProperty = getCdmBaseProperty(uuid, baseName, response);// invokeProperty(instance, baseName, response);\r
+\r
+ // CUT>\r
+\r
+ if(objectFromProperty != null){\r
+\r
+ if( Collection.class.isAssignableFrom(objectFromProperty.getClass())){\r
+ // Map types cannot be returned as list or in a pager!\r
+\r
+ Collection c = (Collection)objectFromProperty;\r
+ if(start != null){\r
+ // return list\r
+ limit = (limit == null ? DEFAULT_PAGE_SIZE : limit);\r
+ Collection sub_c = subCollection(c, start, limit);\r
+ modelAndView.addObject(sub_c);\r
+\r
+ } else {\r
+ //FIXME use real paging mechanism of according service class instead of subCollection()\r
+ //FIXME use BaseListController.normalizeAndValidatePagerParameters(pageNumber, pageSize, response);\r
+ PagerParameters pagerParameters = new PagerParameters(pageSize, pageNumber);\r
+ pagerParameters.normalizeAndValidate(response);\r
+\r
+ start = pagerParameters.getPageIndex() * pagerParameters.getPageSize();\r
+ List sub_c = subCollection(c, start, pagerParameters.getPageSize());\r
+ Pager p = new DefaultPagerImpl(pageNumber, c.size(), pagerParameters.getPageSize(), sub_c);\r
+ modelAndView.addObject(p);\r
+ }\r
+\r
+ } else {\r
+ modelAndView.addObject(objectFromProperty);\r
+ }\r
+\r
+ }\r
+\r
+ if(modelAndView.isEmpty()){\r
+ return null;\r
+ } else {\r
+\r
+ return modelAndView;\r
+ }\r
+ }\r
+\r
+ public Object getCdmBaseProperty(UUID uuid, String property, HttpServletResponse response) throws IOException{\r
+\r
+ T instance = (T) HibernateProxyHelper.deproxy(getCdmBaseInstance(uuid, response, property));\r
+\r
+ Object objectFromProperty = invokeProperty(instance, property, response);\r
+\r
+ return objectFromProperty;\r
+ }\r
+\r
+ private Class<?> propertyClass(T instance, String baseName) {\r
+ PropertyDescriptor propertyDescriptor = null;\r
+ Class<?> c = null;\r
+ try {\r
+ propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);\r
+ if(propertyDescriptor != null){\r
+ c = propertyDescriptor.getClass();\r
+ }\r
+ } catch (IllegalAccessException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ } catch (InvocationTargetException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ } catch (NoSuchMethodException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ }\r
+ return c;\r
+ }\r
+\r
+ /**\r
+ * @param <SUB_T>\r
+ * @param clazz\r
+ * @param uuid\r
+ * @param response\r
+ * @param pathProperties\r
+ * @return\r
+ * @throws IOException\r
+ */\r
+ @SuppressWarnings("unchecked")\r
+ protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, List<String> pathProperties)\r
+ throws IOException {\r
+\r
+ CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperties);\r
+ if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){\r
+ HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);\r
+ }\r
+ return (SUB_T) cdmBaseObject;\r
+ }\r
+\r
+ /**\r
+ * @param <SUB_T>\r
+ * @param clazz\r
+ * @param uuid\r
+ * @param response\r
+ * @param pathProperty\r
+ * @return\r
+ * @throws IOException\r
+ */\r
+ @SuppressWarnings("unchecked")\r
+ protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, String pathProperty)\r
+ throws IOException {\r
+\r
+ CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperty);\r
+ if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){\r
+ HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);\r
+ }\r
+ return (SUB_T) cdmBaseObject;\r
+ }\r
+\r
+ /**\r
+ * @param uuid\r
+ * @param response\r
+ * @param pathProperty\r
+ * @return\r
+ * @throws IOException\r
+ */\r
+ protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, String pathProperty)\r
+ throws IOException {\r
+ return getCdmBaseInstance(baseClass, uuid, response, Arrays\r
+ .asList(new String[] { pathProperty }));\r
+ }\r
+\r
+\r
+ /**\r
+ * @param uuid\r
+ * @param response\r
+ * @param pathProperties\r
+ * @return\r
+ * @throws IOException\r
+ */\r
+ protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, List<String> pathProperties)\r
+ throws IOException {\r
+ return getCdmBaseInstance(baseClass, service, uuid, response, pathProperties);\r
+ }\r
+\r
+ /**\r
+ * @param <CDM_BASE>\r
+ * @param clazz\r
+ * @param service\r
+ * @param uuid\r
+ * @param response\r
+ * @param pathProperties\r
+ * @return\r
+ * @throws IOException\r
+ */\r
+ protected final <CDM_BASE extends CdmBase> CDM_BASE getCdmBaseInstance(Class<CDM_BASE> clazz, IService<CDM_BASE> service, UUID uuid, HttpServletResponse response, List<String> pathProperties)\r
+ throws IOException {\r
+\r
+ CDM_BASE cdmBaseObject = service.load(uuid, pathProperties);\r
+ if (cdmBaseObject == null) {\r
+ HttpStatusMessage.UUID_NOT_FOUND.send(response);\r
+ throw new NoRecordsMatchException("No instance found for UUID " + uuid.toString());\r
+ }\r
+ return cdmBaseObject;\r
+ }\r
+\r
+ /**\r
+ * @param instance\r
+ * @param baseName\r
+ * @param response\r
+ * @return\r
+ * @throws IOException\r
+ */\r
+ private final Object invokeProperty(T instance,\r
+ String baseName, HttpServletResponse response) throws IOException {\r
+\r
+ Object result = null;\r
+ try {\r
+ PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);\r
+ if(propertyDescriptor == null){\r
+ throw new NoSuchMethodException("No such method: " + instance.getClass().getSimpleName() + ".get" + baseName);\r
+ }\r
+ Method method = propertyDescriptor.getReadMethod();\r
+\r
+ Class<?> returnType = method.getReturnType();\r
+\r
+ if(CdmBase.class.isAssignableFrom(returnType)\r
+ || Collection.class.isAssignableFrom(returnType)\r
+ || Map.class.isAssignableFrom(returnType)\r
+ || INomenclaturalReference.class.isAssignableFrom(returnType)){\r
+\r
+ result = method.invoke(instance, (Object[])null);\r
+\r
+ result = HibernateProxyHelper.deproxy(result);\r
+\r
+ }else{\r
+ HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);\r
+ }\r
+ } catch (SecurityException e) {\r
+ logger.error("SecurityException: ", e);\r
+ HttpStatusMessage.INTERNAL_ERROR.send(response);\r
+ } catch (NoSuchMethodException e) {\r
+ HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);\r
+ } catch (IllegalArgumentException e) {\r
+ HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);\r
+ } catch (IllegalAccessException e) {\r
+ HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);\r
+ } catch (InvocationTargetException e) {\r
+ HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);\r
+ }\r
+ return result;\r
+ }\r
+\r
+ private <E> List<E> subCollection(Collection<? extends E> c, Integer start, Integer length){\r
+ List<E> sub_c = new ArrayList<E>(length);\r
+ if(c.size() > length){\r
+ E[] a = (E[]) c.toArray();\r
+ for(int i = start; i < start + length; i++){\r
+ sub_c.add(a[i]);\r
+ }\r
+ } else {\r
+ sub_c.addAll(c);\r
+ }\r
+ return sub_c;\r
+\r
+ }\r
+\r
+\r
+ /* TODO implement\r
+\r
+ private Validator validator;\r
+\r
+ private javax.validation.Validator javaxValidator;\r
+\r
+ @RequestMapping(method = RequestMethod.PUT, headers="content-type=multipart/form-data")\r
+ public T doPutForm(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {\r
+ object.setUuid(uuid);\r
+ validator.validate(object, result);\r
+ if (result.hasErrors()) {\r
+ throw new Error();\r
+ // set http status code depending upon what happened, possibly return\r
+ // the put object and errors so that they can be rendered into a suitable error response\r
+ } else {\r
+ // requires merging detached object ?gilead?\r
+ service.save(object);\r
+ }\r
+\r
+ return object;\r
+ }\r
+\r
+ @RequestMapping(method = RequestMethod.PUT, headers="content-type=text/json")\r
+ public T doPutJSON(@PathVariable(value = "uuid") UUID uuid, @RequestBody String jsonMessage) {\r
+ JSONObject jsonObject = JSONObject.fromObject(jsonMessage);\r
+ T object = (T)JSONObject.toBean(jsonObject, this.getClass());\r
+\r
+\r
+ Set<ConstraintViolation<T>> constraintViolations = javaxValidator.validate(object);\r
+ if (!constraintViolations.isEmpty()) {\r
+ throw new Error();\r
+ // set http status code depending upon what happened, possibly return\r
+ // the put object and errors so that they can be rendered into a suitable error response\r
+ } else {\r
+ // requires merging detached object ?gilead?\r
+ service.save(object);\r
+ }\r
+\r
+ return object;\r
+ }\r
+\r
+ @RequestMapping(method = RequestMethod.PUT) // the cdm-server may not allow clients to specify the uuid for resources\r
+ public T doPut(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {\r
+ validator.validate(object, result);\r
+ if (result.hasErrors()) {\r
+ // set http status code depending upon what happened, possibly return\r
+ // the put object and errors so that they can be rendered into a suitable error response\r
+ } else {\r
+ service.save(object);\r
+ }\r
+ }\r
+\r
+ @RequestMapping(method = RequestMethod.DELETE)\r
+ public void doDelete(@PathVariable(value = "uuid") UUID uuid) {\r
+ T object = service.find(uuid);\r
+ // provided the object exists\r
+ service.delete(uuid);\r
+ // might return 204 or 200\r
+ }\r
+ }\r
*/\r
- \r
+\r
\r
}\r