package io.cattle.platform.process.common.handler; import io.cattle.platform.agent.AgentLocator; import io.cattle.platform.agent.RemoteAgent; import io.cattle.platform.archaius.util.ArchaiusUtil; import io.cattle.platform.async.utils.TimeoutException; import io.cattle.platform.core.constants.InstanceConstants; import io.cattle.platform.engine.handler.HandlerResult; import io.cattle.platform.engine.process.ProcessInstance; import io.cattle.platform.engine.process.ProcessState; import io.cattle.platform.eventing.EventCallOptions; import io.cattle.platform.eventing.EventProgress; import io.cattle.platform.eventing.exception.AgentRemovedException; import io.cattle.platform.eventing.exception.EventExecutionException; import io.cattle.platform.eventing.model.Event; import io.cattle.platform.eventing.model.EventVO; import io.cattle.platform.object.meta.Relationship; import io.cattle.platform.object.serialization.ObjectSerializer; import io.cattle.platform.object.serialization.ObjectSerializerFactory; import io.cattle.platform.object.util.ObjectUtils; import io.cattle.platform.process.common.util.ProcessUtils; import io.cattle.platform.process.progress.ProcessProgress; import io.cattle.platform.process.progress.ProcessProgressInstance; import io.cattle.platform.util.exception.ExecutionException; import io.cattle.platform.util.type.CollectionUtils; import io.cattle.platform.util.type.InitializationTask; import io.cattle.platform.util.type.Priority; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; import com.netflix.config.DynamicStringProperty; public class AgentBasedProcessLogic extends AbstractObjectProcessLogic implements InitializationTask, Priority { private static final String DEFAULT_NAME = "AgentBased"; protected AgentLocator agentLocator; ObjectSerializerFactory factory; ObjectSerializer serializer; ProcessProgress progress; boolean reportProgress = false; boolean shouldContinue; boolean sendNoOp; String errorChainProcess; String configPrefix = "event.data."; String dataType; Class<?> dataTypeClass; String commandName; String[] processNames; String agentResourceRelationship; String dataResourceRelationship; String eventResourceRelationship; Integer eventRetry; boolean shortCircuitIfAgentRemoved; boolean timeoutIsError; List<String> processDataKeys = new ArrayList<>(); String expression; int priority = Priority.SPECIFIC; Map<String, ObjectSerializer> serializers = new ConcurrentHashMap<String, ObjectSerializer>(); public AgentBasedProcessLogic() { if (this.getClass() == AgentBasedProcessLogic.class) { setName(DEFAULT_NAME); } } @Override public String[] getProcessNames() { if (DEFAULT_NAME.equals(getName())) { return new String[0]; } if (processNames == null) { return new String[] { ProcessUtils.getDefaultProcessName(this) }; } return processNames; } @Override public HandlerResult handle(ProcessState state, ProcessInstance process) { Object eventResource = getEventResource(state, process); Object dataResource = getDataResource(state, process); Object agentResource = getAgentResource(state, process, dataResource); if (eventResource == null) { return null; } if (dataResource == null) { return null; } RemoteAgent agent = agentLocator.lookupAgent(agentResource); if (agent == null) { return new HandlerResult(true, CollectionUtils.asMap((Object) "_noAgent", true)); } try { Event reply = handleEvent(state, process, eventResource, dataResource, agentResource, agent); if (reply == null) { return null; } return new HandlerResult(shouldContinue, getResourceDataMap(getObjectManager().getType(eventResource), reply.getData())); } catch (EventExecutionException e) { if (StringUtils.isNotBlank(errorChainProcess)) { getObjectProcessManager().scheduleProcessInstance(errorChainProcess, state.getResource(), null); e.setResources(state.getResource()); } throw e; } } protected Event handleEvent(ProcessState state, ProcessInstance process, Object eventResource, Object dataResource, Object agentResource, RemoteAgent agent) { ObjectSerializer serializer = getObjectSerializer(dataResource); Map<String, Object> data = serializer == null ? null : serializer.serialize(dataResource); boolean shortCircuit = false; Map<String, Object> processData = new HashMap<String, Object>(); for (String key : processDataKeys) { Object value = state.getData().get(key); if (value != null) { shortCircuit = InstanceConstants.PROCESS_DATA_NO_OP.equals(key) && Boolean.TRUE.equals(value); processData.put(key, value); } } if (sendNoOp) { shortCircuit = false; } if (processData.size() > 0) { data.put("processData", processData); } EventVO<Object> event = EventVO.newEvent(getCommandName() == null ? process.getName() : getCommandName()) .withData(data) .withResourceType(getObjectManager().getType(eventResource)) .withResourceId(ObjectUtils.getId(eventResource).toString()); preProcessEvent(event, state, process, eventResource, dataResource, agentResource); EventCallOptions options = new EventCallOptions(); options.setRetry(eventRetry); final ProcessProgressInstance progressInstance = progress.get(); if (reportProgress && progressInstance != null) { options.withProgressIsKeepAlive(true).withProgress(new EventProgress() { @Override public void progress(Event event) { String message = event.getTransitioningMessage(); if (message != null) { progressInstance.messsage(message); } Integer eventProgress = event.getTransitioningProgress(); if (eventProgress != null) { progressInstance.progress(eventProgress); } } }); } Event reply; try { if (shortCircuit) { reply = EventVO.reply(event); } else { reply = callSync(agent, event, options); } } catch (AgentRemovedException e) { if (shortCircuitIfAgentRemoved) { return null; } else { throw e; } } catch (TimeoutException e) { if (timeoutIsError) { throw new ExecutionException(e); } else { throw e; } } postProcessEvent(event, reply, state, process, eventResource, dataResource, agentResource); return reply; } protected Event callSync(RemoteAgent agent, Event event, EventCallOptions options) { return agent.callSync(event, options); } protected Map<Object, Object> getResourceDataMap(String type, Object data) { Object result = CollectionUtils.toMap(data).get(type); return result == null ? null : CollectionUtils.toMap(result); } protected void postProcessEvent(EventVO<?> event, Event reply, ProcessState state, ProcessInstance process, Object eventResource, Object dataResource, Object agentResource) { } protected void preProcessEvent(EventVO<?> event, ProcessState state, ProcessInstance process, Object eventResource, Object dataResource, Object agentResource) { } protected Object getAgentResource(ProcessState state, ProcessInstance process, Object dataResource) { return getObjectByRelationship(agentResourceRelationship, state.getResource()); } protected Object getEventResource(ProcessState state, ProcessInstance process) { return getObjectByRelationship(eventResourceRelationship, state.getResource()); } protected Object getDataResource(ProcessState state, ProcessInstance process) { return getObjectByRelationship(dataResourceRelationship, state.getResource()); } protected Object getObjectByRelationship(String relationship, Object obj) { if (relationship == null || obj == null) { return obj; } String type = getObjectManager().getType(obj); if (type == null) { throw new IllegalArgumentException("Failed to find type for [" + obj + "]"); } Relationship rel = getObjectMetaDataManager().getRelationship(type, relationship); if (rel == null) { throw new IllegalStateException("Failed to find relationship [" + relationship + "] on obj [" + obj + "]"); } if (rel.isListResult()) { throw new IllegalStateException("Relationship [" + relationship + "] on obj [" + obj + "] is a list result"); } return getObjectManager().getObjectByRelationship(obj, rel); } protected String getCommandName(ProcessState state, ProcessInstance process, Object eventResource, Object dataResource, Object agentResource) { if (commandName != null) { return commandName; } else { return process.getName(); } } protected ObjectSerializer getObjectSerializer(Object obj) { if (serializer != null) { return serializer; } String type = getObjectManager().getType(obj); if (type == null) { throw new IllegalStateException("Failed to find type for [" + obj + "]"); } ObjectSerializer serializer = serializers.get(type); if (serializer != null) { return serializer; } return buildSerializer(type); } protected synchronized ObjectSerializer buildSerializer(String type) { ObjectSerializer serializer = serializers.get(type); if (serializer != null) { return serializer; } String expression = getExpression(type); if (expression == null) { return null; } serializer = factory.compile(type, expression); serializers.put(type, serializer); return serializer; } public String getExpression() { if (expression != null) { return expression; } if (serializer != null) { return serializer.getExpression(); } return null; } protected String getExpression(String type) { if (expression != null) { return expression; } DynamicStringProperty prop = getExpressionProperty(type); prop.addCallback(new Runnable() { @Override public void run() { for (String type : serializers.keySet()) { buildSerializer(type); } } }); return prop.get(); } protected DynamicStringProperty getExpressionProperty(String type) { return ArchaiusUtil.getString(getConfigPrefix() + type); } public AgentLocator getAgentLocator() { return agentLocator; } @Inject public void setAgentLocator(AgentLocator agentLocator) { this.agentLocator = agentLocator; } @Override public void start() { if (dataType == null && dataTypeClass != null) { dataType = getObjectManager().getType(dataTypeClass); if (dataType == null) { throw new IllegalStateException("Failed to find type for class [" + dataTypeClass + "]"); } } if (dataType != null) { loadDefaultSerializer(); getExpressionProperty(dataType).addCallback(new Runnable() { @Override public void run() { loadDefaultSerializer(); } }); } } protected void loadDefaultSerializer() { serializer = buildSerializer(dataType); } public String getConfigPrefix() { return configPrefix; } public void setConfigPrefix(String configPrefix) { this.configPrefix = configPrefix; } public ObjectSerializerFactory getFactory() { return factory; } @Inject public void setFactory(ObjectSerializerFactory factory) { this.factory = factory; } public String getCommandName() { return commandName; } public void setCommandName(String commandName) { this.commandName = commandName; } public void setProcessNames(String[] processNames) { this.processNames = processNames; } public String getAgentResourceRelationship() { return agentResourceRelationship; } public void setAgentResourceRelationship(String agentResourceRelationship) { this.agentResourceRelationship = agentResourceRelationship; } public String getDataResourceRelationship() { return dataResourceRelationship; } public void setDataResourceRelationship(String dataResourceRelationship) { this.dataResourceRelationship = dataResourceRelationship; } public String getEventResourceRelationship() { return eventResourceRelationship; } public void setEventResourceRelationship(String eventResourceRelationship) { this.eventResourceRelationship = eventResourceRelationship; } public String getDataType() { return dataType; } public void setDataType(String dataType) { this.dataType = dataType; } public Class<?> getDataTypeClass() { return dataTypeClass; } public void setDataTypeClass(Class<?> dataTypeClass) { this.dataTypeClass = dataTypeClass; } public void setExpression(String expression) { this.expression = expression; } @Override public int getPriority() { return priority; } public void setPriority(int priority) { this.priority = priority; } public boolean isShouldContinue() { return shouldContinue; } public void setShouldContinue(boolean shouldContinue) { this.shouldContinue = shouldContinue; } public ProcessProgress getProgress() { return progress; } @Inject public void setProgress(ProcessProgress progress) { this.progress = progress; } public boolean isReportProgress() { return reportProgress; } public void setReportProgress(boolean reportProgress) { this.reportProgress = reportProgress; } public List<String> getProcessDataKeys() { return processDataKeys; } public void setProcessDataKeys(List<String> processDataKeys) { this.processDataKeys = processDataKeys; } public boolean isShortCircuitIfAgentRemoved() { return shortCircuitIfAgentRemoved; } public void setShortCircuitIfAgentRemoved(boolean shortCircuitIfAgentRemoved) { this.shortCircuitIfAgentRemoved = shortCircuitIfAgentRemoved; } public String getErrorChainProcess() { return errorChainProcess; } public void setErrorChainProcess(String errorChainProcess) { this.errorChainProcess = errorChainProcess; } public boolean isTimeoutIsError() { return timeoutIsError; } public void setTimeoutIsError(boolean timeoutIsError) { this.timeoutIsError = timeoutIsError; } public Integer getEventRetry() { return eventRetry; } public void setEventRetry(Integer eventRetry) { this.eventRetry = eventRetry; } public boolean isSendNoOp() { return sendNoOp; } public void setSendNoOp(boolean sendNoOp) { this.sendNoOp = sendNoOp; } }