* 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.
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;
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<String> 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<String,List<DateFormat>> dateFormatters = new HashMap<String,List<DateFormat>>();
static Map<String,List<String>> datePatterns = new HashMap<String,List<String>>();
+ static Map<String,List<String>> localeLanguageCodes = new HashMap<String,List<String>>();
// FIXME:
}
// Otherwise, generate that list and cache it for re-use.
List<String> patterns = getDateFormatPatternsForTenant(tenantId);
+ List<String> 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) {
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<String> getLanguageCodesForTenant(ServiceContext ctx) {
+ if (ctx == null) {
+ return new ArrayList<String>();
+ }
+ 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<String> getLanguageCodesForTenant(String tenantId) {
+ List<String> languageCodes = new ArrayList<String>();
+ 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<String> validateLanguageCodes(List<String> languageCodes) {
+ if (languageCodes == null) {
+ return new ArrayList<String>();
+ }
+ List<String> validLanguageCodes = new ArrayList<String>();
+ 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.
}
/**
- * 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.
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.
*
* 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());
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,
*/
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;
};
/**
* 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<String, Object> parseProperties(Node document)
}
}
//
- // 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);
}
}
/**
- * 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
}
/**
- * 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
}
/**
- * getTextNodeValue retrieves text node value
+ * getTextNodeValue retrieves text node dateVal
* @param cnode
* @return
*/
}
/**
- * 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) {
}
/**
- * 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;
*/
* @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,
NameValue nv = unqualify(val);
Element c = document.createElement(nv.name);
e.appendChild(c);
- insertTextNode(document, c, nv.value);
+ insertTextNode(document, c, nv.dateVal);
}
}
*/
*
* @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);
*/
/**
- * 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();
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);
}
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() + "'");
}
}
}