/******************************************************************************* * 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 static org.sharegov.cirm.OWL.*; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.xml.datatype.DatatypeFactory; import mjson.Json; import org.semanticweb.owlapi.model.OWLAxiom; import org.semanticweb.owlapi.model.OWLClass; import org.semanticweb.owlapi.model.OWLClassExpression; import org.semanticweb.owlapi.model.OWLDataFactory; import org.semanticweb.owlapi.model.OWLDataProperty; import org.semanticweb.owlapi.model.OWLIndividual; import org.semanticweb.owlapi.model.OWLLiteral; import org.semanticweb.owlapi.model.OWLNamedIndividual; import org.semanticweb.owlapi.model.OWLObjectPropertyAssertionAxiom; import org.semanticweb.owlapi.model.OWLOntology; import org.semanticweb.owlapi.model.OWLOntologyManager; import org.semanticweb.owlapi.vocab.OWL2Datatype; import org.sharegov.cirm.BOntology; import org.sharegov.cirm.Refs; import org.sharegov.cirm.StartUp; import org.sharegov.cirm.OWL; import org.sharegov.cirm.owl.Model; import org.sharegov.cirm.rest.LegacyEmulator; import org.sharegov.cirm.utils.ActivityUtils; import org.sharegov.cirm.utils.GenUtils; import org.sharegov.cirm.utils.ThreadLocalStopwatch; /** * The ActivityManager is responsible for creating and updating serviceActivities as well as executing configured triggers, * such as close case on outcome, execute email templates and trigger the creation of new activities or dependent service cases. * Furthermore, it's submitting tasks to the time machine for overdue callback checks. * * @author Syed * @author Tom */ public class ActivityManager { public static boolean DBG = true; public static boolean THROW_ALL_EXC = false; public static boolean USE_MESSAGE_MANAGER = true; public static boolean USE_TIME_MACHINE = true; public static final String ACTIVITY_AUTO = "auto"; public static final String ACTIVITY_ERROR = "error"; private final ActivityUtils utils = new ActivityUtils(); /** * Counts time machine calls since activitymanager creation for each task. * As activitymanagers should be created once per transaction execution, it can ensure proper time machine task overwrites on retry. * Task keys should remain constant during all retries of a transaction. * Assuming single threaded execution. */ private final HashMap<String, Integer> timeMachineTaskToIndex = new HashMap<String, Integer>(); /** * Gets a unique taskId within this transaction execution that remains equal on transaction retries. * Use with FRAGMENTA-OVERDUE-FRAGMENTB or DELAY-FRAGMENTA. * Not thread safe, assuming single worker thread. * hilpold * @param task a task string that should be equal on retries and therefore not contain regenerated timestamps or any sequence ids. * @return task + indexForThisTask */ private String getNextTimeMachineTaskIDFor(String task) { Integer curIdx = timeMachineTaskToIndex.get(task); if (curIdx == null) curIdx = 0; else curIdx ++; timeMachineTaskToIndex.put(task, curIdx); return task + " idx " + curIdx.toString(); } private String findAutoAssignment(OWLNamedIndividual rule, BOntology bo, OWLNamedIndividual serviceActivity, OWLNamedIndividual outcome) { if (rule.getIRI().equals(Model.legacy("AssignActivityToCaseCreator"))) { OWLLiteral createdBy = bo.getDataProperty("isCreatedBy"); if (createdBy != null) return createdBy.getLiteral(); } // else if (rule.getIRI().toString().equals(OWLRefs.LEGACY_PREFIX + "#AssignActivityByGeoArea")) // { // OWLNamedIndividual srType = individual(bo.getTypeIRI()); // OWLLiteral geoArea = dataProperty(srType, "legacy:hasGeoAreaCode"); // // } // All other rules: Set<OWLClass> S = reasoner().getTypes(rule, true).getFlattened(); if (S.isEmpty()) return null; OWLClass type = S.iterator().next(); if (type.getIRI().equals(Model.legacy("AssignActivityToUserRule"))) { OWLLiteral assignment = dataProperty(rule, "hasUsername"); if (assignment != null) return assignment.getLiteral(); } else if (type.getIRI().equals(Model.legacy("AssignActivityFromGeoAttribute"))) { // Get property info first Json locationInfo = Refs.gisClient.resolve().getLocationInfo( Double.parseDouble(bo.getDataProperty("hasXCoordinate").getLiteral()), Double.parseDouble(bo.getDataProperty("hasYCoordinate").getLiteral()), null); for (OWLNamedIndividual assignment : OWL.objectProperties(rule, "legacy:hasAssignmentRule")) { OWLLiteral attributeName = dataProperty(assignment, "hasName"); OWLNamedIndividual layer = objectProperty(assignment, "hasGisLayer"); OWLLiteral layerName = dataProperty(layer, "hasName"); OWLLiteral valueExpression = dataProperty(assignment, "hasValue"); OWLLiteral username = dataProperty(assignment, "hasUsername"); if (Refs.gisClient.resolve().testLayerValue(locationInfo, layerName.getLiteral(), attributeName.getLiteral(), valueExpression.getLiteral())) return username.getLiteral(); } } else if (type.getIRI().equals(Model.legacy("AssignActivityToOutcomeEmail"))) { return getAssignActivityToOutcomeEmail(outcome, bo); } return null; } /** * Gets all activities defined for a ServiceCase type * in the legacy ontology. * * @param serviceCaseType * @return A set containing the iri of each activity. * */ public Set<OWLNamedIndividual> getActivities(OWLClass serviceCaseType) { Set<OWLNamedIndividual> activities = reasoner().getObjectPropertyValues( individual(serviceCaseType.getIRI()), objectProperty(fullIri("legacy:hasActivity")) ).getFlattened(); return activities; } /** * Creates the serviceActivity axioms for the business object * as defined by the ontology property isAutoCreate 'Y' * for the activityType configuration of the * supplied serviceCaseType. * If the activity was disabled, it will not be created. * * @param serviceCaseType * @param bo */ public void createDefaultActivities(OWLClass serviceCaseType, BOntology bo, Date createdDate, List<CirmMessage> messages) { for(OWLNamedIndividual activityType : getActivities(serviceCaseType)) { if (utils.isAutoCreate(activityType) && !utils.isDisabled(activityType)) { Date completedDate = null; OWLNamedIndividual outcome = null; List<CirmMessage> localMessages = new ArrayList<CirmMessage>(); createActivity(activityType, outcome, null, null, bo, createdDate, completedDate, null, localMessages); for (CirmMessage lm : localMessages) { lm.addExplanation("createDefaultActivities T: " + serviceCaseType.getIRI().getFragment()); messages.add(lm); } } } //for createActivitiesFromQuestions(bo, messages); } /** * Creates activities that should be created when case transitions into pending state, if any are defined for the type and intake method. * @param serviceCaseType * @param bo * @param createdDate * @param messages */ public void createAutoOnPendingActivities(BOntology bo, Date createdDate, List<CirmMessage> messages) { try { OWLNamedIndividual intakeMethodInd = bo.getObjectProperty("legacy:hasIntakeMethod"); OWLNamedIndividual srTypeInd = OWL.individual("legacy:" + bo.getTypeIRI().getFragment()); ThreadLocalStopwatch.start("START createAutoOnPendingActivities " + srTypeInd + " IntakeMethod: " + intakeMethodInd); Set<OWLNamedIndividual> autoActivities = utils.getAutoOnPendingActivities(srTypeInd, intakeMethodInd); for (OWLNamedIndividual activityType : autoActivities) { if (!utils.isDisabled(activityType)) { ThreadLocalStopwatch.now("Found AutoOnPendingActivity " + activityType); Date completedDate = null; OWLNamedIndividual outcome = null; List<CirmMessage> localMessages = new ArrayList<CirmMessage>(); createActivity(activityType, outcome, null, null, bo, createdDate, completedDate, null, localMessages); for (CirmMessage lm : localMessages) { lm.addExplanation("createAutoOnPendingActivities T: " + srTypeInd.getIRI().getFragment()); messages.add(lm); } } } //for ThreadLocalStopwatch.start("END createAutoOnPendingActivities "); } catch(Exception e) { ThreadLocalStopwatch.fail("FAIL createAutoOnPendingActivities " + e); e.printStackTrace(); } } /** * Creates activities that should be created when case transitions into locked state, if any are defined for the bo type and intake method. * @param serviceCaseType * @param bo * @param createdDate * @param messages */ public void createAutoOnLockedActivities(BOntology bo, Date createdDate, List<CirmMessage> messages) { try { OWLNamedIndividual intakeMethodInd = bo.getObjectProperty("legacy:hasIntakeMethod"); OWLNamedIndividual srTypeInd = OWL.individual("legacy:" + bo.getTypeIRI().getFragment()); ThreadLocalStopwatch.start("START createAutoOnLockedActivities " + srTypeInd + " IntakeMethod: " + intakeMethodInd); Set<OWLNamedIndividual> autoActivities = utils.getAutoOnLockedActivities(srTypeInd, intakeMethodInd); for (OWLNamedIndividual activityType : autoActivities) { if (!utils.isDisabled(activityType)) { ThreadLocalStopwatch.now("Found AutoOnLockedActivity " + activityType); Date completedDate = null; OWLNamedIndividual outcome = null; List<CirmMessage> localMessages = new ArrayList<CirmMessage>(); createActivity(activityType, outcome, null, null, bo, createdDate, completedDate, null, localMessages); for (CirmMessage lm : localMessages) { lm.addExplanation("createAutoOnLockedActivities T: " + srTypeInd.getIRI().getFragment()); messages.add(lm); } } } //for ThreadLocalStopwatch.start("END createAutoOnLockedActivities "); } catch(Exception e) { ThreadLocalStopwatch.fail("FAIL createAutoOnLockedActivities " + e); e.printStackTrace(); } } /** * Creates an activity now and ignores a potentially configured occurday setting. * This method is used to create already scheduled activities on TM callback. * * @param activityType * @param bo * @param messages */ public void createActivityOccurNow(OWLNamedIndividual activityType, BOntology bo, List<CirmMessage> messages) { createActivityImpl(activityType, null, null, null, bo, null, null, null, messages, true); } /** * Creates an activity or schdules it for creation (occurdays > 0). * @param activityType * @param details * @param isAssignedTo * @param bo * @param createdDate * @param createdBy * @param messages */ public void createActivity(OWLNamedIndividual activityType, String details, String isAssignedTo, BOntology bo, Date createdDate, String createdBy, List<CirmMessage> messages) { //Don't set the defaultOutcome, first the activityType needs to be accepted by the Assignee!! // Set<OWLNamedIndividual> outcomes = reasoner().getObjectPropertyValues( // individual(activity.getIRI()), // objectProperty("legacy:hasDefaultOutcome")) // .getFlattened(); // if(outcomes.size() > 0) // createActivity(activity, outcomes.iterator().next(), details, isAssignedTo, bo); // else createActivity(activityType, null, details, isAssignedTo, bo, createdDate, null, createdBy, messages); } /** * Creates an activity or schedules it for creation (occurdays >0). * * @param activityType * @param outcome * @param details * @param isAssignedTo * @param bo * @param createdDate * @param completedDate * @param createdBy * @param messages a list of messages to add messages to. * @return a Pair of message and template */ public void createActivity(OWLNamedIndividual activityType, OWLNamedIndividual outcome, String details, String isAssignedTo, BOntology bo, Date createdDate, Date completedDate, String createdBy, List<CirmMessage> messages) { createActivityImpl(activityType, outcome, details, isAssignedTo, bo, createdDate, completedDate, createdBy, messages, false); } /** * Creates an activity with all side effect. Delays creation through Time Machine, if activity type's occurdays * if configured as > 0.0f and ignoreOccurdays is false. * @param activityType * @param outcome * @param details * @param isAssignedTo * @param bo * @param createdDate * @param completedDate * @param createdBy * @param messages a list of messages to add messages to. * @param ignoreOccurDays ignore activity type occur day setting and create activity now. * @return a Pair of message and template */ private void createActivityImpl(OWLNamedIndividual activityType, OWLNamedIndividual outcome, String details, String isAssignedTo, BOntology bo, Date createdDate, Date completedDate, String createdBy, List<CirmMessage> messages, boolean ignoreOccurDays) { try { OWLOntology o = bo.getOntology(); OWLOntologyManager manager = o.getOWLOntologyManager(); OWLDataFactory factory = manager.getOWLDataFactory(); OWLClass activityTypeClass = owlClass("legacy:ServiceActivity"); Calendar now = Calendar.getInstance(); Calendar calcreated = Calendar.getInstance(); calcreated.setTime(createdDate != null ? createdDate : now.getTime()); boolean useWorkWeek = false; float suspenseDaysConfiguredValue = determineSuspenseDays(activityType); float occurDaysConfiguredValue = determineOccurDays(activityType); //A) Check for immediate auto default outcome, if the activity type has it configured if (outcome == null && suspenseDaysConfiguredValue == 0) { outcome = determineAutoDefaultOutcome(activityType); } //B) Check for user base date configured and user answer provided for occur or suspense usage. Date occurOrSuspenseBaseDate = determineDueBaseDate(bo, activityType, now.getTime()); Set<OWLLiteral> businessCodes = reasoner().getDataPropertyValues( activityType, dataProperty("legacy:hasBusinessCodes")); if(businessCodes.size() > 0) { useWorkWeek = businessCodes.iterator().next().getLiteral().contains("5DAYWORK"); } /** * If activityType hasOccurDays > 0, the set a timer for delayed * activity creation */ if(occurDaysConfiguredValue > 0 && !ignoreOccurDays) { scheduleActivityCreationOccurDays(bo, activityType, occurOrSuspenseBaseDate, occurDaysConfiguredValue, useWorkWeek, details, isAssignedTo); return;//activity creation will be delayed. } OWLNamedIndividual serviceActivity = factory.getOWLNamedIndividual( fullIri(activityTypeClass.getIRI().getFragment() + Refs.idFactory.resolve().newId(null))); manager.addAxiom(o, factory.getOWLClassAssertionAxiom(activityTypeClass, serviceActivity)); manager.addAxiom(o,factory.getOWLObjectPropertyAssertionAxiom( objectProperty("legacy:hasActivity") , serviceActivity, activityType)); OWLLiteral createdDateLiteral = factory.getOWLLiteral(DatatypeFactory.newInstance() .newXMLGregorianCalendar((GregorianCalendar)calcreated) .toXMLFormat() ,OWL2Datatype.XSD_DATE_TIME_STAMP); manager.addAxiom(o, factory.getOWLDataPropertyAssertionAxiom( dataProperty("hasDateCreated"), serviceActivity, createdDateLiteral )); manager.addAxiom(o, factory.getOWLDataPropertyAssertionAxiom( dataProperty("legacy:hasUpdatedDate"),serviceActivity, createdDateLiteral )); if(details != null) { manager.addAxiom(o,factory.getOWLDataPropertyAssertionAxiom( dataProperty("legacy:hasDetails") , serviceActivity, details)); } if (isAssignedTo == null) { Set<OWLNamedIndividual> assignRules = OWL.objectProperties(activityType, "legacy:hasAssignmentRule"); for (OWLNamedIndividual rule : assignRules) { isAssignedTo = findAutoAssignment(rule, bo, serviceActivity, outcome); if (isAssignedTo != null) break; } } if(isAssignedTo != null) { manager.addAxiom(o, factory.getOWLDataPropertyAssertionAxiom( dataProperty("legacy:isAssignedTo"), serviceActivity, isAssignedTo)); } if(createdBy != null) { manager.addAxiom(o,factory.getOWLDataPropertyAssertionAxiom( dataProperty("isCreatedBy") , serviceActivity, createdBy)); } if(outcome != null || completedDate != null) { if (outcome == null) outcome = OWL.individual("legacy:OUTCOME_COMPLETE"); manager.addAxiom(o,factory.getOWLObjectPropertyAssertionAxiom( objectProperty("legacy:hasOutcome") , serviceActivity, outcome)); Calendar calcompleted = Calendar.getInstance(); calcompleted.setTime(completedDate != null ? completedDate : calcreated.getTime()); OWLLiteral completedDateLiteral = factory.getOWLLiteral(DatatypeFactory.newInstance() .newXMLGregorianCalendar((GregorianCalendar)calcompleted) .toXMLFormat() ,OWL2Datatype.XSD_DATE_TIME_STAMP); manager.addAxiom(o, factory.getOWLDataPropertyAssertionAxiom( dataProperty("legacy:hasCompletedTimestamp"),serviceActivity, completedDateLiteral)); List<CirmMessage> localMessages = new ArrayList<CirmMessage>(); checkOutcomeTrigger(serviceActivity, activityType, outcome, isAssignedTo, bo, localMessages); for (CirmMessage lm : localMessages) { lm.addExplanation("createactivity.checkoutcomeTrigger " + outcome.getIRI().getFragment() + " Act: " + activityType.getIRI().getFragment()); messages.add(lm); } } if (suspenseDaysConfiguredValue > 0) { Date calculatedDueDate = calculateScheduledDelayDate(occurOrSuspenseBaseDate, suspenseDaysConfiguredValue, useWorkWeek); Calendar due = Calendar.getInstance(); due.setTime(calculatedDueDate); OWLLiteral dueDate = factory.getOWLLiteral(DatatypeFactory.newInstance() .newXMLGregorianCalendar((GregorianCalendar)due) .toXMLFormat() ,OWL2Datatype.XSD_DATE_TIME_STAMP); manager.addAxiom(o, factory.getOWLDataPropertyAssertionAxiom( dataProperty("legacy:hasDueDate"), serviceActivity, dueDate )); Set<OWLNamedIndividual> overdueActivity = reasoner().getObjectPropertyValues( activityType, objectProperty("legacy:hasOverdueActivity")) .getFlattened(); if(overdueActivity.size() > 0) { OWLNamedIndividual overdueActivityType = overdueActivity.iterator().next(); scheduleOverdueActivityCreationAtDueDate(bo, overdueActivityType, due, serviceActivity, activityType); } } manager.addAxiom(o, factory.getOWLObjectPropertyAssertionAxiom( objectProperty("legacy:hasServiceActivity") , bo.getBusinessObject(), serviceActivity)); OWLNamedIndividual emailTemplate = objectProperty(activityType, "legacy:hasEmailTemplate"); if(emailTemplate != null && USE_MESSAGE_MANAGER) { if (hasAssignActivityToOutcomeEmail(activityType) && isAssignedTo == null) { //prevent email creation for serviceActivity as it should be created on a later update, where an outcome email is found. System.out.println("createActivity: email creation prevented, because serviceActivity " + serviceActivity + " Type: " + activityType + " hasAssignActivityToOutcomeEmail, was executed and still noone assigned."); } else { CirmMessage m = MessageManager.get().createMessageFromTemplate(bo, dataProperty(activityType, "legacy:hasLegacyCode"), emailTemplate); if (m!= null) { m.addExplanation("createActivity " + serviceActivity.getIRI().getFragment() + " Tpl: " + emailTemplate.getIRI().getFragment()); messages.add(m); } else System.err.println("ActivityManager: created Message was Null for " + (bo != null? bo.getObjectId() : bo) + "act: " + serviceActivity + " actT:" + activityType + " tmpl: " + emailTemplate); } } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } private String getServerUrl() { try { OWLNamedIndividual operationsService = Refs.configSet.resolve().get("OperationsRestService"); OWLLiteral osUrl = dataProperty(operationsService, "hasUrl"); return osUrl.getLiteral(); }catch(Exception e) { e.printStackTrace(System.out); if (THROW_ALL_EXC) throw new RuntimeException(e); else return null; } } /** * Updates the passed in Business Ontology's ServiceActivity Axioms with the passed in parameter values. * Any parameter may be null. * * @param activityType : String representation of the ActivityType * @param serviceActivity : String representation of the ServiceActivity to be updated * @param outcome : String representation of Outcome of the ServiceActivity * @param details : Specified Details for the ServiceActivity by the user * @param assignedTo : The person's email/name/eNet No. to whom this ServiceActivity is assigned * @param modifiedBy : The eNet No. of the User who sent in the request * @param isAccepted : true if the Assignee accepts the ServiceActivity * @param bo : The Business Ontology of the Service Request */ public void updateActivity(String activityType, String serviceActivity, String outcome, String details, String assignedTo, String modifiedBy, boolean isAccepted, BOntology bo, List<CirmMessage> messages) { if(serviceActivity != null) { OWLOntology o = bo.getOntology(); OWLOntologyManager manager = o.getOWLOntologyManager(); OWLDataFactory factory = manager.getOWLDataFactory(); OWLNamedIndividual serviceActivityIndividual = factory.getOWLNamedIndividual(fullIri(serviceActivity)); OWLNamedIndividual outcomeIndividual = null; if(outcome != null) outcomeIndividual = factory.getOWLNamedIndividual(fullIri(outcome)); //Apply default default outcome only if the ServiceActivity isAccepted by the Assignee if(outcome == null && isAccepted == true) { OWLNamedIndividual activityTypeInd = individual(activityType); Set<OWLNamedIndividual> outcomes = reasoner().getObjectPropertyValues( activityTypeInd, objectProperty("legacy:hasDefaultOutcome")).getFlattened(); if(outcomes.size() > 0) outcomeIndividual = outcomes.iterator().next(); } updateActivity(serviceActivityIndividual, outcomeIndividual, details, assignedTo, modifiedBy, bo, messages); } } /** * Updates the serviceActivity by setting auto default outcome if no outcome is set yet. * @param serviceActivity */ public void updateActivityIfAutoDefaultOutcome(OWLNamedIndividual serviceActivity, BOntology bo, List<CirmMessage> messages) { OWLNamedIndividual existingOutcome = bo.getObjectProperty(serviceActivity, "legacy:hasOutcome"); if (existingOutcome == null) { OWLNamedIndividual activityType = objectProperty(serviceActivity, "legacy:hasActivity", bo.getOntology()); if (activityType != null) { OWLNamedIndividual autoDefaultOutcome = determineAutoDefaultOutcome(activityType); if (autoDefaultOutcome != null) { updateActivity(serviceActivity, autoDefaultOutcome, null, null, "auto", bo, messages); } } } } /** * Updates the passed in Business Ontology's ServiceActivity Axioms with the passed in parameter values. * If any of the parameter values are null then that property is ignored. * * @param serviceActivity : The ServiceActivity Individual to be updated * @param outcome : The Outcome Individual which is to be set as the Outcome of the ServiceActivity (can be null) * @param details : Specified Details for the ServiceActivity by the user (can be null) * @param assignedTo : The person's email/name/eNet No. to whom this ServiceActivity is assigned (can be null) * @param modifiedBy : The eNet No. of the User who sent in the request (can be null) * @param bo : The Business Ontology of the Service Request */ public void updateActivity(OWLNamedIndividual serviceActivity, OWLNamedIndividual outcome, String details, String assignedTo, String modifiedBy, BOntology bo, List<CirmMessage> messages) { boolean createMessageFromTemplate = false; try { OWLOntology o = bo.getOntology(); OWLOntologyManager manager = o.getOWLOntologyManager(); OWLDataFactory factory = manager.getOWLDataFactory(); //06-20-2013 syed - Use the SR hasDateLastModified as the ServiceActivity hasUpdatedDate. OWLLiteral updatedDate = bo.getDataProperty("hasDateLastModified"); //2016.11.05 hilpold - if sr was never updated, use now. if (updatedDate == null) { updatedDate = factory.getOWLLiteral(GenUtils.formatDate(new Date()), OWL2Datatype.XSD_DATE_TIME); } bo.deleteDataProperty(serviceActivity, dataProperty("legacy:hasUpdatedDate")); manager.addAxiom(o, factory.getOWLDataPropertyAssertionAxiom( dataProperty("legacy:hasUpdatedDate"),serviceActivity, updatedDate )); if(details != null) { bo.deleteDataProperty(serviceActivity, dataProperty("legacy:hasDetails")); manager.addAxiom(o,factory.getOWLDataPropertyAssertionAxiom( dataProperty("legacy:hasDetails") , serviceActivity, details)); } if(modifiedBy != null) { bo.deleteDataProperty(serviceActivity, dataProperty("isModifiedBy")); manager.addAxiom(o,factory.getOWLDataPropertyAssertionAxiom( dataProperty("isModifiedBy") , serviceActivity, modifiedBy)); } if(outcome != null) { OWLNamedIndividual activityType = objectProperty(serviceActivity, "legacy:hasActivity", bo.getOntology()); if (activityType != null) { //HGDB fix. Might be impl; need HGDB for allProperties. activityType = OWL.individual(activityType.getIRI()); bo.deleteObjectProperty(serviceActivity, objectProperty("legacy:hasOutcome")); manager.addAxiom(o,factory.getOWLObjectPropertyAssertionAxiom( objectProperty("legacy:hasOutcome") , serviceActivity, outcome )); bo.deleteDataProperty(serviceActivity, dataProperty("legacy:hasCompletedTimestamp")); manager.addAxiom(o, factory.getOWLDataPropertyAssertionAxiom( dataProperty("legacy:hasCompletedTimestamp"),serviceActivity, updatedDate )); List<CirmMessage> localMessages = new ArrayList<CirmMessage>(); checkOutcomeTrigger(serviceActivity, activityType, outcome, assignedTo, bo, localMessages); if (assignedTo == null) { if (hasAssignActivityToOutcomeEmail(activityType)) { assignedTo = getAssignActivityToOutcomeEmail(outcome, bo); if (assignedTo != null) { //hilpold assign here??? axiom createMessageFromTemplate = true; } } } for (CirmMessage lm : localMessages) { lm.addExplanation("updateActivity.checkoutcomeTrigger " + outcome.getIRI().getFragment() + "Act: " + activityType.getIRI().getFragment()); messages.add(lm); } } } //hilpold this has to happen after (!) hasAssignActivityToOutcomeEmail. if(assignedTo != null) { bo.deleteDataProperty(serviceActivity, dataProperty("legacy:isAssignedTo")); manager.addAxiom(o,factory.getOWLDataPropertyAssertionAxiom( dataProperty("legacy:isAssignedTo") , serviceActivity, assignedTo)); } //TODO: if time based activity then update time record. if (createMessageFromTemplate) { OWLNamedIndividual activityType = objectProperty(serviceActivity, "legacy:hasActivity", bo.getOntology()); //HGDB error getting data properties for non HGDB individual activitytype; convert to hgdb: activityType = OWL.individual(activityType.getIRI()); OWLNamedIndividual emailTemplate = objectProperty(activityType, "legacy:hasEmailTemplate"); if(emailTemplate != null && USE_MESSAGE_MANAGER) { System.out.println("updateactivity & email: aType: " + activityType); CirmMessage m = MessageManager.get().createMessageFromTemplate(bo, dataProperty(activityType, "legacy:hasLegacyCode"), emailTemplate); if (m!= null) { m.addExplanation("updateActivity outcomeEmailAssign " + serviceActivity.getIRI().getFragment() + " AType: " + activityType + " Outcome: " + outcome + " Tpl: " + emailTemplate.getIRI().getFragment()); messages.add(m); } else System.err.println("ActivityManager: created Message was Null for " + (bo != null? bo.getObjectId() : bo) + " act:" + activityType + " tmpl: " + emailTemplate); } } }catch (Exception e) { e.printStackTrace(); if (THROW_ALL_EXC) throw new RuntimeException(e); } } public void deleteActivity(OWLNamedIndividual serviceActivity, BOntology bo) { OWLOntology o = bo.getOntology(); OWLOntologyManager manager = o.getOWLOntologyManager(); OWLDataFactory factory = manager.getOWLDataFactory(); manager.removeAxiom(o, factory.getOWLObjectPropertyAssertionAxiom( objectProperty("legacy:hasServiceActivity") , bo.getBusinessObject(), serviceActivity)); manager.removeAxioms(o,o.getAxioms(serviceActivity)); } private void checkOutcomeTrigger(OWLNamedIndividual serviceActivity, OWLNamedIndividual activityType, OWLNamedIndividual outcome, String assignedTo, BOntology bo , List<CirmMessage> messages) { //Create activityType triggers. triggerActivityAssignments(serviceActivity, activityType, outcome, assignedTo, bo, messages); //Create referral SRs. triggerReferralCaseOnOutcome(outcome, bo); //close service case on outcome triggerCloseCaseOnOutcome(serviceActivity, outcome, bo, messages); //Send email when status changes to X-Error triggerSendEmailOnOutCome(outcome, bo, messages); } /** * Sends an email to WCS/Support group when an interface error is rejected by the department and the SR status is changed to X-Error * @param outcome : The Outcome Individual which is to be set as the Outcome of the ServiceActivity (can be null) * @param bo : The Business Ontology of the Service Request * @param messages : */ private void triggerSendEmailOnOutCome(OWLNamedIndividual outcome, BOntology bo, List<CirmMessage> messages) { if (outcome == null) return; // ensure HGDB individual if not yet in repo: outcome = OWL.individual(outcome.getIRI()); System.out.println("Outcome:"+outcome); if (!reasoner().getInstances( OWL.and(OWL.oneOf(outcome), OWL.some(OWL.objectProperty("legacy:hasLegacyEvent"), OWL.owlClass("legacy:SendEmail"))),true).isEmpty()) { OWLNamedIndividual outcomeEvent = OWL.objectProperty(outcome, "legacy:hasLegacyEvent"); System.out.println("outcomeEvent:"+outcomeEvent); OWLNamedIndividual emailTemplate = objectProperty(outcomeEvent,"legacy:hasEmailTemplate"); System.out.println("emailTemplate:"+emailTemplate); Set<OWLNamedIndividual> srType = objectProperties(outcomeEvent,"legacy:hasServiceCase"); System.out.println("srType:"+srType); OWLIndividual boSRType = individual(bo.getTypeIRI("legacy")); System.out.println("boSRType:"+boSRType); if (emailTemplate != null && USE_MESSAGE_MANAGER && srType.contains(boSRType)) { System.out.println("Sending Email:"); CirmMessage m = MessageManager.get().createMessageFromTemplate(bo, dataProperty(outcomeEvent, "legacy:hasLegacyCode"),emailTemplate); System.out.println("CirmMessage is : "+m); if (m != null) { m.addExplanation("triggerSendEmailOnOutCome " + outcome); messages.add(m); } else System.err.println("ActivityManager: Message is NULL"); } else{ System.out.println(boSRType+ " IS NOT AN INTERFACE SR TYPE: EMAIL WILL NOT BE SENT"); } } } /** * Closes a case using the activity completed date as status change date. * @param serviceActivity * @param outcome * @param bo */ private void triggerCloseCaseOnOutcome(OWLNamedIndividual serviceActivity, OWLNamedIndividual outcome, BOntology bo, List<CirmMessage> messages) { if (serviceActivity == null) throw new IllegalArgumentException(); if (outcome == null) throw new IllegalArgumentException(); if (bo == null) throw new IllegalArgumentException(); OWLNamedIndividual outcomeEvent = OWL.objectProperty(outcome, "legacy:hasLegacyEvent"); if(outcomeEvent != null && outcomeEvent.getIRI().getFragment().equals("CloseServiceCase")) { OWLLiteral statusChangeModifiedOrCreatedBy; OWLNamedIndividual statusChangeStatus; OWLLiteral statusChangeDate; OWLDataFactory df = bo.getOntology().getOWLOntologyManager().getOWLDataFactory(); // User / maybe department statusChangeModifiedOrCreatedBy = bo.getDataProperty(serviceActivity, "mdc:isModifiedBy"); if(statusChangeModifiedOrCreatedBy == null) //the sr is new and not yet modified! statusChangeModifiedOrCreatedBy = bo.getDataProperty(serviceActivity, "mdc:isCreatedBy"); // The new status statusChangeStatus = objectProperty(outcomeEvent, "legacy:hasStatus"); // Use activtiy completion date as status change created, updated and completed date. statusChangeDate = bo.getDataProperty(serviceActivity, "legacy:hasCompletedTimestamp"); // Validation if (statusChangeModifiedOrCreatedBy == null) { System.err.println("Error: triggerCloseCaseOnOutcome statusChangeModifiedOrCreatedBy could not be determined for act " + serviceActivity); System.err.println("Error: SR Number was: " + bo.getObjectId()); statusChangeModifiedOrCreatedBy = df.getOWLLiteral(ACTIVITY_ERROR); } if (statusChangeStatus == null) { System.err.println("Error: triggerCloseCaseOnOutcome statusChangeStatus could not be determined for act " + serviceActivity.getIRI() + " and event " + outcomeEvent.getIRI()); System.err.println("Error: SR Number was: " + bo.getObjectId()); statusChangeStatus = df.getOWLNamedIndividual(fullIri("legacy:C-CLOSED")); } if (statusChangeDate == null) { System.err.println("Error: triggerCloseCaseOnOutcome statusChangeDate could not be determined for act " + serviceActivity.getIRI() + " and event " + outcomeEvent.getIRI()); System.err.println("Error: SR Number was: " + bo.getObjectId()); System.err.println("Error: Using either SRs mdc:hasDateLastModified or mdc:hasDateCreated"); statusChangeDate = bo.getDataProperty("mdc:hasDateLastModified"); if (statusChangeDate == null) statusChangeDate = bo.getDataProperty("mdc:hasDateCreated"); } bo.deleteObjectProperty(bo.getBusinessObject(), "legacy:hasStatus"); bo.addObjectProperty(bo.getBusinessObject(), "legacy:hasStatus", Json.object().set("iri", statusChangeStatus.getIRI().toString())); changeStatus(statusChangeStatus, GenUtils.parseDate(statusChangeDate), statusChangeModifiedOrCreatedBy.getLiteral(), bo, messages); } } private void triggerReferralCaseOnOutcome(OWLNamedIndividual outcome, BOntology bo) { OWLClassExpression q = and(owlClass("legacy:ServiceCaseOutcomeTrigger"), OWL.has(objectProperty("legacy:hasServiceCase"), individual(bo.getTypeIRI("legacy"))), OWL.has(objectProperty("legacy:hasOutcome"), individual(outcome.getIRI())), OWL.some(objectProperty("legacy:hasLegacyEvent"), owlClass("legacy:CreateServiceCase"))); Set<OWLNamedIndividual> createCaseTriggers = reasoner().getInstances(q, false).getFlattened(); for(OWLNamedIndividual trigger : createCaseTriggers) { Set<OWLNamedIndividual> events = reasoner().getObjectPropertyValues(trigger, objectProperty("legacy:hasLegacyEvent")).getFlattened(); for(OWLNamedIndividual event: events) { OWLNamedIndividual srTypeToCreate = objectProperty(event ,"legacy:hasServiceCase"); OWLNamedIndividual statusToSet = objectProperty(event ,"legacy:hasStatus"); if(srTypeToCreate != null && statusToSet != null) { Json referrer = bo.toJSON(); createReferralCase(Long.parseLong(bo.getObjectId()),referrer, OWL.owlClass(srTypeToCreate.getIRI()), statusToSet); } } } } private void triggerActivityAssignments(OWLNamedIndividual serviceActivity, OWLNamedIndividual activityType, OWLNamedIndividual outcome, String assignedTo, BOntology bo, List<CirmMessage> messages) { OWLClassExpression q = and(owlClass("legacy:ActivityTrigger"), OWL.has(objectProperty("legacy:hasActivity"), individual(activityType.getIRI())), OWL.has(objectProperty("legacy:hasOutcome"), individual(outcome.getIRI())), OWL.some(objectProperty("legacy:hasLegacyEvent"), owlClass("legacy:ActivityAssignment"))); Set<OWLNamedIndividual> triggers = reasoner().getInstances(q, false).getFlattened(); Date actCompletedDate; Date newActCreatedDate; try { actCompletedDate = GenUtils.parseDate(bo.getDataProperty(serviceActivity, "legacy:hasCompletedTimestamp")); if (actCompletedDate == null) throw new IllegalStateException("ServiceActivity must be completed and have a completed date."); //12-20-2013 tom/syed/boris Use the triggering activity's completed date as the new activities created date. newActCreatedDate = actCompletedDate; } catch (Exception e) { String msg = "triggerActivityAssignments could not determine dates for serviceActivity: " + serviceActivity.getIRI() + " Type: " + activityType.getIRI() + " Exc was: " + e.toString(); throw new RuntimeException(msg, e); } for(OWLNamedIndividual trigger : triggers) { Set<OWLNamedIndividual> events = reasoner().getObjectPropertyValues(trigger, objectProperty("legacy:hasLegacyEvent")).getFlattened(); for(OWLNamedIndividual event: events) { OWLNamedIndividual a = reasoner().getObjectPropertyValues( event, objectProperty("legacy:hasActivity")) .getFlattened().iterator().next(); Set<OWLNamedIndividual> outcomes = reasoner().getObjectPropertyValues( event, objectProperty("legacy:hasOutcome")) .getFlattened(); //01-07-2014 - removed per Liz's request. // if(outcomes.isEmpty()) // { // //01-03-2014 Syed - If no outcome is configured for the event, then check the default outcome // //of the activity // outcomes = reasoner().getObjectPropertyValues(a, objectProperty("legacy:hasDefaultOutcome")).getFlattened(); // } Set<OWLLiteral> businessCodes = reasoner().getDataPropertyValues( a, dataProperty("legacy:hasBusinessCodes")); boolean dupStaff = false; if(businessCodes.size() > 0) { dupStaff = businessCodes.iterator().next().getLiteral().contains("DUPSTAFF"); } List<CirmMessage> localMessages = new ArrayList<CirmMessage>(); if(outcomes.size() > 0) createActivity(a, outcomes.iterator().next(), null, (dupStaff) ? assignedTo : null, bo, newActCreatedDate, null, ACTIVITY_AUTO, localMessages); else createActivity(a, null, null, (dupStaff) ? assignedTo : null, bo, newActCreatedDate, null, ACTIVITY_AUTO, localMessages); for (CirmMessage lm : localMessages) { lm.addExplanation("triggerActivityAssignments " + assignedTo + " Act: " + serviceActivity.getIRI().getFragment() + " ActType: " + activityType.getIRI().getFragment() + " Tri: " + trigger.getIRI().getFragment() + " Eve: " + event.getIRI().getFragment()); messages.add(lm); } } } } /** * Creates a Referral Case * * @param referringCase * @param owlClass - SR Type to * @param statusToSet */ private void createReferralCase(Long referringCaseId, Json referringCase, OWLClass srType, OWLNamedIndividual statusToSet) { LegacyEmulator emulator = new LegacyEmulator(); Json newCase = Json.object("properties", Json.object()); Json props = newCase.at("properties"); newCase.set("type", "legacy:"+srType.getIRI().getFragment()); GenUtils.timeStamp(props); props.set("legacy:hasStatus", Json.object("iri", statusToSet.getIRI().toString())); props.set("legacy:hasParentCaseNumber", referringCaseId); Json rprops = referringCase.at("properties"); if(rprops.has("atAddress")) props.set("atAddress",rprops.at("atAddress")); if(rprops.has("hasXCoordinate")) props.set("hasXCoordinate",rprops.at("hasXCoordinate")); if(rprops.has("hasYCoordinate")) props.set("hasYCoordinate",rprops.at("hasYCoordinate")); //TODO: not sure if this is needed. //expandIris(data); // remove properties that should be ignore or have been taken care of above already //validateAddresses(data); // we still need to go through actors' address etc. // set properties to the parent. if (rprops.has("hasPriority")) props.set("legacy:hasPriority", rprops.at("hasPriority")); if (rprops.has("hasIntakeMethod")) props.set("legacy:hasIntakeMethod", rprops.at("hasIntakeMethod")); Json result = emulator.saveNewServiceRequest(newCase.toString()); try { Set<OWLNamedIndividual> interfaces = reasoner().getInstances( and(owlClass("legacy:LegacyInterface"), has(objectProperty("legacy:hasAllowableEvent"), individual("legacy:NEWSR")), has(objectProperty("legacy:isLegacyInterface"), individual(newCase.at("type").asString()))), false).getFlattened(); if (!interfaces.isEmpty()) { OWLNamedIndividual LI = interfaces.iterator().next(); result.at("data").set("hasLegacyInterface", Json.object("hasLegacyCode", OWL.dataProperty(LI, "legacy:hasLegacyCode").getLiteral())); } // JMSClient.connectAndSend(LegacyMessageType.NewCase, // ((DBIDFactory) Refs.idFactory.resolve()).generateSequenceNumber(), result); }catch(Exception e) { System.err.println("Referral Case send to queue interface exception"); e.printStackTrace(System.err); if (THROW_ALL_EXC) throw new RuntimeException(e); } } /** * Creates the serviceActivity axioms for the business object * based on Fields and Answers that have been configured * to generate activities. * @param bo */ private void createActivitiesFromQuestions(BOntology bo, List<CirmMessage> messages) { OWLOntology ontology = bo.getOntology(); OWLNamedIndividual businessObject = bo.getBusinessObject(); OWLDataFactory factory = ontology.getOWLOntologyManager().getOWLDataFactory(); Set<OWLIndividual> answers = businessObject.getObjectPropertyValues(factory.getOWLObjectProperty(fullIri("legacy:hasServiceAnswer")), ontology); for(OWLIndividual answer : answers) { Set<OWLIndividual> fields = answer.getObjectPropertyValues(factory.getOWLObjectProperty(fullIri("legacy:hasServiceField")), ontology); if(fields.isEmpty()) continue; OWLNamedIndividual field = individual(fields.iterator().next().asOWLNamedIndividual().getIRI()); List<CirmMessage> localMessages = new ArrayList<CirmMessage>(); triggerActivityAssignmentsOnAnswer(field, answer, bo, localMessages); for (CirmMessage lm : localMessages) { lm.addExplanation("createActivitiesFromQuestions F: " + field.getIRI().getFragment() + "Ans: " + (answer.isNamed()? answer.asOWLNamedIndividual().getIRI().getFragment() : "anonymous")); messages.add(lm); } } } private void triggerActivityAssignmentsOnAnswer( OWLNamedIndividual field, OWLIndividual answer, BOntology bo, List<CirmMessage> messages) { OWLOntology ontology = bo.getOntology(); OWLDataFactory factory = ontology.getOWLOntologyManager().getOWLDataFactory(); Set<OWLNamedIndividual> triggers = reasoner().getObjectPropertyValues(field, objectProperty("legacy:hasActivityAssignment")).getFlattened(); if(triggers.isEmpty()) return; Set<OWLIndividual> answerObjects = answer.getObjectPropertyValues(factory.getOWLObjectProperty(fullIri("legacy:hasAnswerObject")), ontology); Set<OWLLiteral> answerValues = answer.getDataPropertyValues(factory.getOWLDataProperty(fullIri("legacy:hasAnswerValue")), ontology); for(OWLNamedIndividual trigger :triggers) { Set<OWLNamedIndividual> triggerAnswerObjects = reasoner().getObjectPropertyValues(trigger, objectProperty("legacy:hasAnswerObject")).getFlattened(); Set<OWLLiteral> triggerAnswerValues = reasoner().getDataPropertyValues(trigger, dataProperty("legacy:hasAnswerValue")); for(OWLLiteral triggerAnswer : triggerAnswerValues) { if(answerValues.size() > 0) { String answerValue = answerValues.iterator().next().getLiteral(); //When the hasAnswervalue in the ontology doesn't matter, it was given a value of "any" if(triggerAnswer.getLiteral().equalsIgnoreCase("any")) { createActivityFromAnswer(bo, messages, trigger, field, answerValue, null); } //TODO : The below commented "else if" can be modified and used for other requirements //Saw another QuestionTrigger individual "1370732305" with hasAnswerValue // No idea what the requirement was, ie, Do we need to persist the hasAnswerValue // into any Activity field or not persist at all. //else if(triggerAnswer.getLiteral().equals(answerValue)) { //createActivityFromAnswer(bo, messages, trigger, field, answerValue, null); //} } } for(OWLNamedIndividual triggerAnswer: triggerAnswerObjects ) if(answerObjects.contains(triggerAnswer)) { createActivityFromAnswer(bo, messages, trigger, field, null, triggerAnswer); } } } private void createActivityFromAnswer(BOntology bo, List<CirmMessage> messages, OWLNamedIndividual trigger, OWLNamedIndividual field, String details, OWLNamedIndividual triggerAnswer) { Set<OWLNamedIndividual> events = reasoner().getObjectPropertyValues(trigger, objectProperty("legacy:hasLegacyEvent")).getFlattened(); for(OWLNamedIndividual event: events) { Set<OWLNamedIndividual> activities = reasoner().getObjectPropertyValues( event, objectProperty("legacy:hasActivity")) .getFlattened(); if(!activities.isEmpty()) { OWLNamedIndividual a = activities.iterator().next(); Set<OWLNamedIndividual> outcomes = reasoner().getObjectPropertyValues( event, objectProperty("legacy:hasOutcome")) .getFlattened(); List<CirmMessage>localMessages = new ArrayList<CirmMessage>(); if(outcomes.size() > 0) createActivity(a, outcomes.iterator().next(), details, null, bo, null, null, null, localMessages); else createActivity(a, null, details, null, bo, null, null, null, localMessages); for (CirmMessage lm : localMessages) { lm.addExplanation("triggerActivityAssignmentsOnAnswer F:" + field.getIRI().getFragment() + " A:" + triggerAnswer.getIRI().getFragment() + " TriggerAns: " + triggerAnswer.getIRI().getFragment() + " Event: " + event.getIRI().getFragment() + " Activity: " + a.getIRI().getFragment()); messages.add(lm); } } } } /** * Registers a status change of an SR by creating a legacy:StatusChangeActivity. */ public void changeStatus(OWLNamedIndividual newStatus, Date statusChangeDate, String statusChangedBy, BOntology bo, List<CirmMessage> messages) { changeStatus(null, newStatus, statusChangeDate, statusChangedBy, bo, messages); } /** * Registers a status change of an SR by creating a legacy:StatusChangeActivity. * Adds the old status into the details field of the status change activity. * @param oldStatus the status of the SR before the status change. */ public void changeStatus(OWLNamedIndividual oldStatus, OWLNamedIndividual newStatus, Date statusChangeDate, String statusChangedBy, BOntology bo, List<CirmMessage> messages) { OWLNamedIndividual statusChange = individual("legacy:StatusChangeActivity"); String details = null; if (oldStatus != null) { String oldStatusFragment = oldStatus.getIRI().getFragment(); details = oldStatusFragment == null? null : "Old: " + oldStatusFragment; } createActivity(statusChange, newStatus, details, null, bo, statusChangeDate, statusChangeDate, statusChangedBy, messages); } /** * Set's the assign field of an activity based on an email found in the outcome label, * if the serviceActivity is an autoAssign activity and the activityType has an OutcomeEmailAssignmentRule * and an outcome was selected that contains an email address * * @param bo * @param activity */ private String getAssignActivityToOutcomeEmail(OWLNamedIndividual outcome, BOntology bo) { if (outcome == null) return null; //throw exc later if (bo == null) return null; //throw exc later // Check if activityType has a rule that assigns an outcome email to the activity's assigned to field String result = null; //1. Get Outcome and outcome label //2. check if outcome label contains email //3. return email address only String outcomeLabel = "NULL"; if (outcome != null) { outcomeLabel = OWL.getEntityLabel(outcome); if (outcomeLabel != null && outcomeLabel.contains("@")) { result = GenUtils.findEmailIn(outcomeLabel); } } System.out.println("AssignActivityToOutcomeEmail for outcome: " + outcome + " olabel: " + outcomeLabel); return result; } /** * For activity types with templates and an AssignActivityToOutcomeEmail rule, * emails should be sent only after update, not on creation of the activity unless there is a default outcome. * @param activityType * @param bo * @return */ private boolean hasAssignActivityToOutcomeEmail(OWLNamedIndividual activityType) { Set<OWLNamedIndividual> assignRules = OWL.objectProperties(activityType, "legacy:hasAssignmentRule"); for (OWLNamedIndividual rule : assignRules) { if (Model.legacy("AssignActivityToOutcomeEmail").equals(rule.getIRI())) return true; } return false; } /** * Determines the configured suspense days for an activity type. * * @param activityType * @return days or 0 if not configured or parse error. */ private float determineSuspenseDays(OWLNamedIndividual activityType) { float result = 0; Set<OWLLiteral> suspenseDays = reasoner().getDataPropertyValues( activityType, dataProperty("legacy:hasSuspenseDays")); if (suspenseDays.size() > 0) { try { result = suspenseDays.iterator().next().parseFloat(); } catch (Exception e) { result = 0; System.err.println("Error: Could not parse suspense day float value for " + activityType); } } return result; } /** * Determines the configured occur day value for an activity type. * @param activityType * @return days or 0 if not configured or error. */ private float determineOccurDays(OWLNamedIndividual activityType) { float result = 0; Set<OWLLiteral> occurDays = reasoner().getDataPropertyValues( activityType, dataProperty("legacy:hasOccurDays")); if(occurDays.size() > 0) { try { result = occurDays.iterator().next().parseFloat(); } catch (Exception e) { result = 0; System.err.println("ActivityManager: " + activityType + " parseFloat problem - Delayed activity creation failed!"); if (THROW_ALL_EXC) throw new RuntimeException(e); } } return result; } /** * Schedules Activity Creation in OccurDays from now. * @param bo * @param activityType * @param occurBaseDate typically now unless configured and user provided * @param delayDays * @param useWorkWeek * @param details * @param isAssignedTo */ private void scheduleActivityCreationOccurDays(BOntology bo, OWLNamedIndividual activityType, Date occurBaseDate, float occurDays, boolean useWorkWeek, String details, String isAssignedTo) { Date delayedCreationDate = calculateScheduledDelayDate(occurBaseDate, occurDays, useWorkWeek); try { String serverUrl = getServerUrl(); if(serverUrl != null) { String path = "/legacy/bo/"+ bo.getObjectId() + "/activities/create/"+ activityType.getIRI().getFragment(); String fullUrl = serverUrl + path; if (USE_TIME_MACHINE) { //Json post = Json.object(); Json post = null; //if(details != null) // post.set("legacy:hasDetails", details); //if(isAssignedTo != null) // post.set("legacy:isAssignedTo", isAssignedTo); String taskId = getNextTimeMachineTaskIDFor(path); if (DBG) System.out.println("ActManager: TM task " + taskId); Calendar delayedDateCal = Calendar.getInstance(); delayedDateCal.setTime(delayedCreationDate); Json j = GenUtils.timeTask(taskId, delayedDateCal, fullUrl, post); if (j.is("ok", false)) throw new RuntimeException("Time machine post returned false"); } } else { System.err.println("ActivityManager: " + activityType + " Server URL was NULL - Delayed activity creation failed! bo: " + bo.getObjectId()); } }catch(Exception e) { System.out.println("Could not addTimer for activityType " + activityType.getIRI()); if(DBG) e.printStackTrace(System.err); if (THROW_ALL_EXC) throw new RuntimeException(e); } } /** * Schedules creation of an Overdue Activity at a specified due date. * @param bo * @param overdueActivityType * @param due * @param activity activity for which this overdue activity should be created * @param activityType type of activity for which this overdue activity should be created */ private void scheduleOverdueActivityCreationAtDueDate(BOntology bo, OWLNamedIndividual overdueActivityType, Calendar due, OWLNamedIndividual activity, OWLNamedIndividual activityType) { try { String serverUrl = getServerUrl(); if(serverUrl != null) { String path = "/legacy/bo/"+ bo.getObjectId() + "/activity/"+ activity.getIRI().getFragment() + "/overdue/create/" + overdueActivityType.getIRI().getFragment(); String fullUrl = serverUrl + path; if (USE_TIME_MACHINE) { //cannot use task, as serviceActivity will get new id on each retry. oa is type and therefore constant across retries. String almostTaskId = bo.getObjectId() + "act: " + activityType.getIRI().getFragment() + "/overdue/create/" + overdueActivityType.getIRI().getFragment(); String taskId = getNextTimeMachineTaskIDFor(almostTaskId); if (DBG) System.out.println("ActManager: TM task " + taskId); Json j = GenUtils.timeTask(taskId, due, fullUrl, null); if (j.is("ok", false)) throw new RuntimeException("Time machine post returned false"); } } } catch(Exception e) { ThreadLocalStopwatch.error("Could not addTimer for serviceActivity" + activity.getIRI().toString()); if(DBG) e.printStackTrace(System.out); if (THROW_ALL_EXC) throw new RuntimeException(e); } } /** * Calculates the scheduled date by adding daysToAdd to now and optionally using workweek.<br> * <br> * In test mode this will time lapse days to minutes for workflow testing.<br> * <br> * @param now * @param daysToAdd days to add to now (in test mode, minutes will be added) * @param useWorkWeek skip sat/sun and holidays. * @return */ private Date calculateScheduledDelayDate(Date now, float daysToAdd, boolean useWorkWeek) { if (StartUp.isProductionMode()) { return OWL.addDaysToDate(now, daysToAdd, useWorkWeek); } else { return calculateTimeLapseDelayDateForTest(now, daysToAdd); } } /** * Converts daysToAdd to minutes (min 1 to max 10) and calculates a future date for workflow testing purposes.<br> * e.g. 6.5 days would be converted to 6.5 minutes.<br> * e.g. 90 days will be converted to 9 minutes.<br> * e.g. 0.5 days will be converted to 1 minute.<br> * <br> * @param now * @param daysToAdd days to convert to minutes * @return * @throws IllegalStateException if unintentionally called in 311Hub production mode. */ private Date calculateTimeLapseDelayDateForTest(Date now, float daysToAdd) { ThreadLocalStopwatch.now("TEST MODE: IGNORING PROVIDED BASE DATE " + now); now = new Date(); if (StartUp.isProductionMode()) throw new IllegalStateException("Illegal use of test time lapse calculation in production"); //use minutes instead of days float minutesToAddMax10 = daysToAdd; while (minutesToAddMax10 > 10) { minutesToAddMax10 = minutesToAddMax10 / 10.0f; } if (minutesToAddMax10 < 1) { minutesToAddMax10 = 1; } long milliSecondsToAdd = Math.round(minutesToAddMax10 * 60.0 * 1000.0); Date result = new Date(now.getTime() + milliSecondsToAdd); ThreadLocalStopwatch.now("TEST MODE: CALCULATED DATE " + result + " current time is " + now); return result; } /** * Gets the due base date by looking up if legacy:hasUserProvidedDueBaseDate is available in activityType or SR type. * If ServiceAnswer in BO has valid date & it is configured for the activity or SR type, it is returned. * If not configured, or not parseable, now is returned. * * @param bo * @param activityType * @return */ private Date determineDueBaseDate(BOntology bo, OWLNamedIndividual activityType, Date defaultDate) { Date result = defaultDate; //Determine type OWLNamedIndividual serviceRequestType = OWL.individual("legacy:" + bo.getTypeIRI().getFragment()); Set<OWLNamedIndividual> dateServiceQuestions = OWL.objectProperties(serviceRequestType, "legacy:hasUserProvidedDueBaseDate"); if (!dateServiceQuestions.isEmpty()) { OWLNamedIndividual dateServiceQuestion = dateServiceQuestions.iterator().next(); OWLLiteral dataType = OWL.dataProperty(dateServiceQuestion, "legacy:hasDataType"); if ("DATE".equals(dataType.getLiteral())) { Date userDate = findServiceAnswerDateForQuestion(bo, dateServiceQuestion); if (userDate != null) { result = userDate; } } } return result; } /** * Returns the default outcome for the activityType, if legacy:isAutoDefaultOutcome true and * hasDefaultOutcome is available. Null is returned otherwise. * * @param bo * @return */ private OWLNamedIndividual determineAutoDefaultOutcome(OWLNamedIndividual activityType) { OWLNamedIndividual result = null; if (isAutoDefaultOutcomeTrue(activityType)) { Set<OWLNamedIndividual> defaultOutcomeSet = OWL.objectProperties(activityType, "legacy:hasDefaultOutcome"); if (!defaultOutcomeSet.isEmpty()) { result = defaultOutcomeSet.iterator().next(); } } return result; } /** * Determines if legacy:isAutoDefaultOutcome true is configured for an activity type. * * @param activityType * @return */ private boolean isAutoDefaultOutcomeTrue(OWLNamedIndividual activityType) { Set<OWLLiteral> booleanLiterals = OWL.dataProperties(activityType, "legacy:isAutoDefaultOutcome"); if (!booleanLiterals.isEmpty()) { OWLLiteral booleanLiteral = booleanLiterals.iterator().next(); return (booleanLiteral.isBoolean() && booleanLiteral.parseBoolean()); } else { return false; } } private static OWLDataProperty hasAnswerValueDP = OWL.dataProperty(Model.legacy("hasAnswerValue")); /** * Finds answer date to serviceQuestion in Bo, if available. * * @param bo * @param serviceQuestion * @return parsedDate or null if invalid or not available. */ private Date findServiceAnswerDateForQuestion(BOntology bo, OWLNamedIndividual serviceQuestion) { Date result = null; OWLOntology o = bo.getOntology(); Set<OWLAxiom> referencingAxioms = o.getReferencingAxioms(serviceQuestion, false); Iterator<OWLAxiom> it = referencingAxioms.iterator(); while (result == null && it.hasNext()) { //Find axioms where serviceQuestion is Object OWLAxiom cur = it.next(); if (cur instanceof OWLObjectPropertyAssertionAxiom) { OWLObjectPropertyAssertionAxiom opa = (OWLObjectPropertyAssertionAxiom) cur; if (serviceQuestion.equals(opa.getObject())) { OWLIndividual answerCandidate = opa.getSubject(); Set<OWLLiteral> dateCandidates = answerCandidate.getDataPropertyValues(hasAnswerValueDP, o); Iterator<OWLLiteral> itDateCand = dateCandidates.iterator(); while (result == null && itDateCand.hasNext()) { //Find parseable date OWLLiteral dateCandidate = itDateCand.next(); if (dateCandidate.getLiteral() != null && !dateCandidate.getLiteral().isEmpty()) try { result = GenUtils.parseDate(dateCandidate); } catch (Exception e) { result = null; } } //inner while } } } //outer while return result; } }