| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.solr.handler.dataimport; |
| |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IllformedLocaleException; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TimeZone; |
| |
| import org.apache.solr.common.util.SuppressForbidden; |
| import org.apache.solr.handler.dataimport.config.EntityField; |
| import org.apache.solr.util.DateMathParser; |
| |
| import static org.apache.solr.handler.dataimport.DataImportHandlerException.SEVERE; |
| import static org.apache.solr.handler.dataimport.DataImportHandlerException.wrapAndThrow; |
| |
| /** |
| * <p>Formats values using a given date format. </p> |
| * <p>Pass three parameters: |
| * <ul> |
| * <li>An {@link EntityField} or a date expression to be parsed with |
| * the {@link DateMathParser} class If the value is in a String, |
| * then it is assumed to be a datemath expression, otherwise it |
| * resolved using a {@link VariableResolver} instance</li> |
| * <li>A date format see {@link SimpleDateFormat} for the syntax.</li> |
| * <li>The {@link Locale} to parse. |
| * (optional. Defaults to the Root Locale) </li> |
| * </ul> |
| */ |
| public class DateFormatEvaluator extends Evaluator { |
| |
| public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; |
| protected Map<String, Locale> availableLocales = new HashMap<>(); |
| protected Set<String> availableTimezones = new HashSet<>(); |
| |
| @SuppressForbidden(reason = "Usage of outdated locale parsing with Locale#toString() because of backwards compatibility") |
| public DateFormatEvaluator() { |
| for (Locale locale : Locale.getAvailableLocales()) { |
| availableLocales.put(locale.toString(), locale); |
| } |
| for (String tz : TimeZone.getAvailableIDs()) { |
| availableTimezones.add(tz); |
| } |
| } |
| |
| private SimpleDateFormat getDateFormat(String pattern, TimeZone timezone, Locale locale) { |
| final SimpleDateFormat sdf = new SimpleDateFormat(pattern, locale); |
| sdf.setTimeZone(timezone); |
| return sdf; |
| } |
| |
| @Override |
| public String evaluate(String expression, Context context) { |
| List<Object> l = parseParams(expression, context.getVariableResolver()); |
| if (l.size() < 2 || l.size() > 4) { |
| throw new DataImportHandlerException(SEVERE, "'formatDate()' must have two, three or four parameters "); |
| } |
| Object o = l.get(0); |
| Object format = l.get(1); |
| if (format instanceof VariableWrapper) { |
| VariableWrapper wrapper = (VariableWrapper) format; |
| o = wrapper.resolve(); |
| format = o.toString(); |
| } |
| Locale locale = Locale.ENGLISH; // we default to ENGLISH for dates for full Java 9 compatibility |
| if(l.size()>2) { |
| Object localeObj = l.get(2); |
| String localeStr = null; |
| if (localeObj instanceof VariableWrapper) { |
| localeStr = ((VariableWrapper) localeObj).resolve().toString(); |
| } else { |
| localeStr = localeObj.toString(); |
| } |
| locale = availableLocales.get(localeStr); |
| if (locale == null) try { |
| locale = new Locale.Builder().setLanguageTag(localeStr).build(); |
| } catch (IllformedLocaleException ex) { |
| throw new DataImportHandlerException(SEVERE, "Malformed / non-existent locale: " + localeStr, ex); |
| } |
| } |
| TimeZone tz = TimeZone.getDefault(); // DWS TODO: is this the right default for us? Deserves explanation if so. |
| if(l.size()==4) { |
| Object tzObj = l.get(3); |
| String tzStr = null; |
| if (tzObj instanceof VariableWrapper) { |
| tzStr = ((VariableWrapper) tzObj).resolve().toString(); |
| } else { |
| tzStr = tzObj.toString(); |
| } |
| if(availableTimezones.contains(tzStr)) { |
| tz = TimeZone.getTimeZone(tzStr); |
| } else { |
| throw new DataImportHandlerException(SEVERE, "Unsupported Timezone: " + tzStr); |
| } |
| } |
| String dateFmt = format.toString(); |
| SimpleDateFormat fmt = getDateFormat(dateFmt, tz, locale); |
| Date date = null; |
| if (o instanceof VariableWrapper) { |
| date = evaluateWrapper((VariableWrapper) o, locale, tz); |
| } else { |
| date = evaluateString(o.toString(), locale, tz); |
| } |
| return fmt.format(date); |
| } |
| |
| /** |
| * NOTE: declared as a method to allow for extensibility |
| * |
| * @lucene.experimental this API is experimental and subject to change |
| * @return the result of evaluating a string |
| */ |
| protected Date evaluateWrapper(VariableWrapper variableWrapper, Locale locale, TimeZone tz) { |
| Date date = null; |
| Object variableval = resolveWrapper(variableWrapper,locale,tz); |
| if (variableval instanceof Date) { |
| date = (Date) variableval; |
| } else { |
| String s = variableval.toString(); |
| try { |
| date = getDateFormat(DEFAULT_DATE_FORMAT, tz, locale).parse(s); |
| } catch (ParseException exp) { |
| wrapAndThrow(SEVERE, exp, "Invalid expression for date"); |
| } |
| } |
| return date; |
| } |
| |
| /** |
| * NOTE: declared as a method to allow for extensibility |
| * @lucene.experimental |
| * @return the result of evaluating a string |
| */ |
| protected Date evaluateString(String datemathfmt, Locale locale, TimeZone tz) { |
| // note: DMP does not use the locale but perhaps a subclass might use it, for e.g. parsing a date in a custom |
| // string that doesn't necessarily have date math? |
| //TODO refactor DateMathParser.parseMath a bit to have a static method for this logic. |
| if (datemathfmt.startsWith("NOW")) { |
| datemathfmt = datemathfmt.substring("NOW".length()); |
| } |
| try { |
| DateMathParser parser = new DateMathParser(tz); |
| parser.setNow(new Date());// thus do *not* use SolrRequestInfo |
| return parser.parseMath(datemathfmt); |
| } catch (ParseException e) { |
| throw wrapAndThrow(SEVERE, e, "Invalid expression for date"); |
| } |
| } |
| |
| /** |
| * NOTE: declared as a method to allow for extensibility |
| * @lucene.experimental |
| * @return the result of resolving the variable wrapper |
| */ |
| protected Object resolveWrapper(VariableWrapper variableWrapper, Locale locale, TimeZone tz) { |
| return variableWrapper.resolve(); |
| } |
| |
| } |