/*******************************************************************************
* Copyright 2014 Miami-Dade County
*
* Licensed 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.sharegov.cirm.legacy;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLNamedIndividual;
import org.sharegov.cirm.OWL;
import org.sharegov.cirm.utils.GenUtils;
import mjson.Json;
/**
* Resolves all message variables that refer to an AnswerValue/Object(s) of a ServiceQuestion.
* Each variable will be resolved dependent on its hasLegacyCode property.
* If hasLegacyCode is no REGEXP, one answer for a question, whose IRI fragments ends with the legacy code will be returned.
* Else if hasLegacyCode starts with REGEX_DETECT_STR "(", the result will contain answers to all questions matching the regex pattern by iri fragment in the given sr.
* (Separarted by ";", no ")
*
* Date answer literals will be formatted. Optionally a DateModifier can be passed to the constructor for date manipulation, if dates are detected.
*
* @author Thomas Hilpold
*/
public class AnswerResolver implements VariableResolver
{
public static final String[] DATE_FORMAT_PATTERNS = new String[]
{ "MMM d, yyyy", //'Mon DD, YYYY'
"MM-dd-yyyy h:mm aa" //'MON-DD-YYYY HH:MI AM '
};
public static boolean DBG = true;
public static final String HAS_LEGACY_CODE_DP = "legacy:hasLegacyCode";
public static final char ANSWER_SEPARATOR_CHAR = ';';
public static final String REGEX_DETECT_STR = "(";
private DateModifier dateModifier = null;
/**
* Creates an AnswerResolver. Detected dates will be formatted, but not modified.
*/
public AnswerResolver() {
//Date modification disabled
}
/**
* Creates an AnswerResolver with date modification enabled.
* If an answer can be parsed as date, the dateModifier will be called
* to modify the date.
*
* @param dateModifier a date modifier
* @throws IllegalArgumentException if dateModifier is null
*/
public AnswerResolver(DateModifier dateModifier) {
if (dateModifier == null) throw new IllegalArgumentException("DateModifier must not be null for this contructor.");
//Date modification enabled
this.dateModifier = dateModifier;
}
@Override
public String resolve(String variableName, Json sr, Properties properties)
{
String result = null;
//1. Get Var individual and hasLegacyCode
OWLNamedIndividual varInd = MessageManager.findIndividualFromVariable(variableName);
if (varInd == null)
{
System.err.println("AnswerResolver Error: varInd not found for: " + variableName );
return null;
}
OWLLiteral questionEndsWithLit = OWL.dataProperty(varInd, HAS_LEGACY_CODE_DP);
if (questionEndsWithLit == null || questionEndsWithLit.getLiteral() == null
|| questionEndsWithLit.getLiteral().isEmpty())
{
System.err.println("AnswerResolver Error: questionEndsWithLit not found for: " + variableName );
return null;
}
String serviceQuestionEndsWith = questionEndsWithLit.getLiteral();
try {
result = resolveQuestionAnswer(sr, serviceQuestionEndsWith);
} catch (Exception e) {
System.err.println("AnswerResolver Error: Exception resolving var " + variableName + " for sr: " + sr.toString());
e.printStackTrace();
}
if (DBG)
{
System.out.println("AnswerResolver: Var: " + variableName + " result: " + result + " code: " + serviceQuestionEndsWith);
}
return result;
}
/**
* Gets all answers to a question matching the serviceQuestionSuffixOrRegex.
* A regexp is detected by startswith REGEX_DETECT_STR.
* If the rexep matches multiple service questions in the same SR, the answers
* will be separated by ANSWER_SEPARATOR_CHAR ";" (e.g. A1;A2;).
*
* @param sr
* @param serviceQuestionSuffix
* @return
*/
String resolveQuestionAnswer(Json sr, String serviceQuestionSuffixOrRegex)
{
//TODO: use a cache for allAnswers by key (BOID UPDATEDTIME)
List<Json> allAnswers = sr.at("hasServiceAnswer").isArray()? sr.at("hasServiceAnswer").asJsonList() : Collections.singletonList(sr.at("hasServiceAnswer"));
//ServiceRequestReportUtil.getAllServiceAnswers(sr);
if (serviceQuestionSuffixOrRegex.startsWith(REGEX_DETECT_STR))
{
Pattern serviceFieldFragmentRegex;
try {
serviceFieldFragmentRegex = Pattern.compile(serviceQuestionSuffixOrRegex);
} catch (Exception e)
{
System.err.println("AnswerResolver. Pattern.compile failed for pattern: " + serviceQuestionSuffixOrRegex + " aborting answer resolving");
return null;
}
return getAllServiceAnswers(serviceFieldFragmentRegex, allAnswers);
}
else
{
return getServiceAnswer(serviceQuestionSuffixOrRegex, allAnswers);
}
}
String getServiceAnswer(String serviceFieldFragmentSuffix, List<Json> allAnswers)
{
String result = "";
for (Json tempObj : allAnswers)
{
String candidate = tempObj.at("hasServiceField", Json.object()).at("iri", "").asString();
if (candidate.endsWith(serviceFieldFragmentSuffix)) {
//hasAnswerObject could be array of {iri, label};
if (tempObj.has("hasAnswerObject")) {
Json hasAnswerObjectArrOrObj = tempObj.at("hasAnswerObject");
List<Json> hasAnswerObjectlist = hasAnswerObjectArrOrObj.isArray()? hasAnswerObjectArrOrObj.asJsonList() : Collections.singletonList(hasAnswerObjectArrOrObj);
int i = 0;
for (Json answerObject : hasAnswerObjectlist)
{
result += answerObject.at("label", "").asString();
if (i == hasAnswerObjectlist.size() - 2) {
if (hasAnswerObjectlist.size() > 2) {
result += ",";
}
result += " and ";
}
else if (i < hasAnswerObjectlist.size() - 1) result += ", ";
i++;
}
}
else if (tempObj.has("hasAnswerValue"))
{
result = tempObj.at("hasAnswerValue", "").asString();
result = tryDateFormat(result);
}
// else not valid
}
}
return result;
}
/**
* Gets all service answers concatenated by ANSWER_SEPARATOR_CHAR, whose IRI fragment matches the given regex pattern.
* @param serviceFieldFragmentRegex
* @param allAnswers
* @return
*/
String getAllServiceAnswers(Pattern serviceFieldFragmentRegex, List<Json> allAnswers)
{
String result = "";
for (Json tempObj : allAnswers)
{
boolean found = false;
Matcher candidateFragmentMatcher;
String candidateFull = tempObj.at("hasServiceField", Json.object()).at("iri", "").asString();
candidateFragmentMatcher = serviceFieldFragmentRegex.matcher((IRI.create(candidateFull).getFragment()));
if (candidateFragmentMatcher.matches()) {
//hasAnswerObject could be array of {iri, label};
if (tempObj.has("hasAnswerObject")) {
found = true;
Json hasAnswerObjectArrOrObj = tempObj.at("hasAnswerObject");
List<Json> hasAnswerObjectlist = hasAnswerObjectArrOrObj.isArray()? hasAnswerObjectArrOrObj.asJsonList() : Collections.singletonList(hasAnswerObjectArrOrObj);
int i = 0;
for (Json answerObject : hasAnswerObjectlist)
{
result += answerObject.at("label", "").asString();
if (i == hasAnswerObjectlist.size() - 2) {
if (hasAnswerObjectlist.size() > 2) {
result += ",";
}
result += " and ";
}
else if (i < hasAnswerObjectlist.size() - 1) result += ", ";
i++;
}
}
else if (tempObj.has("hasAnswerValue"))
{
found = true;
result += tempObj.at("hasAnswerValue", "").asString();
}
// else not valid
// ans1;ans2;ans3;
if (found) result += ANSWER_SEPARATOR_CHAR;
}
}
return result;
}
/**
* Attempt to detect if a string is a date and formats it accordingly.
* Day only format is used if hours and minutes are zero.
* Optionally a dateModifier will be called to manipulate the date.
*
* @param dateLiteralCandidate
* @return
*/
String tryDateFormat(String dateLiteralCandidate) {
String result;
Date date = tryParseDate(dateLiteralCandidate);
if (date != null) {
if (isDateModificationEnabled()) {
date = dateModifier.modifyDate(date);
}
result = formatDateOrDateTime(date);
} else {
result = dateLiteralCandidate;
}
return result;
}
/**
* Tries to parse a date if the parameter contains T and 00.
* e.g.: 2016-11-04T20:09:28.916-0400
* @param dateLiteralCandidate
* @return null or a valid date
*/
Date tryParseDate(String dateLiteralCandidate) {
if (dateLiteralCandidate == null) return null;
Date result = null;
if (dateLiteralCandidate != null && dateLiteralCandidate.contains("T") && dateLiteralCandidate.contains("00")) {
try {
Date date = GenUtils.parseDate(dateLiteralCandidate.trim());
result = date;
} catch (Exception e) {
System.err.println("Ignored that date parsing failed for " + dateLiteralCandidate);
}
}
return result;
}
/**
* Never true, intended to be overwritten by subclass.
* @return
*/
boolean isDateModificationEnabled() {
return dateModifier != null;
}
/**
* Formats date as date, if hour and minute are zero, dateTime otherwise.
* @param date
* @return
*/
String formatDateOrDateTime(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
SimpleDateFormat dFormat;
if (cal.get(Calendar.HOUR_OF_DAY) == 0 && cal.get(Calendar.MINUTE) == 0) {
dFormat = new SimpleDateFormat(DATE_FORMAT_PATTERNS[0]);
} else {
dFormat = new SimpleDateFormat(DATE_FORMAT_PATTERNS[1]);
}
return dFormat.format(date);
}
}