]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
CSPACE-2418: Services can now accept, as input to date fields in their record types...
authorAron Roberts <aron@socrates.berkeley.edu>
Wed, 18 Aug 2010 01:38:25 +0000 (01:38 +0000)
committerAron Roberts <aron@socrates.berkeley.edu>
Wed, 18 Aug 2010 01:38:25 +0000 (01:38 +0000)
services/common/src/main/config/services/tenant-bindings.xml
services/common/src/main/java/org/collectionspace/services/common/datetime/DateTimeFormatUtils.java
services/common/src/main/java/org/collectionspace/services/common/document/DocumentUtils.java
services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RemoteDocumentModelHandlerImpl.java
services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RemoteSubItemDocumentModelHandlerImpl.java
services/movement/service/src/main/java/org/collectionspace/services/movement/nuxeo/MovementValidatorHandler.java

index f42e066e7a4b1204270e73680852a1436e83b336..e11388c0120c2bbcdf0d4eb8f2346f5b805ddba5 100644 (file)
@@ -19,9 +19,9 @@
         <tenant:repositoryDomain name="default-domain" repositoryClient="nuxeo-java"/>
 
         <tenant:properties>
-            <types:item><types:key>datePattern</types:key><types:value>MM/dd/YYYY</types:value></types:item>
-            <types:item><types:key>datePattern</types:key><types:value>dd.MM.YYYY</types:value></types:item>
-            <!-- <types:item><types:key>datePattern</types:key><types:value>dd/MM/YYYY</types:value></types:item> -->
+            <types:item><types:key>datePattern</types:key><types:value>MM/dd/yyyy</types:value></types:item>
+            <types:item><types:key>datePattern</types:key><types:value>dd.MM.yyyy</types:value></types:item>
+            <!-- <types:item><types:key>datePattern</types:key><types:value>dd/MM/yyyy</types:value></types:item> -->
             <!-- <types:item><types:key>localeLanguage</types:key><types:value>en</types:value></types:item> -->
             <!-- <types:item><types:key>localeLanguage</types:key><types:value>da</types:value></types:item> -->
         </tenant:properties>
         <tenant:repositoryDomain name="pahma-domain" repositoryClient="nuxeo-java"/>
 
         <tenant:properties>
-            <types:item><types:key>datePattern</types:key><types:value>MM/dd/YYYY</types:value></types:item>
-            <types:item><types:key>datePattern</types:key><types:value>dd.MM.YYYY</types:value></types:item>
-            <!-- <types:item><types:key>datePattern</types:key><types:value>dd/MM/YYYY</types:value></types:item> -->
+            <types:item><types:key>datePattern</types:key><types:value>MM/dd/yyyy</types:value></types:item>
+            <types:item><types:key>datePattern</types:key><types:value>dd.MM.yyyy</types:value></types:item>
+            <!-- <types:item><types:key>datePattern</types:key><types:value>dd/MM/yyyy</types:value></types:item> -->
             <!-- <types:item><types:key>localeLanguage</types:key><types:value>en</types:value></types:item> -->
             <!-- <types:item><types:key>localeLanguage</types:key><types:value>da</types:value></types:item> -->
         </tenant:properties>
index b01e5b80b2929fc93e04ad549b0758364986973c..55f17f8cf7afdcbe04f2f289fe115174f9c6f4db 100644 (file)
@@ -23,7 +23,9 @@ import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.TimeZone;
 
 import org.collectionspace.services.common.ServiceMain;
