/* * JBoss, Home of Professional Open Source * Copyright 2012 Red Hat Inc. and/or its affiliates and other contributors * as indicated by the @authors tag. All rights reserved. */ package org.jboss.elasticsearch.tools.content; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Map; import org.elasticsearch.common.settings.SettingsException; import org.elasticsearch.common.xcontent.support.XContentMapValues; /** * <pre> * { * "name" : "Date range checker.", * "class" : "org.jboss.elasticsearch.tools.content.IsDateInRangePreprocessor", * "settings" : { * "left_date" : "start_date", * "left_date_format" : "yyyy-MM-dd'T'HH:mm:ss.SSSXX", * "right_date" : "end_date", * "right_date_format" : "yyyy-MM-dd'T'HH:mm:ss.SSSXX", * "checked_date" : "tested_date", * "checked_date_format" : "yyyy-MM-dd'T'HH:mm:ss.SSSXX", * "checked_date_relative" : "false", * "result_field" : "result", * "default_value" : "false", * "source_bases" : ["prep_sys_authors","fields.assignee"] * } * } * </pre> * * Options are: * <ul> * <li><code>left_date</code> - An optional parameter specifying location where left-hand side date can be found for * range checking. If not given an open range is assumed. However at least one of date parameters needs to be provided. * Dot notation for nested values can be used here (see {@link XContentMapValues#extractValue(String, Map)}). * <li><code>left_date_format</code> - This parameter defines date format for the left-hand side date. It's optional and * defaults to <code>yyyy-MM-dd'T'HH:mm:ss.SSSXX</code> * <li><code>right_date</code> - An optional parameter specifying location where right-hand side date can be found for * range checking. If not given an open range is assumed. However at least one of date parameters needs to be provided. * Dot notation for nested values can be used here (see {@link XContentMapValues#extractValue(String, Map)}). * <li><code>right_date_format</code> - This parameter defines date format for the right-hand side date. It's optional * and defaults to <code>yyyy-MM-dd'T'HH:mm:ss.SSSXX</code> * <li><code>checked_date</code> - The parameter specifies location where the date for range checking is located. Dot * notation for nested values can be used here (see {@link XContentMapValues#extractValue(String, Map)}). * <li><code>checked_date_format</code> - This parameter defines date format for the checked date. It's optional and * defaults to <code>yyyy-MM-dd'T'HH:mm:ss.SSSXX</code> * <li><code>checked_date_relative</code> - An optional parameter with default value of 'false' defines whether * <code>checked_date</code> should be resolved against source base(true) or root(false). * <li><code>result_field</code> - result field in data to store boolean result of comparison. Dot notation can be used * here for structure nesting. * <li><code>source_bases</code> - list of fields in source data which are used as bases. If defined then range * comparison is done for each of this fields. <code>left_date</code>, <code>right_date</code> and * <code>target_field</code> are resolved relatively against this base. Base must provide object or list of objects. * </ul> * * @author Ryszard Kozmik (rkozmik at redhat dot com) * */ public class IsDateInRangePreprocessor extends StructuredContentPreprocessorWithSourceBasesBase<Map<String, Object>> { protected static final String CFG_LEFT_DATE = "left_date"; protected static final String CFG_RIGHT_DATE = "right_date"; protected static final String CFG_CHECKED_DATE = "checked_date"; protected static final String CFG_LEFT_DATE_FORMAT = "left_date_format"; protected static final String CFG_RIGHT_DATE_FORMAT = "right_date_format"; protected static final String CFG_CHECKED_DATE_FORMAT = "checked_date_format"; protected static final String CFG_CHECKED_DATE_RELATIVE = "checked_date_relative"; protected static final String CFG_RESULT_FIELD = "result_field"; protected static final String CFG_DEFAULT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSXX"; protected SimpleDateFormat dateFormatter = new SimpleDateFormat(); protected String leftDateField; protected String rightDateField; protected String checkedDateField; protected String resultField; protected String leftDateFormat; protected String rightDateFormat; protected String checkedDateFormat; protected boolean checkedDateRelative; @Override public void init(Map<String, Object> settings) throws SettingsException { super.init(settings); leftDateField = XContentMapValues.nodeStringValue(settings.get(CFG_LEFT_DATE), null); leftDateField = leftDateField != null && leftDateField.isEmpty() ? null : leftDateField; leftDateFormat = XContentMapValues.nodeStringValue(settings.get(CFG_LEFT_DATE_FORMAT), CFG_DEFAULT_DATE_FORMAT); rightDateField = XContentMapValues.nodeStringValue(settings.get(CFG_RIGHT_DATE), null); rightDateField = rightDateField != null && rightDateField.isEmpty() ? null : rightDateField; rightDateFormat = XContentMapValues.nodeStringValue(settings.get(CFG_RIGHT_DATE_FORMAT), CFG_DEFAULT_DATE_FORMAT); checkedDateField = XContentMapValues.nodeStringValue(settings.get(CFG_CHECKED_DATE), null); validateConfigurationObjectNotEmpty(checkedDateField, CFG_CHECKED_DATE); checkedDateFormat = XContentMapValues.nodeStringValue(settings.get(CFG_CHECKED_DATE_FORMAT), CFG_DEFAULT_DATE_FORMAT); checkedDateRelative = XContentMapValues.nodeBooleanValue(settings.get(CFG_CHECKED_DATE_RELATIVE), false); resultField = XContentMapValues.nodeStringValue(settings.get(CFG_RESULT_FIELD), null); validateConfigurationStringNotEmpty(resultField, CFG_RESULT_FIELD); // At least one of date ranges fields need to be provided. if (leftDateField == null && rightDateField == null) { throw new SettingsException("At least one of dates defining range, settings/" + CFG_LEFT_DATE + " or settings/" + CFG_RIGHT_DATE + " need to be provided."); } } @Override protected Map<String, Object> createContext(Map<String, Object> data) { return data; } @Override protected void processOneSourceValue(Map<String, Object> data, Map<String, Object> context, String base, PreprocessChainContext chainContext) { if (data == null) return; Boolean result = null; Date leftDate = null; Date rightDate = null; Date checkedDate = null; try { leftDate = handleDateExtractionAndParsing(leftDateField, leftDateFormat, data, base, chainContext); rightDate = handleDateExtractionAndParsing(rightDateField, rightDateFormat, data, base, chainContext); if(checkedDateRelative) { checkedDate = handleDateExtractionAndParsing(checkedDateField, checkedDateFormat, data, base, chainContext); } else { checkedDate = handleDateExtractionAndParsing(checkedDateField, checkedDateFormat, (base != null ? context : data), null, chainContext); } } catch (DataProblemException e) { return; } // If needed we switch the dates around so that leftDate is before rightDate. if (leftDate != null && rightDate != null && leftDate.after(rightDate)) { Date tmpDate = leftDate; leftDate = rightDate; rightDate = tmpDate; } if (leftDate != null && rightDate != null) { result = checkedDate.compareTo(leftDate) >= 0 && checkedDate.compareTo(rightDate) <= 0 ? true : false; } else if (leftDate != null) { result = checkedDate.compareTo(leftDate) >= 0 ? true : false; } else if (rightDate != null) { result = checkedDate.compareTo(rightDate) <= 0 ? true : false; } else { result = false; } StructureUtils.putValueIntoMapOfMaps(data, resultField, result); } @Override public List<String> getSourceBases() { return sourceBases; } /** * An util method to extract date value out from the field and parse it using the given date format. * * @param settings * @param cfgDateLocation * @param cfgDateFormat * @return parsed date object */ protected Date handleDateExtractionAndParsing(String dateField, String dateFormat, Map<String, Object> data, String base, PreprocessChainContext chainContext) throws DataProblemException { if (dateField == null) return null; Date resultDate = null; Object dateFieldData = null; if (dateField.contains(".")) { dateFieldData = XContentMapValues.extractValue(dateField, data); } else { dateFieldData = data.get(dateField); } if (dateFieldData != null) { if (!(dateFieldData instanceof String)) { String msg = "Value for field '" + dateField + "' is not a String, so can't be parsed to the date object."; addDataWarning(chainContext, msg); throw new DataProblemException(); } else { String dateStr = dateFieldData.toString(); if (dateStr != null && !dateStr.isEmpty()) { synchronized(dateFormatter) { dateFormatter.applyPattern(dateFormat); try { resultDate = dateFormatter.parse(dateStr); } catch (ParseException e) { String msg = dateField + " parameter value of " + dateStr + " could not be parsed using " + dateFormat + " format."; addDataWarning(chainContext, msg); throw new DataProblemException(); } } } } } return resultDate; } /** * Overrided warnings handler helps to save the information that any data parsing was problematic. If anything wrong * happened during the processing of data, we don't want to preprocess on it further on. */ protected void addDataWarning(PreprocessChainContext chainContext, String message) { super.addDataWarning(chainContext, message); logger.debug(message); } /** * An utility exception to handle data exceptions navigation nicely in this preprocessor. */ class DataProblemException extends Exception { private static final long serialVersionUID = 1L; } }