From: Aron Roberts Date: Tue, 24 Aug 2010 06:27:14 +0000 (+0000) Subject: CSPACE-2699: Dates in the current format emitted by the UI date picker are now recogn... X-Git-Url: https://git.aero2k.de/?a=commitdiff_plain;h=f5df53d463ca4db9d0764012353d85e88d8c3c39;p=tmp%2Fjakarta-migration.git CSPACE-2699: Dates in the current format emitted by the UI date picker are now recognized as valid values for date fields in the services layer. --- diff --git a/services/common/src/main/config/services/tenant-bindings.xml b/services/common/src/main/config/services/tenant-bindings.xml index 0caf1ff9f..1cb73143b 100644 --- a/services/common/src/main/config/services/tenant-bindings.xml +++ b/services/common/src/main/config/services/tenant-bindings.xml @@ -20,9 +20,10 @@ datePatternMM/dd/yyyy + datePatternMMM dd, yyyy datePatterndd.MM.yyyy - + localeLanguageen diff --git a/services/common/src/main/java/org/collectionspace/services/common/datetime/DateTimeFormatUtils.java b/services/common/src/main/java/org/collectionspace/services/common/datetime/DateTimeFormatUtils.java index 289187d30..9c3af532f 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/datetime/DateTimeFormatUtils.java +++ b/services/common/src/main/java/org/collectionspace/services/common/datetime/DateTimeFormatUtils.java @@ -6,7 +6,7 @@ * http://www.collectionspace.org * http://wiki.collectionspace.org - * Copyright 2009 University of California at Berkeley + * Copyright © 2009 University of California at Berkeley * Licensed under the Educational Community License (ECL), Version 2.0. * You may not use this file except in compliance with this License. @@ -20,10 +20,12 @@ package org.collectionspace.services.common.datetime; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.GregorianCalendar; -import java.util.ArrayList; import java.util.HashMap; +import java.util.Locale; import java.util.List; import java.util.Map; import java.util.TimeZone; @@ -49,10 +51,14 @@ public class DateTimeFormatUtils { private static final Logger logger = LoggerFactory.getLogger(DateTimeFormatUtils.class); final static String DATE_FORMAT_PATTERN_PROPERTY_NAME = "datePattern"; + final static String LOCALE_LANGUAGE_CODE_PROPERTY_NAME = "localeLanguage"; + final static Locale NULL_LOCALE = null; + final static List isoLanguageCodes = new ArrayList(Arrays.asList(Locale.getISOLanguages())); 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> dateFormatters = new HashMap>(); static Map> datePatterns = new HashMap>(); + static Map> localeLanguageCodes = new HashMap>(); // FIXME: @@ -102,11 +108,31 @@ public class DateTimeFormatUtils { } // Otherwise, generate that list and cache it for re-use. List patterns = getDateFormatPatternsForTenant(tenantId); + List languageCodes = getLanguageCodesForTenant(tenantId); + Locale locale = null; DateFormat df = null; - for (String pattern : patterns) { - df = getDateFormatter(pattern); - if (df != null) { - formatters.add(df); + boolean hasLanguageCodes = languageCodes != null && languageCodes.size() > 0; + // FIXME: this code pairs every locale language code with every date or + // date/time pattern. This is a quick and dirty expedient, and must + // necessarily be replaced by date pattern/language code pairs. + if (hasLanguageCodes) { + for (String languageCode : languageCodes) { + if (languageCode != null && ! languageCode.trim().isEmpty()) { + locale = getLocale(languageCode); + for (String pattern : patterns) { + df = getDateFormatter(pattern, locale); + if (df != null) { + formatters.add(df); + } + } + } + } + } else { + for (String pattern : patterns) { + df = getDateFormatter(pattern, locale); + if (df != null) { + formatters.add(df); + } } } if (dateFormatters != null) { @@ -198,10 +224,88 @@ public class DateTimeFormatUtils { return validPatterns; } + // FIXME: Routines specific to Locales, including their constituent language + // and country codes, should be moved to their own utility class, and likely + // to their own common package. + + /** + * Returns a list of the locale language codes permitted in a service context. + * + * @param ctx a service context. + * + * @return a list of locale language codes permitted in the service context. + * Returns an empty list of language codes if the service context is null. + */ + public static List getLanguageCodesForTenant(ServiceContext ctx) { + if (ctx == null) { + return new ArrayList(); + } + return getLanguageCodesForTenant(ctx.getTenantId()); + } + + /** + * Returns a list of the locale language codes permitted for a tenant, specified + * by tenant ID. + * + * The values of these codes must be valid ISO 639-1 language codes. + * + * @param tenantId a tenant ID. + * + * @return a list of locale language codes permitted for the tenant. + * Returns an empty list of language codes if the tenant ID is null or empty. + */ + public static List getLanguageCodesForTenant(String tenantId) { + List languageCodes = new ArrayList(); + if (tenantId == null || tenantId.trim().isEmpty()) { + return languageCodes; + } + // If a list of language codes for this tenant already exists, return it. + if (localeLanguageCodes != null && localeLanguageCodes.containsKey(tenantId)) { + languageCodes = localeLanguageCodes.get(tenantId); + if (languageCodes != null && languageCodes.size() > 0) { + return languageCodes; + } + } + // Otherwise, generate that list and cache it for re-use. + TenantBindingConfigReaderImpl tReader = + ServiceMain.getInstance().getTenantBindingConfigReader(); + TenantBindingType tenantBinding = tReader.getTenantBinding(tenantId); + languageCodes = TenantBindingUtils.getPropertyValues(tenantBinding, + LOCALE_LANGUAGE_CODE_PROPERTY_NAME); + languageCodes = validateLanguageCodes(languageCodes); + if (localeLanguageCodes != null) { + localeLanguageCodes.put(tenantId, languageCodes); + } + return languageCodes; + } + + /** + * Validates a list of language codes, verifying codes against a + * list of valid ISO 639-1 language codes. + * + * @param patterns a list of language codes. + * + * @return a list of valid language codes, excluding any codes + * that are not valid ISO 639-1 language codes. + */ + public static List validateLanguageCodes(List languageCodes) { + if (languageCodes == null) { + return new ArrayList(); + } + List validLanguageCodes = new ArrayList(); + for (String code : languageCodes) { + if (code != null && isoLanguageCodes.contains(code.trim())) { + validLanguageCodes.add(code); + } + } + return validLanguageCodes; + } + /** * 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. + * date/time string. Applies the set of date formatters for a supplied tenant, + * in sequence, to attempt to parse the string, and returns the timestamp + * resulting from the first successful parse attempt. * * @param str a String, possibly a date or date/time String. * @param tenantId a tenant ID. @@ -244,7 +348,7 @@ public class DateTimeFormatUtils { } /** - * Formats a provided calendar date using a provided date formatter, + * Formats a provided calendar date using a supplied date formatter, * in the default system time zone. * * @param date A calendar date to format. @@ -372,6 +476,26 @@ public class DateTimeFormatUtils { return date; } + /** + * Returns the locale associated with a supplied ISO 639-1 language code. + * + * @param lang A language code. + * + * @return A locale based on that language code; or null + * if the code was null, empty, or invalid. + */ + public static Locale getLocale(String lang) { + if (lang == null || lang.trim().isEmpty()) { + logger.warn("Null or empty date language code was provided when getting locale."); + return NULL_LOCALE; + } + if (! isoLanguageCodes.contains(lang.trim())) { + logger.warn("Invalid language code '" + lang + "'"); + return NULL_LOCALE; + } + return new Locale(lang); + } + /** * Returns a date formatter for a provided date or date/time pattern. * @@ -381,13 +505,31 @@ public class DateTimeFormatUtils { * if the pattern was null, empty, or invalid. */ public static DateFormat getDateFormatter(String pattern) { + return getDateFormatter(pattern, NULL_LOCALE); + } + + /** + * Returns a date formatter for a supplied date or date/time pattern, + * in the supplied locale (if any). + * + * @param pattern A date or date/time pattern. + * @param locale A locale. + * + * @return A date formatter using that pattern and locale (if any), or null + * if the pattern was null, empty, or invalid. + */ + public static DateFormat getDateFormatter(String pattern, Locale locale) { DateFormat df = null; if (pattern == null || pattern.trim().isEmpty()) { logger.warn("Null or empty date pattern string was provided when getting date formatter."); return df; } try { - df = new SimpleDateFormat(pattern); + if (locale == null) { + df = new SimpleDateFormat(pattern); + } else { + df = new SimpleDateFormat(pattern, locale); + } df.setLenient(false); } catch (IllegalArgumentException iae) { logger.warn("Invalid date pattern string '" + pattern + "': " + iae.getMessage()); diff --git a/services/common/src/main/java/org/collectionspace/services/common/datetime/GregorianCalendarDateTimeUtils.java b/services/common/src/main/java/org/collectionspace/services/common/datetime/GregorianCalendarDateTimeUtils.java index 0aab30641..a37cc7235 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/datetime/GregorianCalendarDateTimeUtils.java +++ b/services/common/src/main/java/org/collectionspace/services/common/datetime/GregorianCalendarDateTimeUtils.java @@ -6,7 +6,7 @@ * http://www.collectionspace.org * http://wiki.collectionspace.org - * Copyright 2009 University of California at Berkeley + * Copyright © 2009 University of California at Berkeley * Licensed under the Educational Community License (ECL), Version 2.0. * You may not use this file except in compliance with this License. diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/DocumentUtils.java b/services/common/src/main/java/org/collectionspace/services/common/document/DocumentUtils.java index 7d288ecf3..1e276a0ae 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/document/DocumentUtils.java +++ b/services/common/src/main/java/org/collectionspace/services/common/document/DocumentUtils.java @@ -107,7 +107,7 @@ public class DocumentUtils { private static final Logger logger = LoggerFactory.getLogger(DocumentUtils.class); - /** The name value separator. */ + /** The name dateVal separator. */ private static String NAME_VALUE_SEPARATOR = "|"; // The delimiter in a schema-qualified field name, @@ -128,14 +128,14 @@ public class DocumentUtils { */ private static class NameValue { /** - * Instantiates a new name value. + * Instantiates a new name dateVal. */ NameValue() { // default scoped constructor to removed "synthetic accessor" warning } /** The name. */ String name; - /** The value. */ + /** The dateVal. */ String value; }; @@ -318,7 +318,7 @@ public class DocumentUtils { /** * parseProperties extract given payload (XML) into Name-Value properties. this * @param document to parse - * @return map key=property name, value=property value + * @return map key=property name, dateVal=property dateVal * @throws Exception */ public static Map parseProperties(Node document) @@ -346,8 +346,8 @@ public class DocumentUtils { } } // - // Set the value even if it's null. - // A null value implies a clear/delete of the property + // Set the dateVal even if it's null. + // A null dateVal implies a clear/delete of the property // objectProps.put(name, value); } @@ -356,7 +356,7 @@ public class DocumentUtils { } /** - * getMultiStringValues retrieve multi-value element values + * getMultiStringValues retrieve multi-dateVal element values * assumption: backend does not support more than 1 level deep hierarchy * @param node * @return @@ -400,7 +400,7 @@ public class DocumentUtils { } /** - * getMultiValues retrieve multi-value element values + * getMultiValues retrieve multi-dateVal element values * assumption: backend does not support more than 1 level deep hierarchy * @param node * @return @@ -437,7 +437,7 @@ public class DocumentUtils { } /** - * getTextNodeValue retrieves text node value + * getTextNodeValue retrieves text node dateVal * @param cnode * @return */ @@ -451,11 +451,11 @@ public class DocumentUtils { } /** - * isQualified check if the given value is already qualified with given property name + * isQualified check if the given dateVal is already qualified with given property name * e.g. otherNumber|urn:org.collectionspace.id:24082390 is qualified with otherNumber * but urn:org.walkerart.id:123 is not qualified * @param name of the property, e.g. otherNumber - * @param value of the property e.g. otherNumber + * @param dateVal of the property e.g. otherNumber * @return */ private static boolean isQualified(String name, String value) { @@ -469,22 +469,22 @@ public class DocumentUtils { } /** - * qualify qualifies given property value with given property name, e.g. - * name=otherNumber and value=urn:org.collectionspace.id:24082390 would be + * qualify qualifies given property dateVal with given property name, e.g. + * name=otherNumber and dateVal=urn:org.collectionspace.id:24082390 would be * qualified as otherNumber|urn:org.collectionspace.id:24082390. however, - * name=otherNumber and value=otherNumber|urn:org.collectionspace.id:24082390 - * would be ignored as the given value is already qualified once. + * name=otherNumber and dateVal=otherNumber|urn:org.collectionspace.id:24082390 + * would be ignored as the given dateVal is already qualified once. * @param name - * @param value + * @param dateVal * @return */ private static String qualify(String name, String value) { /* String result = null; - if (isQualified(name, value)) { - result = value; + if (isQualified(name, dateVal)) { + result = dateVal; } else { - result = name + NAME_VALUE_SEPARATOR + value; + result = name + NAME_VALUE_SEPARATOR + dateVal; } return result; */ @@ -566,7 +566,7 @@ public class DocumentUtils { * @param document the document * @param parent the parent * @param field the field - * @param value the value + * @param dateVal the dateVal * @throws IOException Signals that an I/O exception has occurred. */ private static void buildProperty(Document document, Element parent, @@ -866,7 +866,7 @@ public class DocumentUtils { NameValue nv = unqualify(val); Element c = document.createElement(nv.name); e.appendChild(c); - insertTextNode(document, c, nv.value); + insertTextNode(document, c, nv.dateVal); } } */ @@ -941,7 +941,7 @@ public class DocumentUtils { * * @param document the document * @param e the e - * @param strValue the str value + * @param strValue the str dateVal private static void insertTextNode(Document document, Element e, String strValue) { Text tNode = document.createTextNode(strValue); e.appendChild(tNode); @@ -949,11 +949,11 @@ public class DocumentUtils { */ /** - * unqualify given value. + * unqualify given dateVal. * input of otherNumber|urn:org.collectionspace.id:24082390 would be unqualified - * as name=otherNumber and value=urn:org.collectionspace.id:24082390 + * as name=otherNumber and dateVal=urn:org.collectionspace.id:24082390 * @param input - * @return name and value + * @return name and dateVal * @exception IllegalStateException private static NameValue unqualify(String input) { NameValue nv = new NameValue(); @@ -961,11 +961,11 @@ public class DocumentUtils { int tokens = stz.countTokens(); if (tokens == 2) { nv.name = stz.nextToken(); - nv.value = stz.nextToken(); + nv.dateVal = stz.nextToken(); // Allow null or empty values } else if (tokens == 1) { nv.name = stz.nextToken(); - nv.value = ""; + nv.dateVal = ""; } else { throw new IllegalStateException("Unexpected format for multi valued element: " + input); } @@ -1151,23 +1151,23 @@ public class DocumentUtils { if (type.isSimpleType()) { if (isDateType(type)) { - String value = element.getText(); - if (value == null || value.trim().isEmpty()) { + String dateVal = element.getText(); + if (dateVal == null || dateVal.trim().isEmpty()) { result = type.decode(""); } else { - // Dates or date/times in ISO 8601-based representations + // Dates or date/times in any ISO 8601-based representations // directly supported by Nuxeo will be successfully decoded. - result = type.decode(element.getText()); + result = type.decode(dateVal); // All other date or date/time values must first be converted // to a supported ISO 8601-based representation. if (result == null) { - dateStr = DateTimeFormatUtils.toIso8601Timestamp(element.getText(), + dateStr = DateTimeFormatUtils.toIso8601Timestamp(dateVal, ctx.getTenantId()); if (dateStr != null) { result = type.decode(dateStr); } else { throw new IllegalArgumentException("Unrecognized date value '" - + element.getText() + "' in field '" + element.getName() + "'"); + + dateVal + "' in field '" + element.getName() + "'"); } } }