@@ -47,15 +49,71 @@ public class DateTimeFormatUtils {
 
     private static final Logger logger = LoggerFactory.getLogger(DateTimeFormatUtils.class);
     final static String DATE_FORMAT_PATTERN_PROPERTY_NAME = "datePattern";
+    final static String ISO_8601_FLOATING_DATE_PATTERN = "yyyy-MM-dd";
     final static String ISO_8601_UTC_TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+    static Map<String,List<DateFormat>> dateFormatters = new HashMap<String,List<DateFormat>>();
+    static Map<String,List<String>> datePatterns = new HashMap<String,List<String>>();
+
 
     // FIXME:
     // - Add a method to return the default set of ISO 8601-based date patterns,
     //   irrespective of per-tenant configuration.
-    // - Consider cacheing the lists of per-tenant date format patterns
-    //   and refresh the cached copies whenever tenant bindings are read.
     // - Methods below related to per-tenant configuration of date formats might
     //   be moved to their own class.
+    // - Investigate whether the map of per-tenant date formatters might best be
+    //   maintained within a singleton class.
+
+    /**
+     * Returns a list of the date formatters permitted in a service context.
+     *
+     * @param ctx a service context.
+     *
+     * @return    a list of date formatters permitted in the service context.
+     *            Returns an empty list of date formatters if the service context is null.
+     */
+    public static List<DateFormat> getDateFormattersForTenant(ServiceContext ctx) {
+        List<DateFormat> formatters = new ArrayList<DateFormat>();
+        if (ctx == null) {
+            return formatters;
+        }
+        return getDateFormattersForTenant(ctx.getTenantId());
+    }
+
+    /**
+     * Returns a list of the date formatters permitted for a tenant, specified
+     * by tenant ID.
+     *
+     * @param tenantId  a tenant ID.
+     *
+     * @return    a list of date formatters permitted for the tenant.
+     *            Returns an empty list of date formatters if the tenant ID is null or empty.
+     */
+    public static List<DateFormat> getDateFormattersForTenant(String tenantId) {
+        List<DateFormat> formatters = new ArrayList<DateFormat>();
+        if (tenantId == null || tenantId.trim().isEmpty()) {
+            return formatters;
+        }
+        // If a list of date formatters for this tenant already exists, return it.
+        if (dateFormatters != null && dateFormatters.containsKey(tenantId)) {
+            formatters = dateFormatters.get(tenantId);
+            if (formatters != null && formatters.size() > 0) {
+                return formatters;
+            }
+        }
+        // Otherwise, generate that list and cache it for re-use.
+        List<String> patterns = getDateFormatPatternsForTenant(tenantId);
+        DateFormat df = null;
+        for (String pattern : patterns) {
+            df = getDateFormatter(pattern);
+            if (df != null)  {
+                formatters.add(df);
+            }
+        }
+        if (dateFormatters != null) {
+            dateFormatters.put(tenantId, formatters);
+        }
+        return formatters;
+    }
 
     /**
      * Returns a list of the date format patterns permitted in a service context.
@@ -84,28 +142,54 @@ public class DateTimeFormatUtils {
      * @param tenantId  a tenant ID.
      *
      * @return          a list of date format patterns permitted for the tenant.
+     *                  Returns an empty list of patterns if the tenant ID is null or empty.
      */
     public static List<String> getDateFormatPatternsForTenant(String tenantId) {
         List<String> patterns = new ArrayList<String>();
         if (tenantId == null || tenantId.trim().isEmpty()) {
             return patterns;
         }
+        // If a list of date patterns for this tenant already exists, return it.
+        if (datePatterns != null && datePatterns.containsKey(tenantId)) {
+            patterns = datePatterns.get(tenantId);
+            if (patterns != null && patterns.size() > 0) {
+                return patterns;
+            }
+        }
+        // Otherwise, generate that list and cache it for re-use.
         TenantBindingConfigReaderImpl tReader =
                 ServiceMain.getInstance().getTenantBindingConfigReader();
         TenantBindingType tenantBinding = tReader.getTenantBinding(tenantId);
         patterns = TenantBindingUtils.getPropertyValues(tenantBinding,
                 DATE_FORMAT_PATTERN_PROPERTY_NAME);
-        return validatePatterns(patterns);
+        patterns = validatePatterns(patterns);
+        if (datePatterns != null) {
+            datePatterns.put(tenantId, patterns);
+        }
+        return patterns;
     }
 
+    /**
+     * Validates a list of date or date/time patterns, checking each pattern
+     * to determine whether it can be used to instantiate a date formatter.
+     *
+     * These patterns must conform to the format for date and time pattern strings
+     * specified in the Javadocs for the Java language class, java.text.SimpleDateFormat.
+     *
+     * @param patterns  a list of date or date/time patterns.
+     *
+     * @return          a list of valid patterns, excluding any patterns
+     *                  that could not be used to instantiate a date formatter.
+     */
     public static List<String> validatePatterns(List<String> patterns) {
         if (patterns == null) {
             return new ArrayList<String>();
         }
+        DateFormat df = new SimpleDateFormat();
         List<String> validPatterns = new ArrayList<String>();
         for (String pattern : patterns) {
             try {
-                DateFormat df = getDateFormatter(pattern);
+                df = getDateFormatter(pattern);
                 validPatterns.add(pattern);
             } catch (IllegalArgumentException iae) {
                 logger.warn("Invalid " + DATE_FORMAT_PATTERN_PROPERTY_NAME + " property: " + pattern);
@@ -114,6 +198,38 @@ public class DateTimeFormatUtils {
         return validPatterns;
     }
 
+    /**
+     * Returns an ISO 8601 timestamp representation of a presumptive date or
+     * date/time string.  Applies the set of date formatters for a supplied tenant
+     * to attempt to parse the string.
+     *
+     * @param str       a String, possibly a date or date/time String.
+     * @param tenantId  a tenant ID.
+     *
+     * @return          an ISO 8601 timestamp representation of that String.
+     *                  If the String cannot be parsed by the date formatters
+     *                  for the supplied tenant, return the original string.
+     */
+    public static String toIso8601Timestamp(String dateStr, String tenantId) {
+        Date date = null;
+        List<DateFormat> formatters = getDateFormattersForTenant(tenantId);
+//        for (DateFormat formatter : getDateFormattersForTenant(tenantId)) {
+        for (DateFormat formatter : formatters) {
+            date = parseDate(dateStr, formatter);
+            if (date != null) {
+                break;
+            }
+        }
+        if (date == null) {
+            return dateStr;
+        } else {
+            GregorianCalendar gcal = new GregorianCalendar();
+            gcal.setTime(date);
+            String isoStr = formatAsISO8601Timestamp(gcal);
+            return isoStr;
+        }
+    }
+
     /**
      * Returns a representation of a calendar date and time instance,
      * as an ISO 8601-formatted timestamp in the UTC time zone.
@@ -176,10 +292,9 @@ public class DateTimeFormatUtils {
 
     /**
      * Identifies whether a presumptive date or date/time can be parsed
-     * by a date parser, using a specified format pattern.
+     * by a date parser, using a supplied format pattern.
      *
      * @param str      a String, possibly a date or date/time String.
-     *
      * @param pattern  A date or date/time pattern.
      *
      * @return         true, if the String can be parsed, using the pattern;
@@ -190,6 +305,9 @@ public class DateTimeFormatUtils {
         if (pattern == null || pattern.trim().isEmpty()) {
             return false;
         }
+        if (str == null || str.trim().isEmpty()) {
+            return false;
+        }
         DateFormat df = null;
         try {
             df = new SimpleDateFormat(pattern);
@@ -202,6 +320,59 @@ public class DateTimeFormatUtils {
         return true;
     }
 
+    /**
+     * Parses a presumptive date or date/time, using a supplied format pattern.
+     *
+     * @param str      a String, possibly a date or date/time String.
+     * @param pattern  A date or date/time pattern.
+     *
+     * @return         A date value, resulting from parsing the String using the
+     *                 supplied pattern.  Returns null if the parsing attempt fails.
+     */
+    public static Date parseDate(String str, String pattern) {
+        if (pattern == null || pattern.trim().isEmpty()) {
+            return null;
+        }
+        if (str == null || str.trim().isEmpty()) {
+            return null;
+        }
+        DateFormat df = null;
+        Date date = null;
+        try {
+            df = new SimpleDateFormat(pattern);
+            date = parseDate(str, df);
+        } catch (IllegalArgumentException iae) {
+            return null;
+        }
+        return date;
+    }
+
+       /**
+     * Parses a presumptive date or date/time, using a supplied format pattern.
+     *
+     * @param str      a String, possibly a date or date/time String.
+     * @param df       A date formatter.
+     *
+     * @return         A date value, resulting from parsing the String using the
+     *                 supplied formatter.  Returns null if the parsing attempt fails.
+     */
+    public static Date parseDate(String str, DateFormat df) {
+        if (df == null) {
+            return null;
+        }
+        if (str == null || str.trim().isEmpty()) {
+            return null;
+        }
+        Date date = null;
+        try {
+            df.setLenient(false);
+            date = df.parse(str);
+        } catch (ParseException pe) {
+            return null;
+        }
+        return date;
+    }
+
     /**
      * Returns a date formatter for a provided date or date/time pattern.
      *
@@ -218,9 +389,11 @@ public class DateTimeFormatUtils {
         }
         try {
             df = new SimpleDateFormat(pattern);
+            df.setLenient(false);
         } catch (IllegalArgumentException iae) {
             logger.warn("Invalid date pattern string '" + pattern + "': " + iae.getMessage());
         }
         return df;
     }
+
 }
index b99991d04bd3b769cbef9f644712d5ed76ea78c3..b65235f94900caddb6dc1d83ddb0baa7969a172f 100644 (file)
@@ -23,6 +23,8 @@
  */
 package org.collectionspace.services.common.document;
 
+import java.util.Calendar;
+
 import java.lang.reflect.Array;
 
 import java.io.File;
@@ -52,6 +54,8 @@ import javax.xml.transform.TransformerConfigurationException;
 import javax.xml.transform.TransformerException; 
 
 import org.collectionspace.services.common.ServiceMain;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.datetime.DateTimeFormatUtils;
 import org.collectionspace.services.common.service.ObjectPartContentType;
 import org.collectionspace.services.common.service.ObjectPartType;
 import org.collectionspace.services.common.service.XmlContentType;
@@ -69,8 +73,10 @@ import org.nuxeo.ecm.core.schema.types.ComplexType;
 import org.nuxeo.ecm.core.schema.types.Field;
 import org.nuxeo.ecm.core.schema.types.ListType;
 import org.nuxeo.ecm.core.schema.types.Schema;
+import org.nuxeo.ecm.core.schema.types.SimpleType;
 import org.nuxeo.ecm.core.schema.types.Type;
 import org.nuxeo.ecm.core.schema.types.JavaTypes;
+import org.nuxeo.ecm.core.schema.types.primitives.DateType;
 import org.nuxeo.ecm.core.schema.types.primitives.StringType;
 import org.nuxeo.ecm.core.schema.types.FieldImpl;
 import org.nuxeo.ecm.core.schema.types.QName;
@@ -572,7 +578,7 @@ public class DocumentUtils {
                parent.appendChild(element);
                // extract the element content
                if (type.isSimpleType()) {
-                       element.setTextContent(type.encode(value));
+                        element.setTextContent(type.encode(value));
                } else if (type.isComplexType()) {
                        ComplexType ctype = (ComplexType) type;
                        if (ctype.getName().equals(TypeConstants.CONTENT)) {
@@ -830,6 +836,22 @@ public class DocumentUtils {
        }
 
 
+        /*
+         * Identifies whether a property type is a date type.
+         *
+         * @param type   a type.
+        * @return       true, if is a date type;
+         *               false, if it is not a date type.
+         */
+        private static boolean isDateType(Type type) {
+            SimpleType st = (SimpleType) type;
+            if (st.getPrimitiveType() instanceof DateType) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+
        /**
         * Insert multi values.
         *
@@ -1021,7 +1043,7 @@ public class DocumentUtils {
         * @return the map
         */
        public static Map<String, Object> parseProperties(ObjectPartType partMeta,
-                       Document document) {
+                       Document document, ServiceContext ctx) {
                Map<String, Object> result = null;
                String schemaName = partMeta.getLabel();
                Schema schema = getSchemaFromName(schemaName);
@@ -1029,7 +1051,7 @@ public class DocumentUtils {
                org.dom4j.io.DOMReader xmlReader = new org.dom4j.io.DOMReader();
                org.dom4j.Document dom4jDocument = xmlReader.read(document);
                try {
-                       result = loadSchema(schema, dom4jDocument.getRootElement());
+                       result = loadSchema(schema, dom4jDocument.getRootElement(), ctx);
                } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
@@ -1047,7 +1069,7 @@ public class DocumentUtils {
         * @throws Exception the exception
         */
        @SuppressWarnings("unchecked")
-       static private Map<String, Object> loadSchema(Schema schema, org.dom4j.Element schemaElement)
+       static private Map<String, Object> loadSchema(Schema schema, org.dom4j.Element schemaElement, ServiceContext ctx)
        throws Exception {
                String schemaName1 = schemaElement.attributeValue(ExportConstants.NAME_ATTR);
                String schemaName = schema.getName();
@@ -1059,7 +1081,7 @@ public class DocumentUtils {
                        String name = element.getName();
                        Field field = schema.getField(name);
                        if (field != null) {
-                               Object value = getElementData(element, field.getType());
+                               Object value = getElementData(element, field.getType(), ctx);
                                data.put(name, value);
                        } else  {
                                if (logger.isDebugEnabled() == true) {
@@ -1080,18 +1102,25 @@ public class DocumentUtils {
         * @return the element data
         */
        @SuppressWarnings("unchecked")
-       static private Object getElementData(org.dom4j.Element element, Type type) {
+       static private Object getElementData(org.dom4j.Element element, Type type,
+                ServiceContext ctx) {
                Object result = null;
                
                if (type.isSimpleType()) {
-                       result = type.decode(element.getText());
+                        // Convert incoming date values to a canonical date representation,
+                        if (isDateType(type)) {
+                            result = DateTimeFormatUtils.toIso8601Timestamp((String) element.getText(),
+                                    ctx.getTenantId());
+                        } else {
+                           result = type.decode(element.getText());
+                        }
                } else if (type.isListType()) {
                        ListType ltype = (ListType) type;
                        List<Object> list = new ArrayList<Object>();
                        Iterator<org.dom4j.Element> it = element.elementIterator();
                        while (it.hasNext()) {
                                org.dom4j.Element el = it.next();
-                               list.add(getElementData(el, ltype.getFieldType()));
+                               list.add(getElementData(el, ltype.getFieldType(), ctx));
                        }
                        Type ftype = ltype.getFieldType();
                        if (ftype.isSimpleType()) { // these are stored as arrays
@@ -1132,7 +1161,7 @@ public class DocumentUtils {
                                        org.dom4j.Element el = it.next();
                                        String name = el.getName();
                                        Object value = getElementData(el, ctype.getField(
-                                                       el.getName()).getType());
+                                                       el.getName()).getType(), ctx);
                                        map.put(name, value);
                                }
                                result = map;
index fc067ec94fc5a6d4c4310b3ae1d320558189e206..b5d5947a36d26034556dc52620256f9fda1167fe 100644 (file)
@@ -25,7 +25,6 @@ package org.collectionspace.services.nuxeo.client.java;
 
 import java.io.InputStream;
 import java.util.Collection;
-import java.util.Iterator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -56,11 +55,7 @@ import org.nuxeo.ecm.core.api.DocumentModelList;
 import org.nuxeo.ecm.core.api.model.Property;
 import org.nuxeo.ecm.core.api.model.PropertyException;
 
-import org.nuxeo.ecm.core.schema.types.ComplexType;
-import org.nuxeo.ecm.core.schema.types.Field;
-import org.nuxeo.ecm.core.schema.types.ListType;
 import org.nuxeo.ecm.core.schema.types.Schema;
-import org.nuxeo.ecm.core.schema.types.Type;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -213,7 +208,7 @@ public abstract class RemoteDocumentModelHandlerImpl<T, TL>
             if (partMeta == null) {
                 continue;
             }
-            fillPart(part, docModel, partMeta, action);
+            fillPart(part, docModel, partMeta, action, ctx);
         }//rof
 
     }
@@ -225,7 +220,8 @@ public abstract class RemoteDocumentModelHandlerImpl<T, TL>
      * @param partMeta metadata for the object to fill
      * @throws Exception
      */
-    protected void fillPart(InputPart part, DocumentModel docModel, ObjectPartType partMeta, Action action)
+    protected void fillPart(InputPart part, DocumentModel docModel,
+            ObjectPartType partMeta, Action action, ServiceContext ctx)
             throws Exception {
         InputStream payload = part.getBody(InputStream.class, null);
 
@@ -240,7 +236,7 @@ public abstract class RemoteDocumentModelHandlerImpl<T, TL>
                 //TODO: callback to handler if registered to validate the
                 //document
 //                Map<String, Object> objectProps = DocumentUtils.parseProperties(document.getFirstChild());
-                Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, document);
+                Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, document, ctx);
                 if (action == Action.UPDATE) {
                     this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
                 }
index 2b9f88051c3f0f5bbbd68aac139c1102136d4696..baabb0adb38e1d53276c55091f6b6e02be47d53e 100644 (file)
@@ -27,6 +27,7 @@ import java.io.InputStream;
 import java.util.HashMap;\r
 import java.util.Map;\r
 \r
+import org.collectionspace.services.common.context.ServiceContext;\r
 import org.collectionspace.services.common.document.DocumentUtils;\r
 import org.collectionspace.services.common.service.ObjectPartType;\r
 \r
@@ -66,7 +67,7 @@ public abstract class RemoteSubItemDocumentModelHandlerImpl<T, TL> extends
      */\r
        @Override\r
     protected void fillPart(InputPart part, DocumentModel docModel, \r
-                                               ObjectPartType partMeta, Action action)\r
+                                               ObjectPartType partMeta, Action action, ServiceContext ctx)\r
             throws Exception {\r
         InputStream payload = part.getBody(InputStream.class, null);\r
 \r
index 0bd4dd9345a3100eee565718d24d742dc09aa69d..b01efb893a80675a6ea1f917716392ca2e4bb2b5 100644 (file)
@@ -24,7 +24,10 @@ public class MovementValidatorHandler implements ValidatorHandler {
                 if(logger.isDebugEnabled()) {
                     logger.debug("validate() action=" + action.name());
                 }
+
+                /*
                 try {
+
                     MultipartServiceContext mctx = (MultipartServiceContext) ctx;
                     MovementsCommon mc = (MovementsCommon) mctx.getInputPart(mctx.getCommonPartLabel(),
                             MovementsCommon.class);
@@ -39,8 +42,9 @@ public class MovementValidatorHandler implements ValidatorHandler {
                     // in the incoming payload are date fields whose values we
                     // might wish to validate, and of extracting their values,
                     // than hard-coding them here.
+                    //
+                    // See DocumentUtils.parseProperties() for one possible approach.
 
-                    /*
                     boolean validDateFormat = false;
                     String locDate = mc.getLocationDate();
                     for (String pattern : patterns) {
@@ -52,8 +56,6 @@ public class MovementValidatorHandler implements ValidatorHandler {
                         invalid = true;
                         msgBldr.append("\nlocationDate : unrecognized date format '" + locDate + "'");
                     }
-                    *
-                    */
 
                     if(action.equals(Action.CREATE)) {
                         //create specific validation here
@@ -71,6 +73,8 @@ public class MovementValidatorHandler implements ValidatorHandler {
                 } catch (Exception e) {
                     throw new InvalidDocumentException(e);
                 }
+                 *
+                 */
 
        }