All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.viadee.camunda.kafka.pollingclient.service.polling.rest.CamundaRestPollingServiceImpl Maven / Gradle / Ivy

package de.viadee.camunda.kafka.pollingclient.service.polling.rest;

import com.fasterxml.jackson.databind.ObjectMapper;
import de.viadee.camunda.kafka.event.*;
import de.viadee.camunda.kafka.pollingclient.config.properties.CamundaRestPollingProperties;
import de.viadee.camunda.kafka.pollingclient.service.polling.PollingService;
import de.viadee.camunda.kafka.pollingclient.service.polling.rest.response.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 

* CamundaRestPollingServiceImpl class. *

* * @author viadee * @version $Id: $Id */ public class CamundaRestPollingServiceImpl implements PollingService { private static final Logger LOGGER = LoggerFactory.getLogger(CamundaRestPollingServiceImpl.class); private static final String STARTED_AFTER = "startedAfter"; private static final String STARTED_BEFORE = "startedBefore"; private static final String FINISHED_AFTER = "finishedAfter"; private static final String FINISHED_BEFORE = "finishedBefore"; private static final String PROCESS_INSTANCE_ID = "processInstanceId"; private static final String ACTIVITY_INSTANCE_ID = "activityInstanceId"; private static final String PROCESS_DEFINITION_ID = "processDefinitionId"; private static final String DEPLOYMENT_ID = "deploymentId"; private final ObjectMapper objectMapper; private final CamundaRestPollingProperties camundaProperties; private final RestTemplate restTemplate; /** *

* Constructor for CamundaRestPollingServiceImpl. *

* * @param camundaProperties * a {@link de.viadee.camunda.kafka.pollingclient.config.properties.CamundaRestPollingProperties} object. * @param restTemplate * a {@link org.springframework.web.client.RestTemplate} object. */ public CamundaRestPollingServiceImpl(CamundaRestPollingProperties camundaProperties, RestTemplate restTemplate) { this.camundaProperties = camundaProperties; this.restTemplate = restTemplate; String dateFormatPattern = camundaProperties.getDateFormatPattern(); this.objectMapper = new ObjectMapper(); this.objectMapper.setDateFormat(new SimpleDateFormat(dateFormatPattern)); } /** {@inheritDoc} */ @Override public Iterable pollFinishedProcessInstances(Date startedAfter, Date startedBefore, Date finishedAfter) { final String url = camundaProperties.getUrl() + "history/process-instance?finished=true&startedBefore={startedBefore}&startedAfter={startedAfter}&finishedAfter={finishedAfter}"; try { final Map variables = new HashMap<>(); variables.put(STARTED_AFTER, formatDate(startedAfter)); variables.put(STARTED_BEFORE, formatDate(startedBefore)); variables.put(FINISHED_AFTER, formatDate(finishedAfter)); LOGGER.debug("Polling finished process instances from {} ({})", url, variables); List result = this.restTemplate .exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference>() { }, variables) .getBody(); if (result == null) { return new ArrayList<>(); } LOGGER.debug("Found {} finished process instances from {} ({})", result.size(), url, variables); return result .stream() .filter(event -> event.getStartTime().compareTo(startedBefore) < 0) // startedBefore ist // selected as <= by // Camunda - thus add // filter .map(this::createProcessInstanceEvent)::iterator; } catch (RestClientException e) { throw new RuntimeException("Error requesting Camunda REST API (" + url + ") for process instances", e); } } /** {@inheritDoc} */ @Override public Iterable pollUnfinishedProcessInstances(Date startedAfter, Date startedBefore) { final String url = camundaProperties.getUrl() + "history/process-instance?unfinished=true&startedBefore={startedBefore}&startedAfter={startedAfter}"; try { final Map variables = new HashMap<>(); variables.put(STARTED_BEFORE, formatDate(startedBefore)); variables.put(STARTED_AFTER, formatDate(startedAfter)); LOGGER.debug("Polling unfinished process instances from {} ({})", url, variables); List result = this.restTemplate .exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference>() { }, variables) .getBody(); if (result == null) { return new ArrayList<>(); } LOGGER.debug("Found {} unfinished process instances from {}", result.size(), url); return result .stream() .filter(event -> event.getStartTime().compareTo(startedBefore) < 0) // startedBefore ist // selected as <= by // Camunda - thus add // filter .map(this::createProcessInstanceEvent)::iterator; } catch (RestClientException e) { throw new RuntimeException("Error requesting Camunda REST API (" + url + ") for process instances", e); } } /** {@inheritDoc} */ @Override public Iterable pollFinishedActivities(String processInstanceId, Date finishedAfter, Date finishedBefore) { final String url = camundaProperties.getUrl() + "history/activity-instance?finished=true&processInstanceId={processInstanceId}&finishedBefore={finishedBefore}&finishedAfter={finishedAfter}"; try { final Map variables = new HashMap<>(); variables.put(FINISHED_BEFORE, formatDate(finishedBefore)); variables.put(FINISHED_AFTER, formatDate(finishedAfter)); variables.put(PROCESS_INSTANCE_ID, processInstanceId); LOGGER.debug("Polling finished activity instances from {} ({})", url, variables); List result = this.restTemplate .exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference>() { }, variables) .getBody(); if (result == null) { return new ArrayList<>(); } LOGGER.debug("Found {} finished activity instances from {} ({})", result.size(), url, variables); return result .stream() .filter(event -> event.getEndTime().compareTo(finishedBefore) < 0) // finishedBefore ist // selected as <= by Camunda // - thus add filter .map(this::createActivityInstanceEvent)::iterator; } catch (RestClientException e) { throw new RuntimeException("Error requesting Camunda REST API (" + url + ") for activity instances", e); } } /** {@inheritDoc} */ @Override public Iterable pollUnfinishedActivities(String processInstanceId, Date startedAfter, Date startedBefore) { final String url = camundaProperties.getUrl() + "history/activity-instance?unfinished=true&processInstanceId={processInstanceId}&startedBefore={startedBefore}&startedAfter={startedAfter}"; try { final Map variables = new HashMap<>(); variables.put(STARTED_BEFORE, formatDate(startedBefore)); variables.put(STARTED_AFTER, formatDate(startedAfter)); variables.put(PROCESS_INSTANCE_ID, processInstanceId); LOGGER.debug("Polling unfinished activity instances from {} ({})", url, variables); List result = this.restTemplate .exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference>() { }, variables) .getBody(); if (result == null) { return new ArrayList<>(); } LOGGER.debug("Found {} unfinished activity instances from {} ({})", result.size(), url, variables); return result .stream() .filter(event -> event.getStartTime().compareTo(startedBefore) < 0) // startedBefore ist // selected as <= by // Camunda - thus add // filter .map(this::createActivityInstanceEvent)::iterator; } catch (RestClientException e) { throw new RuntimeException("Error requesting Camunda REST API (" + url + ") for activity instances", e); } } /** {@inheritDoc} */ @Override public Iterable pollCurrentVariables(String activityInstanceId) { final String url = camundaProperties.getUrl() + "history/variable-instance?deserializeValues=false&activityInstanceIdIn={activityInstanceId}"; try { final Map variables = new HashMap<>(); variables.put(ACTIVITY_INSTANCE_ID, activityInstanceId); LOGGER.debug("Polling variables from {} ({})", url, variables); final Date pollingTimestamp = new Date(); List result = this.restTemplate .exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference>() { }, variables) .getBody(); if (result == null) { return new ArrayList<>(); } LOGGER.debug("Found {} variables from {} ({})", result.size(), url, variables); return result .stream() .map(response -> createVariableUpdateEventFromInstance(response, pollingTimestamp))::iterator; } catch (RestClientException e) { throw new RuntimeException("Error requesting Camunda REST API (" + url + ") for variables", e); } } /** {@inheritDoc} */ @Override public Iterable pollVariableDetails(String activityInstanceId) { final String url = camundaProperties.getUrl() + "history/detail?deserializeValues=false&type=variableUpdate&activityInstanceId={activityInstanceId}"; try { final Map variables = new HashMap<>(); variables.put(ACTIVITY_INSTANCE_ID, activityInstanceId); LOGGER.debug("Polling variables from {} ({})", url, variables); List result = this.restTemplate .exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference>() { }, variables) .getBody(); if (result == null) { return new ArrayList<>(); } LOGGER.debug("Found {} variables from {} ({})", result.size(), url, variables); return result .stream() .map(this::createVariableUpdateEventFromDetails)::iterator; } catch (RestClientException e) { throw new RuntimeException("Error requesting Camunda REST API (" + url + ") for variables details", e); } } /** {@inheritDoc} */ @Override public Iterable pollProcessDefinitions(final Date startTime, final Date endTime) { List deploymentList = getDeployments(startTime, endTime); List processDefinitionList = new ArrayList<>(); for (GetDeploymentResponse deployment : deploymentList) { processDefinitionList.addAll(getProcessDefinitions(deployment)); } for (ProcessDefinitionEvent processDefinitionEvent : processDefinitionList) { GetProcessDefinitionXmlResponse processDefinitionXML = getProcessDefinitionXML( processDefinitionEvent.getId()); if (processDefinitionXML != null) { processDefinitionEvent.setXml(processDefinitionXML.getBpmn20Xml()); } } return processDefinitionList .stream()::iterator; } /** * {@inheritDoc} * * @param activityInstanceEvent */ @Override public Iterable pollComments(final ActivityInstanceEvent activityInstanceEvent) { final String url = camundaProperties.getUrl() + "task/" + activityInstanceEvent.getTaskId() + "/comment"; try { final Map variables = new HashMap<>(); LOGGER.debug("Polling comments for taskId: {}", activityInstanceEvent.getTaskId()); List result = this.restTemplate .exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference>() { }, variables) .getBody(); if (result == null) { return new ArrayList<>(); } LOGGER.debug("Found {} comments for taskId: {} ", result.size(), activityInstanceEvent.getTaskId()); return result .stream() .map(getCommentResponse -> createCommentEventFromDetails(getCommentResponse, activityInstanceEvent))::iterator; } catch (RestClientException e) { throw new RuntimeException("Error requesting Camunda REST API (" + url + ") for comments", e); } } private GetProcessDefinitionXmlResponse getProcessDefinitionXML(String processDefinitionId) { final String url = camundaProperties.getUrl() + "process-definition/{processDefinitionId}/xml"; GetProcessDefinitionXmlResponse resp; try { final Map variables = new HashMap<>(); variables.put(PROCESS_DEFINITION_ID, processDefinitionId); LOGGER.debug("Polling process definition xml from {} ({})", url, variables); resp = this.restTemplate .exchange(url, HttpMethod.GET, null, GetProcessDefinitionXmlResponse.class, variables) .getBody(); if (resp != null) { LOGGER.debug("Found process definition xml from {} ({})", url, variables); } else { LOGGER.debug("No process definition xml found from {} ({})", url, variables); } } catch (RestClientException e) { throw new RuntimeException( "Error requesting Camunda REST API (" + url + ") for process definition xml", e); } return resp; } private List getProcessDefinitions(GetDeploymentResponse deploymentResponse) { final String url = camundaProperties.getUrl() + "process-definition?deploymentId={deploymentId}"; List processDefinitions = new ArrayList<>(); try { final Map variables = new HashMap<>(); variables.put("deploymentId", deploymentResponse.getId()); LOGGER.debug("Polling process definitions from {} ({})", url, variables); processDefinitions = this.restTemplate .exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference>() { }, variables) .getBody(); if (processDefinitions == null) { processDefinitions = new ArrayList<>(); } LOGGER.debug("Found {} process definitions from {} ({})", processDefinitions.size(), url, variables); } catch (RestClientException e) { throw new RuntimeException( "Error requesting Camunda REST API (" + url + ") for process definitions", e); } return processDefinitions .stream() .map(response -> createProcessDefinitionEvent(response, deploymentResponse)) .collect(Collectors.toList()); } private List getDeployments(Date deploymentAfter, Date deploymentBefore) { // There seems to be a slight bug in Camunda SQL queries regarding deployments. // Where the other history queries regarding time boundaries are inclusive (startedBefore, startedAfter, ...), // deploymentBefore and deploymentAfter are implemented exclusive. // Thus we have to slightly adjust the deploymentAfter parameter by 1 millisecond to act inclusive: deploymentAfter = new Date(deploymentAfter.getTime() - 1); final String deploymentUrl = camundaProperties.getUrl() + "deployment?before={before}&after={after}"; List deployments = new ArrayList<>(); try { final Map variables = new HashMap<>(); variables.put("before", formatDate(deploymentBefore)); variables.put("after", formatDate(deploymentAfter)); LOGGER.debug("Polling deployments from {} ({})", deploymentUrl, variables); deployments = this.restTemplate .exchange(deploymentUrl, HttpMethod.GET, null, new ParameterizedTypeReference>() { }, variables) .getBody(); if (deployments == null) { deployments = new ArrayList<>(); } LOGGER.debug("Found {} deployments from {} ({})", deployments.size(), deploymentUrl, variables); } catch (RestClientException e) { throw new RuntimeException( "Error requesting Camunda REST API (" + deploymentUrl + ") for deployments", e); } return deployments; } private ProcessInstanceEvent createProcessInstanceEvent( GetHistoricProcessInstanceResponse getHistoricProcessInstanceResponse) { final ProcessInstanceEvent event = new ProcessInstanceEvent(); BeanUtils.copyProperties(getHistoricProcessInstanceResponse, event); event.setProcessInstanceId(getHistoricProcessInstanceResponse.getId()); event.setEventType(getHistoricProcessInstanceResponse.getState()); return event; } private ActivityInstanceEvent createActivityInstanceEvent( GetHistoricActivityInstanceRespone getHistoricActivityInstanceRespone) { final ActivityInstanceEvent event = new ActivityInstanceEvent(); BeanUtils.copyProperties(getHistoricActivityInstanceRespone, event); event.setActivityInstanceId(getHistoricActivityInstanceRespone.getId()); return event; } private VariableUpdateEvent createVariableUpdateEventFromInstance( GetHistoricVariableInstancesResponse getHistoricVariableInstancesResponse, Date pollingTimestamp) { final VariableUpdateEvent event = new VariableUpdateEvent(); BeanUtils.copyProperties(getHistoricVariableInstancesResponse, event); event.setVariableName(getHistoricVariableInstancesResponse.getName()); event.setVariableInstanceId(getHistoricVariableInstancesResponse.getId()); event.setEventType(getHistoricVariableInstancesResponse.getState()); event.setTimestamp(pollingTimestamp); setVariableValue(event, getHistoricVariableInstancesResponse.getValue(), getHistoricVariableInstancesResponse.getType(), getHistoricVariableInstancesResponse.getValueInfoEntry("serializationDataFormat")); return event; } private VariableUpdateEvent createVariableUpdateEventFromDetails( GetHistoricDetailVariableUpdateResponse getHistoricDetailVariableUpdateResponse) { final VariableUpdateEvent event = new VariableUpdateEvent(); BeanUtils.copyProperties(getHistoricDetailVariableUpdateResponse, event); event.setTimestamp(getHistoricDetailVariableUpdateResponse.getTime()); setVariableValue(event, getHistoricDetailVariableUpdateResponse.getValue(), getHistoricDetailVariableUpdateResponse.getVariableType(), getHistoricDetailVariableUpdateResponse.getValueInfoEntry("serializationDataFormat")); return event; } private ProcessDefinitionEvent createProcessDefinitionEvent(GetProcessDefinitionResponse resp, final GetDeploymentResponse deploymentResponse) { ProcessDefinitionEvent e = new ProcessDefinitionEvent(); e.setId(resp.getId()); e.setCategory(resp.getCategory()); e.setDescription(resp.getDescription()); e.setHistoryTimeToLive(resp.getHistoryTimeToLive()); e.setKey(resp.getKey()); e.setName(resp.getName()); e.setResource(resp.getResource()); e.setSuspended(resp.getSuspended()); e.setVersion(resp.getVersion()); e.setVersionTag(resp.getVersionTag()); e.setDeploymentId(resp.getDeploymentId()); e.setTenantId(resp.getTenantId()); e.setDeploymentTime(deploymentResponse.getDeploymentTime()); e.setSource(deploymentResponse.getSource()); return e; } private CommentEvent createCommentEventFromDetails( GetCommentResponse commentResponse, ActivityInstanceEvent activityInstanceEvent) { final CommentEvent event = new CommentEvent(); BeanUtils.copyProperties(activityInstanceEvent, event); event.setId(commentResponse.getId()); event.setUserId(commentResponse.getUserId()); event.setTimestamp(commentResponse.getTime()); event.setMessage(commentResponse.getMessage()); return event; } private void setVariableValue(VariableUpdateEvent event, Object value, String type, String serializationDataFormat) { if (value != null) { switch (StringUtils.defaultString(type)) { case "Object": { if (StringUtils.equalsIgnoreCase(serializationDataFormat, "application/json") && value instanceof String) { try { final Object decodedValue = this.objectMapper.readValue((String) value, Object.class); if (decodedValue != null) { event.setComplexValue(decodedValue); event.setSerializerName("spin://application/json"); } } catch (IOException e) { LOGGER.error("IOException found."); } } break; } case "String": { event.setTextValue(Objects.toString(value)); event.setSerializerName(StringUtils.lowerCase(type)); break; } case "Long": case "Integer": case "Byte": case "Short": { event.setDoubleValue(((Number) value).doubleValue()); event.setLongValue(((Number) value).longValue()); event.setTextValue(value.toString()); event.setSerializerName(StringUtils.lowerCase(type)); break; } case "Double": case "Float": { event.setDoubleValue(((Number) value).doubleValue()); event.setSerializerName(StringUtils.lowerCase(type)); break; } case "Boolean": { event.setSerializerName(StringUtils.lowerCase(type)); if (Boolean.TRUE.equals(value)) { event.setLongValue(1L); } else { event.setLongValue(0L); } break; } default: { LOGGER.warn("Data type does not exist."); } } } } /** * * Format Date to String - used in REST API * * @param date * to format * @return formated Date according to configured format * */ String formatDate(Date date) { String dateFormatPattern = camundaProperties.getDateFormatPattern(); SimpleDateFormat apiDateFormat = new SimpleDateFormat(dateFormatPattern); String sourceTimeZone = camundaProperties.getSourceTimeZone(); if (sourceTimeZone != null && !sourceTimeZone.isEmpty()) apiDateFormat.setTimeZone(TimeZone.getTimeZone(sourceTimeZone)); return apiDateFormat.format(date); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy