package com.constellio.app.modules.robots.services; import static com.constellio.app.modules.robots.model.DryRunRobotAction.dryRunRobotAction; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.allConditions; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.anyConditions; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.impossibleCondition; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.not; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org.joda.time.Duration; import com.constellio.app.modules.robots.model.ActionExecutor; import com.constellio.app.modules.robots.model.DryRunRobotAction; import com.constellio.app.modules.robots.model.RegisteredAction; import com.constellio.app.modules.robots.model.services.RobotsService; import com.constellio.app.modules.robots.model.wrappers.Robot; import com.constellio.app.ui.pages.search.criteria.ConditionBuilder; import com.constellio.app.ui.pages.search.criteria.ConditionException; import com.constellio.data.dao.managers.StatefulService; import com.constellio.data.threads.BackgroundThreadConfiguration; import com.constellio.data.threads.BackgroundThreadExceptionHandling; import com.constellio.data.threads.BackgroundThreadsManager; import com.constellio.model.entities.batchprocess.BatchProcess; import com.constellio.model.entities.records.Record; import com.constellio.model.entities.schemas.MetadataSchemaType; import com.constellio.model.entities.schemas.MetadataSchemaTypes; import com.constellio.model.services.batch.manager.BatchProcessesManager; import com.constellio.model.services.factories.ModelLayerFactory; import com.constellio.model.services.records.RecordProvider; import com.constellio.model.services.records.RecordServices; import com.constellio.model.services.schemas.MetadataSchemasManager; import com.constellio.model.services.search.SearchServices; import com.constellio.model.services.search.query.logical.LogicalSearchQuery; import com.constellio.model.services.search.query.logical.condition.LogicalSearchCondition; public class RobotsManager implements StatefulService { public static final String ID = "robotsManager"; private Map<String, RegisteredAction> actions = new HashMap<>(); private RobotSchemaRecordServices robotSchemas; private SearchServices searchServices; private BatchProcessesManager batchProcessesManager; private String collection; private RobotsService robotsService; public RobotsManager(RobotSchemaRecordServices robotSchemas) { this.collection = robotSchemas.getCollection(); this.robotSchemas = robotSchemas; this.searchServices = robotSchemas.getModelLayerFactory().newSearchServices(); this.batchProcessesManager = robotSchemas.getModelLayerFactory().getBatchProcessesManager(); this.robotsService = new RobotsService(robotSchemas.getCollection(), robotSchemas.getAppLayerFactory()); startAutoExecutorThread(); } private void startAutoExecutorThread() { BackgroundThreadsManager manager = robotSchemas.getModelLayerFactory().getDataLayerFactory() .getBackgroundThreadsManager(); manager.configure(BackgroundThreadConfiguration.repeatingAction("startAutoExecutingRobots", new Runnable() { @Override public void run() { startAutoExecutingRobots(); } }).handlingExceptionWith(BackgroundThreadExceptionHandling.CONTINUE) .executedEvery(Duration.standardHours(24))); } public RegisteredAction registerAction(String code, String parametersSchemaLocalCode, Collection<String> types, ActionExecutor executor) { RegisteredAction registeredAction = new RegisteredAction(code, parametersSchemaLocalCode, executor, types); actions.put(code, registeredAction); return registeredAction; } public List<RegisteredAction> getRegisteredActionsFor(String schemaTypeCode) { List<RegisteredAction> returnedActions = new ArrayList<>(); for (RegisteredAction action : actions.values()) { if (action.getSupportedSchemaTypes() == null || action.getSupportedSchemaTypes().contains(schemaTypeCode)) { returnedActions.add(action); } } return returnedActions; } public List<String> getSupportedSchemaTypes() { Set<String> types = new HashSet<>(); for (RegisteredAction action : actions.values()) { if (action.getSupportedSchemaTypes() != null) { types.addAll(action.getSupportedSchemaTypes()); } } return new ArrayList<>(types); } public void startAllRobotsExecution() { for (Robot robot : robotsService.getRootRobots()) { Stack<LogicalSearchCondition> conditions = new Stack<>(); startRobotExecution(robot, conditions, null); } } public void startAutoExecutingRobots() { for (Robot robot : robotsService.getAutoExecutingRootRobots()) { Stack<LogicalSearchCondition> conditions = new Stack<>(); startRobotExecution(robot, conditions, null); } } public void startRobotExecution(String id) { startRobotExecution(robotSchemas.getRobot(id)); } private void startRobotExecution(Robot robot) { Stack<LogicalSearchCondition> conditions = new Stack<>(); startRobotExecution(robot, conditions, null); } private RobotCondition startRobotExecution(Robot robot, Stack<LogicalSearchCondition> conditions, List<DryRunRobotAction> dryRunRobotActions) { LogicalSearchCondition localCondition = getResolveCondition(robot); conditions.push(localCondition); RobotCondition condition = newRobotCondition(conditions, robot); if (searchServices.hasResults(query(condition.conditionIncludingParentCondition))) { for (Robot childRobot : robotsService.getChildRobots(robot.getId())) { RobotCondition childsCondition = startRobotExecution(childRobot, conditions, dryRunRobotActions); condition.childRobotConditions.add(childsCondition); } } conditions.pop(); if (robot.getAction() != null) { LogicalSearchQuery query = query(condition.buildCondition()); if (dryRunRobotActions != null) { ModelLayerFactory modelLayerFactory = robotSchemas.getModelLayerFactory(); MetadataSchemasManager msm = modelLayerFactory.getMetadataSchemasManager(); MetadataSchemaTypes schemaTypes = msm.getSchemaTypes(robot.getCollection()); RecordServices recordServices = modelLayerFactory.newRecordServices(); RobotBatchProcessAction batchProcessAction = new RobotBatchProcessAction(robot.getId(), robot.getAction(), robot.getActionParameters()); batchProcessAction.setDryRun(true); batchProcessAction.execute(searchServices.search(query), schemaTypes, new RecordProvider(recordServices)); Iterator<Record> recordsIterator = batchProcessAction.getProcessedRecords().iterator(); while (recordsIterator.hasNext()) { Record record = recordsIterator.next(); dryRunRobotActions.add(dryRunRobotAction(record, robot, robotSchemas)); } } else { createBatchProcess(robot.getId(), query, robot.getAction(), robot.getActionParameters()); } } return condition; } private void createBatchProcess(String robotId, LogicalSearchQuery query, String action, String actionParametersId) { if (searchServices.hasResults(query)) { RobotBatchProcessAction batchProcessAction = new RobotBatchProcessAction(robotId, action, actionParametersId); BatchProcess batchProcess = batchProcessesManager .addBatchProcessInStandby(query, batchProcessAction, "robot " + robotId); batchProcessesManager.markAsPending(batchProcess); } } public LogicalSearchCondition getResolveCondition(Robot robot) { MetadataSchemaType type = robotSchemas.schemaType(robot.getSchemaFilter()); try { List<LogicalSearchCondition> conditions = buildConditionsForRobot(type, robot); return allConditions(conditions); } catch (ConditionException e) { e.printStackTrace(); return impossibleCondition(robot.getCollection()); } } private List<LogicalSearchCondition> buildConditionsForRobot(MetadataSchemaType type, Robot robot) throws ConditionException { List<LogicalSearchCondition> conditions = new ArrayList<>(); String languageCode = searchServices.getLanguageCode(robot.getCollection()); LogicalSearchCondition condition = new ConditionBuilder(type, languageCode).build(robot.getSearchCriteria()); conditions.add(condition); String parentRobotId = robot.getParent(); if (parentRobotId != null) { Robot parentRobot = robotSchemas.getRobot(parentRobotId); conditions.addAll(buildConditionsForRobot(type, parentRobot)); } return conditions; } @Override public void initialize() { } @Override public void close() { } private LogicalSearchQuery query(LogicalSearchCondition condition) { LogicalSearchQuery query = new LogicalSearchQuery(condition); query.setPreferAnalyzedFields(true); return query; } public RegisteredAction getActionFor(String actionCode) { return actions.get(actionCode); } public ActionExecutor getActionExecutorFor(String actionCode) { RegisteredAction action = getActionFor(actionCode); return action == null ? null : action.getExecutor(); } public boolean canExecute(String id) { return canExecute(robotSchemas.getRobot(id)); } private boolean canExecute(Robot robot) { return robot.isRoot(); } private RobotCondition newRobotCondition(Stack<LogicalSearchCondition> conditions, Robot robot) { LogicalSearchCondition condition = allConditions(new ArrayList<>(conditions)); return new RobotCondition(conditions.peek(), condition, robot); } public List<DryRunRobotAction> dryRun(Robot robot) { List<DryRunRobotAction> actions = new ArrayList<>(); Stack<LogicalSearchCondition> conditions = new Stack<>(); startRobotExecution(robot, conditions, actions); return actions; } private static class RobotCondition { LogicalSearchCondition localCondition; LogicalSearchCondition conditionIncludingParentCondition; Robot robot; List<RobotCondition> childRobotConditions = new ArrayList<>(); public RobotCondition(LogicalSearchCondition localCondition, LogicalSearchCondition conditionIncludingParentCondition, Robot robot) { this.localCondition = localCondition; this.conditionIncludingParentCondition = conditionIncludingParentCondition; this.robot = robot; } private LogicalSearchCondition newConditionIncludingParentsAndExcludingChildren() { List<LogicalSearchCondition> conditions = new ArrayList<>(); for (RobotCondition robotCondition : childRobotConditions) { conditions.add(robotCondition.localCondition); } if (conditions.isEmpty()) { return conditionIncludingParentCondition; } else { return allConditions( conditionIncludingParentCondition, not(anyConditions(conditions)) ); } } public LogicalSearchCondition buildCondition() { if (robot.getExcludeProcessedByChildren()) { return newConditionIncludingParentsAndExcludingChildren(); } else { return conditionIncludingParentCondition; } } } }