*print redlist status from congruent concept relations
[cdmlib.git] / cdmlib-remote / src / main / java / eu / etaxonomy / cdm / remote / controller / BaseController.java
index fc23d82a5be7f1c32faf2c3d7265b6dae2a36ad2..80202e8e5eb4727085bbde5992ab1752a04bd673 100644 (file)
@@ -1,3 +1,4 @@
+// $Id$\r
 /**\r
 * Copyright (C) 2007 EDIT\r
 * European Distributed Institute of Taxonomy \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.beans.factory.annotation.Autowired;\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.editor.UUIDPropertyEditor;\r
 \r
 /**\r
  * based on org.cateproject.controller.common\r
@@ -32,68 +55,337 @@ import eu.etaxonomy.cdm.model.common.CdmBase;
  * @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
+public abstract class BaseController<T extends CdmBase, SERVICE extends IService<T>> extends AbstractController {\r
+\r
        protected SERVICE service;\r
        \r
-       protected Pattern uuidParameterPattern = null;\r
-       \r
-       protected void setUuidParameterPattern(String pattern){\r
-               uuidParameterPattern = Pattern.compile(pattern);\r
-       }\r
+       protected Class<T> baseClass;\r
        \r
        public abstract void setService(SERVICE service);\r
        \r
-       /**@InitBinder\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
-       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
-                                       logger.warn(uuidMatcher.group(1) + "is not a uuid");\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.getServletPath());\r
+               T obj = (T) 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.getServletPath());\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
+                                       pageSize = (pageSize == null ? DEFAULT_PAGE_SIZE : pageSize);\r
+                                       pageNumber = (pageNumber == null ? 0 : pageNumber);\r
+                                       start = pageNumber * pageSize;\r
+                                       List sub_c = subCollection(c, start, pageSize);\r
+                                       Pager p = new DefaultPagerImpl(pageNumber, c.size(), pageSize, 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
-               return null;\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
-       @RequestMapping(method = RequestMethod.GET)\r
-       public T doGet(HttpServletRequest request) {\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
-               UUID uuid = readValueUuid(request);\r
-               Assert.notNull(uuid, "no valid uuid");\r
-               if(uuid == null){\r
-                       return null;\r
+               CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperty);\r
+               if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){\r
+                       HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);\r
                }\r
-               return service.findByUuid(uuid);\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
+               }\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
-         @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
+          \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.update(object);\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