diff --git a/apps/forms-flow-ai/README.md b/apps/forms-flow-ai/README.md index 4819a120..2c684f3d 100644 --- a/apps/forms-flow-ai/README.md +++ b/apps/forms-flow-ai/README.md @@ -66,7 +66,7 @@ The following files are slated to be removed because we can leverage the open so ### **Required files:** -- `pom-docker.xml` +- `pom-docker.xml` (renamed pom.xml with v5.1.0 upgrade) - Needed for redis dependency; otherwise, the dependencies are largely identical to open source. - `src/main/resources/application.yaml` - Useful to have application.yaml in this repo as it is used for customizing camunda. @@ -186,7 +186,7 @@ Upon a new release of open source, care should be taken to keep all the files al - All files under `src` folder. *forms-flow-bpm* -- `pom-docker.xml` +- `pom-docker.xml` (renamed pom.xml with v5.1.0 upgrade) - Java dependencies' versions may be different. - `application.yaml` - should be checked for updates as well but not all changes are applicable to service BC. Changes should be done on a case by case basis based on Changelog documentation. diff --git a/apps/forms-flow-ai/forms-flow-bpm/Dockerfile b/apps/forms-flow-ai/forms-flow-bpm/Dockerfile index acb76076..8d0666cb 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/Dockerfile +++ b/apps/forms-flow-ai/forms-flow-bpm/Dockerfile @@ -1,17 +1,18 @@ # Modified by Yichun Zhao and Walter Moar # Maven build -FROM artifacts.developer.gov.bc.ca/docker-remote/maven:3.6.1-jdk-11-slim AS MAVEN_TOOL_CHAIN +FROM artifacts.developer.gov.bc.ca/docker-remote/maven:3.8.1-openjdk-17-slim AS MAVEN_TOOL_CHAIN +# FROM maven:3.8.1-openjdk-17-slim AS MAVEN_TOOL_CHAIN RUN apt-get update \ && apt-get install -y git RUN git clone -b ${FORMIO_SOURCE_REPO_BRANCH} ${FORMIO_SOURCE_REPO_URL} /bpm/ -#RUN cp /bpm/forms-flow-bpm/pom-docker.xml /tmp/pom.xml +# RUN cp /bpm/forms-flow-bpm/pom.xml /tmp/pom.xml RUN cp /bpm/forms-flow-bpm/settings-docker.xml /usr/share/maven/ref/ +RUN cp /bpm/forms-flow-bpm/pom-default.xml /tmp/pom-default.xml COPY ./pom-docker.xml /tmp/pom.xml -# COPY ./settings-docker.xml /usr/share/maven/ref/ WORKDIR /tmp/ @@ -19,20 +20,23 @@ WORKDIR /tmp/ RUN mvn -s /usr/share/maven/ref/settings-docker.xml dependency:resolve-plugins dependency:resolve dependency:go-offline -B RUN cp -r /bpm/forms-flow-bpm/src/ /tmp/src/ +# ARG CUSTOM_SRC_DIR=src/ ARG CUSTOM_SRC_DIR=src/ # Override these files they have custom changes in the sbc_divapps directory COPY ./${CUSTOM_SRC_DIR}/ /tmp/${CUSTOM_SRC_DIR}/ -RUN mvn -s /usr/share/maven/ref/settings-docker.xml package +# COPY ${CUSTOM_SRC_DIR} /tmp/${CUSTOM_SRC_DIR}/ +RUN mvn -s /usr/share/maven/ref/settings-docker.xml package -P default # Final custom slim java image (for apk command see jdk-11.0.3_7-alpine-slim) -FROM artifacts.developer.gov.bc.ca/docker-remote/adoptopenjdk/openjdk11:jdk-11.0.3_7-alpine +FROM artifacts.developer.gov.bc.ca/docker-remote/openjdk:17-jdk-alpine +# FROM openjdk:17-jdk-alpine -ENV JAVA_VERSION jdk-11.0.3+7 -ENV JAVA_HOME=/opt/java/openjdk \ - PATH="/opt/java/openjdk/bin:$PATH" +ENV JAVA_VERSION=17-ea+14 +ENV JAVA_HOME=/opt/java/openjdk-17 \ + PATH="/opt/java/openjdk-17/bin:$PATH" EXPOSE 8080 # OpenShift has /app in the image, but it's missing when doing local development - Create it when missing @@ -43,4 +47,5 @@ COPY --from=MAVEN_TOOL_CHAIN /tmp/target/forms-flow-bpm.jar ./app RUN chmod a+rwx -R /app WORKDIR /app VOLUME /tmp -ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/forms-flow-bpm.jar"] \ No newline at end of file +# ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/forms-flow-bpm.jar"] +ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom", "-Dpolyglot.js.nashorn-compat=true", "-Dpolyglot.engine.WarnInterpreterOnly=false", "-jar","/app/forms-flow-bpm.jar"] \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/pom-docker.xml b/apps/forms-flow-ai/forms-flow-bpm/pom-docker.xml index 7392e00b..dc2b9ceb 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/pom-docker.xml +++ b/apps/forms-flow-ai/forms-flow-bpm/pom-docker.xml @@ -4,31 +4,33 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.camunda.bpm - extension - 2.0.0 + org.camunda.bpm.extension + formsflow-bpm + 5.1.0 + pom Forms flow BPM Extension Forms flow BPM Extension - 11 - 11 - 11 + 17 + 17 + 17 UTF-8 ${encoding} ${encoding} false - 7.15.0 - 7.15.0 - 1.2.2 - 1.2.0 - 2.6.4 - 2.6.4 - 2.13.3 - 2.2.3 + 2.2.3 + 7.17.0 + 1.5.0 + 1.3.0 + 2.6.6 + 2.6.6 + 2.14.0 + @@ -36,7 +38,7 @@ com.h2database h2 - 2.0.206 + 2.0.206 org.camunda.bpm @@ -63,6 +65,11 @@ + + org.keycloak + keycloak-admin-client + 21.0.1 + org.springframework.boot spring-boot-starter-webflux @@ -71,13 +78,18 @@ org.camunda.bpm.springboot camunda-bpm-spring-boot-starter-webapp - ${version.camundaSpringBoot} + ${version.camunda} org.camunda.bpm.springboot camunda-bpm-spring-boot-starter-rest - ${version.camundaSpringBoot} + ${version.camunda} + + + + org.springframework.boot + spring-boot-starter-hateoas @@ -116,7 +128,7 @@ spring-security-oauth2-jose - + org.camunda.bpm.extension camunda-bpm-identity-keycloak @@ -179,8 +191,8 @@ org.postgresql postgresql - - 42.2.5 + + 42.4.3 @@ -249,7 +261,7 @@ org.jsoup jsoup - 1.13.1 + 1.15.3 @@ -290,15 +302,33 @@ org.springframework spring-websocket - 5.3.4 + 5.3.20 org.springframework spring-messaging - 5.3.4 + 5.3.20 + + org.graalvm.js + js-scriptengine + 22.1.0.1 + + + + org.graalvm.js + js + 22.1.0.1 + + + + org.springframework.boot + spring-boot-starter-jersey + + + org.springframework.boot spring-boot-starter-data-redis-reactive @@ -325,12 +355,12 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M5 + 3.0.0-M7 org.jacoco jacoco-maven-plugin - 0.8.7 + 0.8.8 target/jacoco-ut @@ -349,6 +379,18 @@ org/camunda/bpm/extension/commons/connector/auth/FormioContext.class org/camunda/bpm/extension/commons/connector/*.class org/camunda/bpm/extension/CamundaApplication.class + org/camunda/bpm/extension/commons/exceptions/*.class + org/camunda/bpm/extension/commons/utils/*.class + org/camunda/bpm/extension/hooks/controllers/mapper/*.class + org/camunda/bpm/extension/hooks/exceptions/*.class + org/camunda/bpm/extension/hooks/listeners/execution/FormAccessTokenCacheListener.class + org/camunda/bpm/extension/hooks/rest/exception/*.class + org/camunda/bpm/extension/commons/exceptions/*.class + org/camunda/bpm/extension/commons/config/*.class + org/camunda/bpm/extension/hooks/rest/constant/*.class + org/camunda/bpm/extension/hooks/services/IMessageEvent.class + org/camunda/bpm/extension/hooks/rest/dto/*.class + org/camunda/bpm/extension/hooks/rest/impl/*.class @@ -365,6 +407,7 @@ spring-boot-maven-plugin ${version.springBoot} + org.camunda.bpm.extension.CamundaApplication ZIP @@ -389,37 +432,13 @@ default + + pom-default.xml + true - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0-M5 - - - org.jacoco - jacoco-maven-plugin - 0.8.7 - - target/jacoco-ut - - - - - prepare-agent - report - - - - - - - - + - + + \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/connector/HTTPServiceInvoker.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/connector/HTTPServiceInvoker.java new file mode 100644 index 00000000..69f93693 --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/connector/HTTPServiceInvoker.java @@ -0,0 +1,112 @@ +package org.camunda.bpm.extension.commons.connector; + +import org.apache.commons.lang3.StringUtils; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.camunda.bpm.extension.commons.ro.req.IRequest; +import org.camunda.bpm.extension.commons.ro.res.IResponse; +import org.camunda.bpm.extension.hooks.services.FormSubmissionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.*; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Http Service Invoker. + * This class prepares the payload and invokes the respective access handler + * based on the service ID. + */ +@Component("httpServiceInvoker") +public class HTTPServiceInvoker { + + private static final String FORMIO_URL = "formio.url"; + private static final String API_URL = "api.url"; + private static final String BPM_URL = "bpm.url"; + private static final String ANALYSIS_URL = "analysis.url"; + private static final String APPLICATION_ACCESS_HANDLER = "applicationAccessHandler"; + private static final String BPM_ACCESS_HANDLER = "bpmAccessHandler"; + private static final String TEXT_ANALYZER_ACCESS_HANDLER = "textAnalyzerAccessHandler"; + private static final String FORM_ACCESS_HANDLER = "formAccessHandler"; + private static final String CUSTOM_SUBMISSION_ACCESS_HANDLER = "CustomSubmissionAccessHandler"; + + // private final Logger LOGGER = + // LoggerFactory.getLogger(HTTPServiceInvoker.class.getName()); + private final Logger LOGGER = Logger.getLogger(FormSubmissionService.class.getName()); + + @Autowired + private AccessHandlerFactory accessHandlerFactory; + @Resource(name = "bpmObjectMapper") + private ObjectMapper bpmObjectMapper; + @Autowired + private Properties integrationCredentialProperties; + + public ResponseEntity execute(String url, HttpMethod method, Object payload) throws IOException { + // LOGGER.log("StringUrl=", url); + LOGGER.log(Level.INFO, "StringUrl=" + url); + String dataJson = payload != null ? bpmObjectMapper.writeValueAsString(payload) : null; + LOGGER.log(Level.INFO, "dataJson=" + dataJson); + return execute(url, method, dataJson); + } + + public ResponseEntity execute(String url, HttpMethod method, String payload) { + // LOGGER.info("String Service ID: {}", getServiceId(url)); + LOGGER.log(Level.INFO, "StringUrl=" + url); + LOGGER.log(Level.INFO, "String Service ID: {}" + getServiceId(url)); + LOGGER.log(Level.INFO, "String Service Payload: {}" + payload); + return accessHandlerFactory.getService(getServiceId(url)).exchange(url, method, payload); + } + + public ResponseEntity execute(String url, HttpMethod method, IRequest payload, + Class responseClazz) { + // LOGGER.info("IRequest Service ID: {}", getServiceId(url)); + LOGGER.log(Level.INFO, "IRequest Service ID: {}" + getServiceId(url)); + return accessHandlerFactory.getService(getServiceId(url)).exchange(url, method, payload, responseClazz); + } + + public ResponseEntity executeWithParamsAndPayload(String url, HttpMethod method, + Map requestParams, IRequest payload) { + return accessHandlerFactory.getService(getServiceId(url)).exchange(url, method, requestParams, payload); + } + + private String getServiceId(String url) { + if (StringUtils.contains(url, getProperties().getProperty("api.url"))) { + return "applicationAccessHandler"; + } + return "formAccessHandler"; + // Boolean enableCustomSubmission = Boolean + // .valueOf(integrationCredentialProperties.getProperty("forms.enableCustomSubmission")); + // if (isUrlValid(url, fetchUrlFromProperty(API_URL))) { + // return APPLICATION_ACCESS_HANDLER; + // } else if (isUrlValid(url, fetchUrlFromProperty(BPM_URL))) { + // return BPM_ACCESS_HANDLER; + // } else if (isUrlValid(url, fetchUrlFromProperty(ANALYSIS_URL))) { + // return TEXT_ANALYZER_ACCESS_HANDLER; + // } else if (isUrlValid(url, fetchUrlFromProperty(FORMIO_URL))) { + // if (enableCustomSubmission && StringUtils.contains(url, "/submission")) { + // return CUSTOM_SUBMISSION_ACCESS_HANDLER; + // } else { + // return FORM_ACCESS_HANDLER; + // } + // } + // return ""; + } + + private String fetchUrlFromProperty(String key) { + return getProperties().getProperty(key); + } + + private boolean isUrlValid(String sourceUrl, String targetUrl) { + return StringUtils.isNotBlank(targetUrl) && StringUtils.contains(sourceUrl, targetUrl) ? true : false; + } + + public Properties getProperties() { + return integrationCredentialProperties; + } + +} \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/connector/support/ApplicationAccessHandler.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/connector/support/ApplicationAccessHandler.java index 07f08630..55201dfe 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/connector/support/ApplicationAccessHandler.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/connector/support/ApplicationAccessHandler.java @@ -1,53 +1,87 @@ package org.camunda.bpm.extension.commons.connector.support; import com.google.gson.JsonObject; +import org.camunda.bpm.extension.commons.ro.req.IRequest; +import org.camunda.bpm.extension.commons.ro.res.IResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; +// import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.*; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; +import org.springframework.context.annotation.Primary; -import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId; +import java.util.Map; +import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId; /** * This class serves as gateway for all application service interactions. - * This customization exists because the authentication mechanisms has changed between - * forsmflow open source and service bc's implementation. Once Service BC's authentication - * mechanisms are updated, this file can be removed. * * @author sumathi.thirumani@aot-technologies.com */ @Service("applicationAccessHandler") -public class ApplicationAccessHandler implements IAccessHandler { +public class ApplicationAccessHandler extends AbstractAccessHandler { + // public class ApplicationAccessHandler implements IAccessHandler { + + private final Logger LOGGER = LoggerFactory.getLogger(ApplicationAccessHandler.class); + + @Autowired + private WebClient unAuthenticatedWebClient; + + @Autowired + private OAuth2RestTemplate oAuth2RestTemplate; - private final Logger LOGGER = LoggerFactory.getLogger(ApplicationAccessHandler.class); - - @Autowired - private WebClient unAuthenticatedWebClient; + // @Override + public ResponseEntity exchange(String url, HttpMethod method, String payload) { - @Autowired - private OAuth2RestTemplate oAuth2RestTemplate; + payload = (payload == null) ? new JsonObject().toString() : payload; + ResponseEntity response = unAuthenticatedWebClient.method(method).uri(url) + .accept(MediaType.APPLICATION_JSON) + .headers(httpHeaders -> httpHeaders + .setBearerAuth(oAuth2RestTemplate.getAccessToken().getValue())) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(Mono.just(payload), String.class) + .retrieve() + .toEntity(String.class) + .block(); - public ResponseEntity exchange(String url, HttpMethod method, String payload) { + // ResponseEntity response = entityMono.block(); + return new ResponseEntity<>(response.getBody(), response.getStatusCode()); + } - payload = (payload == null) ? new JsonObject().toString() : payload; + // @Override + public ResponseEntity exchange(String url, HttpMethod method, IRequest payload, + Class responseClazz) { - Mono> entityMono = unAuthenticatedWebClient.method(method).uri(url) - .accept(MediaType.APPLICATION_JSON) - .headers(httpHeaders -> httpHeaders.setBearerAuth(oAuth2RestTemplate.getAccessToken().getValue())) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .body(Mono.just(payload), String.class) - .retrieve() - .toEntity(String.class); + ResponseEntity response = unAuthenticatedWebClient.method(method).uri(url) + .accept(MediaType.APPLICATION_JSON) + .headers(httpHeaders -> httpHeaders + .setBearerAuth(oAuth2RestTemplate.getAccessToken().getValue())) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body((payload == null ? BodyInserters.empty() : BodyInserters.fromValue(payload))) + .retrieve() + .onStatus(HttpStatus::is4xxClientError, + clientResponse -> Mono.error( + new HttpClientErrorException(HttpStatus.BAD_REQUEST))) + .toEntity(responseClazz) + .block(); + return new ResponseEntity<>(response.getBody(), response.getStatusCode()); + } - ResponseEntity response = entityMono.block(); - return new ResponseEntity<>(response.getBody(), response.getStatusCode()); - } + // @Override + /** + * public ResponseEntity exchange(String url, HttpMethod method, + * Map queryParams, + * IRequest payload) { + * return null; + * } + **/ } \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/connector/support/FormAccessHandler.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/connector/support/FormAccessHandler.java new file mode 100644 index 00000000..6c8d3f3d --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/connector/support/FormAccessHandler.java @@ -0,0 +1,117 @@ +package org.camunda.bpm.extension.commons.connector.support; + +import com.google.gson.JsonObject; +import org.apache.commons.lang3.StringUtils; +import org.camunda.bpm.extension.commons.connector.FormioTokenServiceProvider; +import org.camunda.bpm.extension.hooks.exceptions.FormioServiceException; +import org.camunda.bpm.extension.hooks.services.FormSubmissionService; +// import org.slf4j.Logger; +// import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.Properties; + +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Form Access Handler. + * This class serves as gateway for all formio interactions. + */ + +@Service("formAccessHandler") +public class FormAccessHandler extends AbstractAccessHandler implements IAccessHandler { + + // private final Logger logger = + // LoggerFactory.getLogger(FormAccessHandler.class.getName()); + private final Logger LOGGER = Logger.getLogger(FormAccessHandler.class.getName()); + + static final int TOKEN_EXPIRY_CODE = 404; + + @Autowired + private Properties integrationCredentialProperties; + @Autowired + private WebClient unauthenticatedWebClient; + @Autowired + private FormioTokenServiceProvider formioTokenServiceProvider; + + protected Properties getIntegrationCredentialProperties() { + return integrationCredentialProperties; + } + + public ResponseEntity exchange(String url, HttpMethod method, String payload) { + String accessToken = formioTokenServiceProvider.getAccessToken(); + if (StringUtils.isBlank(accessToken)) { + // logger.info("Access token is blank. Cannot invoke service:{}", url); + return null; + } + ResponseEntity response = exchange(url, method, payload, accessToken); + if (response.getStatusCodeValue() == TOKEN_EXPIRY_CODE) { + exchange(url, method, payload, formioTokenServiceProvider.getAccessToken()); + } + // logger.info("Response code for service invocation: {}", + // response.getStatusCode()); + LOGGER.log(Level.INFO, "Response code for service invocation: {}" + response.getStatusCode()); + return response; + } + + public ResponseEntity exchange(String url, HttpMethod method, String payload, String accessToken) { + + payload = (payload == null) ? new JsonObject().toString() : payload; + LOGGER.log(Level.INFO, "HttpMethod=" + HttpMethod.PATCH.name()); + LOGGER.log(Level.INFO, "method.name=" + method.name()); + // logger.info("HttpMethod=" + HttpMethod.PATCH.name()); + // logger.info("method.name()=" + method.name()); + + if (HttpMethod.PATCH.name().equals(method.name())) { + LOGGER.log(Level.INFO, "payload" + payload); + // logger.info("payload=" + payload); + Mono> entityMono = unauthenticatedWebClient.patch() + .uri(getDecoratedServerUrl(url)) + .bodyValue(payload) + .header("x-jwt-token", accessToken) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus(HttpStatus::is4xxClientError, + response -> Mono.error(new FormioServiceException(response.toString()))) + .toEntity(String.class); + + ResponseEntity response = entityMono.block(); + if (response != null && "Token Expired".equalsIgnoreCase(response.getBody())) { + return new ResponseEntity<>(response.getBody(), HttpStatus.valueOf(TOKEN_EXPIRY_CODE)); + } + return response; + } else { + return unauthenticatedWebClient.method(method) + .uri(getDecoratedServerUrl(url)) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header("x-jwt-token", accessToken) + .body(Mono.just(payload), String.class) + .retrieve() + .onStatus(HttpStatus::is4xxClientError, + response -> Mono.error(new FormioServiceException(response.toString()))) + .toEntity(String.class) + .block(); + } + } + + private String getDecoratedServerUrl(String url) { + if (StringUtils.contains(url, "/form/")) { + return getIntegrationCredentialProperties().getProperty("formio.url") + "/form/" + + StringUtils.substringAfter(url, "/form/"); + } + return getIntegrationCredentialProperties().getProperty("formio.url") + "/" + + StringUtils.substringAfterLast(url, "/"); + } +} diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/event/CamundaEventListener.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/event/CamundaEventListener.java index 3dfbf08b..76275e8f 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/event/CamundaEventListener.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/event/CamundaEventListener.java @@ -17,11 +17,15 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; - +import javax.annotation.Resource; import java.util.*; import java.util.logging.Logger; +import static org.camunda.bpm.extension.commons.utils.VariableConstants.FORM_URL; +import static org.camunda.bpm.extension.commons.utils.VariableConstants.APPLICATION_STATUS; +import static org.camunda.bpm.extension.commons.utils.VariableConstants.APPLICATION_ID; + /** * This class intercepts all camunda task and push socket messages for web tier updates. * @@ -41,6 +45,9 @@ public class CamundaEventListener implements ITaskEvent { @Value("${websocket.messageEvents}") private String messageEvents; + @Resource(name = "bpmObjectMapper") + private ObjectMapper bpmObjectMapper; + @EventListener public void onTaskEventListener(DelegateTask taskDelegate) { @@ -48,10 +55,10 @@ public void onTaskEventListener(DelegateTask taskDelegate) { try { if (isRegisteredEvent(taskDelegate.getEventName())) { if(isAllowed(EventCategory.TASK_EVENT_DETAILS.name())) { - this.stringRedisTemplate.convertAndSend(getTopicNameForTaskDetail(), getObjectMapper().writeValueAsString(getTaskMessage(taskDelegate))); + this.stringRedisTemplate.convertAndSend(getTopicNameForTaskDetail(), bpmObjectMapper.writeValueAsString(getTaskMessage(taskDelegate))); } if(isAllowed(EventCategory.TASK_EVENT.name())) { - this.stringRedisTemplate.convertAndSend(getTopicNameForTask(), getObjectMapper().writeValueAsString(getTaskEventMessage(taskDelegate))); + this.stringRedisTemplate.convertAndSend(getTopicNameForTask(), bpmObjectMapper.writeValueAsString(getTaskEventMessage(taskDelegate))); } } } catch (JsonProcessingException e) { @@ -85,7 +92,8 @@ private List getRegisteredEvents() { if ("DEFAULT".equalsIgnoreCase(messageEvents)) { return getDefaultRegisteredEvents(); } - return Arrays.asList(StringUtils.split(messageEvents,",")); + String events = messageEvents != null ? messageEvents : ""; + return Arrays.asList(StringUtils.split(events, ",")); } private Map getVariables(DelegateTask taskDelegate) { @@ -100,7 +108,7 @@ private Map getVariables(DelegateTask taskDelegate) { } private List getElements() { - return new ArrayList<>(Arrays. asList("applicationId", "formUrl", "applicationStatus")); + return new ArrayList<>(Arrays.asList(APPLICATION_ID, FORM_URL, APPLICATION_STATUS)); } private List getDefaultRegisteredEvents() { @@ -110,10 +118,6 @@ private List getDefaultRegisteredEvents() { ); } - private ObjectMapper getObjectMapper() { - return new ObjectMapper(); - } - enum EventCategory { TASK_EVENT, TASK_EVENT_DETAILS; diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/event/TaskEventTopicListener.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/event/TaskEventTopicListener.java index 56b60521..51b2b33d 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/event/TaskEventTopicListener.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/event/TaskEventTopicListener.java @@ -1,6 +1,6 @@ package org.camunda.bpm.extension.commons.io.event; -import org.camunda.bpm.extension.commons.io.socket.message.service.TaskEventMessageService; +import org.camunda.bpm.extension.commons.io.message.service.TaskEventMessageService; import org.camunda.bpm.extension.commons.io.socket.message.TaskMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/service/TaskEventMessageService.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/message/service/TaskEventMessageService.java similarity index 87% rename from apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/service/TaskEventMessageService.java rename to apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/message/service/TaskEventMessageService.java index d0f06b2b..a41dc42d 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/service/TaskEventMessageService.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/message/service/TaskEventMessageService.java @@ -1,4 +1,4 @@ -package org.camunda.bpm.extension.commons.io.socket.message.service; +package org.camunda.bpm.extension.commons.io.message.service; import org.camunda.bpm.extension.commons.io.socket.message.TaskMessage; import org.springframework.beans.factory.annotation.Autowired; @@ -28,9 +28,8 @@ public void sendMessage(TaskMessage message) { try { template.convertAndSend("/topic/task-event", objectMapper.writeValueAsString(message)); } catch (JsonProcessingException e) { - LOGGER.log(Level.SEVERE,"Exception Occured in preparing message", e); + LOGGER.log(Level.SEVERE, "Exception Occured in preparing message", e); } } - } diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/RedisConfig.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/RedisConfig.java index 38a1478f..53c15c10 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/RedisConfig.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/RedisConfig.java @@ -4,6 +4,8 @@ import org.camunda.bpm.extension.commons.io.event.TaskEventTopicListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; @@ -15,6 +17,7 @@ import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import java.util.Properties; +import java.util.logging.Logger; /** * Configuration for Message Broker. @@ -24,27 +27,44 @@ @Configuration public class RedisConfig implements ITaskEvent { + private final Logger LOGGER = Logger.getLogger(RedisConfig.class.getName()); + @Autowired private Properties messageBrokerProperties; + @Value("${websocket.messageBroker.host}") + private String messageBrokerHost; + @Value("${websocket.messageBroker.port}") + private String messageBrokerPort; + @Value("${websocket.messageBroker.passcode}") + private String messageBrokerPasscode; + @Bean RedisConnectionFactory redisConnectionFactory() { - RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(messageBrokerProperties.getProperty("messageBroker.host"), - Integer.valueOf(messageBrokerProperties.getProperty("messageBroker.port"))); - redisStandaloneConfiguration.setPassword(messageBrokerProperties.getProperty("messageBroker.passcode")); + /* + * RedisStandaloneConfiguration redisStandaloneConfiguration = new + * RedisStandaloneConfiguration(messageBrokerProperties.getProperty( + * "messageBroker.host"), + * Integer.valueOf(messageBrokerProperties.getProperty("messageBroker.port"))); + * redisStandaloneConfiguration.setPassword(messageBrokerProperties.getProperty( + * "messageBroker.passcode")); + */ + + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(messageBrokerHost, + Integer.valueOf(messageBrokerPort)); + redisStandaloneConfiguration.setPassword(messageBrokerPasscode); return new LettuceConnectionFactory(redisStandaloneConfiguration); } @Bean RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, - @Qualifier("taskMessageListenerAdapter") MessageListenerAdapter taskMessageListenerAdapter) { + @Qualifier("taskMessageListenerAdapter") MessageListenerAdapter taskMessageListenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.addMessageListener(taskMessageListenerAdapter, new PatternTopic(getTopicNameForTask())); return container; } - @Bean("taskMessageListenerAdapter") MessageListenerAdapter chatMessageListenerAdapter(TaskEventTopicListener taskEventTopicListener) { return new MessageListenerAdapter(taskEventTopicListener, getExecutorName()); @@ -55,7 +75,8 @@ StringRedisTemplate template(RedisConnectionFactory redisConnectionFactory) { return new StringRedisTemplate(redisConnectionFactory); } - private String getExecutorName() { return "receiveTaskMessage";} - + private String getExecutorName() { + return "receiveTaskMessage"; + } } \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/message/TaskEventMessage.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/message/TaskEventMessage.java new file mode 100644 index 00000000..521afccc --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/message/TaskEventMessage.java @@ -0,0 +1,21 @@ +package org.camunda.bpm.extension.commons.io.socket.message; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.io.Serializable; + +/** + * Task Event Message. + * Class for holding TaskEvent data. + */ + +@Data +@NoArgsConstructor +@ToString +public class TaskEventMessage implements Serializable { + private String id; + private String eventName; + private String tenantId; +} \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/message/TaskMessage.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/message/TaskMessage.java new file mode 100644 index 00000000..02b9be2a --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/commons/io/socket/message/TaskMessage.java @@ -0,0 +1,37 @@ +package org.camunda.bpm.extension.commons.io.socket.message; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +/** + * Task Message. + * Class for holding Task data. + */ +@Data +@NoArgsConstructor +@ToString +public class TaskMessage implements Serializable { + + private String assignee; + private Date createTime; + private String deleteReason; + private String description; + private Date dueDate; + private String eventName; + private String executionId; + private Date followUpDate; + private String id; + private String name; + private String owner; + private int priority; + private String processDefinitionId; + private String processInstanceId; + private String taskDefinitionKey; + private Map variables; + private String tenantId; +} diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/controllers/FormBuilderPipelineController.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/controllers/FormBuilderPipelineController.java index af7f7ece..95ec6f20 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/controllers/FormBuilderPipelineController.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/controllers/FormBuilderPipelineController.java @@ -36,7 +36,8 @@ import java.util.logging.Logger; /** - * This class is intended to perform the data transformation from different source systems. + * This class is intended to perform the data transformation from different + * source systems. * Supported sources : Orbeon - Customer Feedback Form * * @author sumathi.thirumani@aot-technologies.com @@ -55,13 +56,14 @@ public class FormBuilderPipelineController { /** * Creates a camunda process instance for the orbeon form data given. + * * @param request The request object containing the CCII form data. */ - @PostMapping(value = "/orbeon/data",consumes = MediaType.APPLICATION_XML_VALUE) + @PostMapping(value = "/orbeon/data", consumes = MediaType.APPLICATION_XML_VALUE) public void createProcess(HttpServletRequest request) { - LOGGER.info("Inside Data transformation controller" +request.getParameterMap()); + LOGGER.info("Inside Data transformation controller" + request.getParameterMap()); String formXML = null; - try(InputStream is = request.getInputStream();BufferedInputStream bis = new BufferedInputStream(is)) { + try (InputStream is = request.getInputStream(); BufferedInputStream bis = new BufferedInputStream(is)) { byte[] xmlData = new byte[request.getContentLength()]; bis.read(xmlData, 0, xmlData.length); if (request.getCharacterEncoding() != null) { @@ -69,43 +71,45 @@ public void createProcess(HttpServletRequest request) { } else { formXML = new String(xmlData); } - LOGGER.info("Received XML Document-------->"+formXML); - Map processVariables = prepareRequestVariableMap(formXML); - for (String key: processVariables.keySet()) { - if(StringUtils.endsWith(key,"_date") || StringUtils.endsWith(key,"_date_time")) { + LOGGER.info("Received XML Document-------->" + formXML); + Map processVariables = prepareRequestVariableMap(formXML); + for (String key : processVariables.keySet()) { + if (StringUtils.endsWith(key, "_date") || StringUtils.endsWith(key, "_date_time")) { VariableData valueVariableData = (VariableData) processVariables.get(key); - if(!isDateValid((String) valueVariableData.getValue())) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The value for " + key + " is invalid"); + if (!isDateValid((String) valueVariableData.getValue())) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "The value for " + key + " is invalid"); } } } Boolean status = createProcessInstance(processVariables); - if(status == false) { - //Email the form to support group for manual processing - sendEmail(formXML,request.getParameter("document"),null); - LOGGER.log(Level.SEVERE,"Unable to create process instance"); - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Unable to create process instance"); + if (status == false) { + // Email the form to support group for manual processing + sendEmail(formXML, request.getParameter("document"), null); + LOGGER.log(Level.SEVERE, "Unable to create process instance"); + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, + "Unable to create process instance"); } } catch (IOException exception) { - sendEmail(formXML,request.getParameter("document"), null); - LOGGER.log(Level.SEVERE,"Unable to parse the XML from orbeon"); - LOGGER.log(Level.SEVERE,"Exception occurred:"+ ExceptionUtils.exceptionStackTraceAsString(exception)); + sendEmail(formXML, request.getParameter("document"), null); + LOGGER.log(Level.SEVERE, "Unable to parse the XML from orbeon"); + LOGGER.log(Level.SEVERE, "Exception occurred:" + ExceptionUtils.exceptionStackTraceAsString(exception)); throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unable to parse the XML from orbeon"); } catch (ResponseStatusException exception) { - sendEmail(formXML,request.getParameter("document"), null); - LOGGER.log(Level.SEVERE,exception.getMessage()); - LOGGER.log(Level.SEVERE,"Exception occurred:"+ ExceptionUtils.exceptionStackTraceAsString(exception)); + sendEmail(formXML, request.getParameter("document"), null); + LOGGER.log(Level.SEVERE, exception.getMessage()); + LOGGER.log(Level.SEVERE, "Exception occurred:" + ExceptionUtils.exceptionStackTraceAsString(exception)); throw exception; } catch (Exception ex) { - sendEmail(formXML,request.getParameter("document"), null); + sendEmail(formXML, request.getParameter("document"), null); LOGGER.log(Level.SEVERE, ex.getMessage()); - LOGGER.log(Level.SEVERE,"Exception occurred:"+ ExceptionUtils.exceptionStackTraceAsString(ex)); + LOGGER.log(Level.SEVERE, "Exception occurred:" + ExceptionUtils.exceptionStackTraceAsString(ex)); throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error"); } } - private void sendEmail(String formXML,String documentId, String exceptionTrace){ - Map variables = new HashMap<>(); + private void sendEmail(String formXML, String documentId, String exceptionTrace) { + Map variables = new HashMap<>(); try { HttpHeaders headers = new HttpHeaders(); ObjectMapper mapper = new ObjectMapper(); @@ -115,19 +119,19 @@ private void sendEmail(String formXML,String documentId, String exceptionTrace){ variables.put("category", new VariableData("api_start_failure")); variables.put("orbeon_document_id", new VariableData(documentId)); variables.put("formXML", new VariableData(formXML)); - //Include exception if any - if(StringUtils.isNotBlank(exceptionTrace)) { - StringValue exceptionDataValue = Variables.stringValue(exceptionTrace,true); + // Include exception if any + if (StringUtils.isNotBlank(exceptionTrace)) { + StringValue exceptionDataValue = Variables.stringValue(exceptionTrace, true); variables.put("exception", exceptionDataValue); } msgRequest.setMessageName("Service_Api_Message_Email"); msgRequest.setProcessVariables(variables); HttpEntity msgReq = new HttpEntity(mapper.writeValueAsString(msgRequest), headers); ResponseEntity msgResponse = getOAuth2RestTemplate().postForEntity( - getAPIContextURL() + "/engine-rest/message", msgReq, String.class); - LOGGER.info("Message response code:"+msgResponse.getStatusCode()); + getAPIContextURL() + "/engine-rest-ext/message", msgReq, String.class); + LOGGER.info("Message response code:" + msgResponse.getStatusCode()); } catch (Exception ex) { - LOGGER.log(Level.SEVERE,"Exception occurred:"+ExceptionUtils.exceptionStackTraceAsString(ex)); + LOGGER.log(Level.SEVERE, "Exception occurred:" + ExceptionUtils.exceptionStackTraceAsString(ex)); } } @@ -145,31 +149,30 @@ private boolean isDateValid(String dateStr) { return true; } - private Boolean createProcessInstance(Map processVariables) throws JsonProcessingException { - //HTTP Headers + private Boolean createProcessInstance(Map processVariables) throws JsonProcessingException { + // HTTP Headers HttpHeaders headers = new HttpHeaders(); ObjectMapper mapper = new ObjectMapper(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("Authorization", "Bearer " + getOAuth2RestTemplate().getAccessToken()); CreateProcessRequest procReq = new CreateProcessRequest(); procReq.setVariables(processVariables); - HttpEntity prcReq = - new HttpEntity(mapper.writeValueAsString(procReq), headers); + HttpEntity prcReq = new HttpEntity(mapper.writeValueAsString(procReq), headers); ResponseEntity wrsp = getOAuth2RestTemplate().postForEntity( - getAPIContextURL() + "/engine-rest/process-definition/key/CC_Process/start", prcReq, String.class); + getAPIContextURL() + "/engine-rest-ext/process-definition/key/CC_Process/start", prcReq, String.class); Map responseMap = mapper.readValue(wrsp.getBody(), HashMap.class); LOGGER.info("Response Map post instance creation-------->" + responseMap); - String instanceId = responseMap != null && responseMap.containsKey("id") ? String.valueOf(responseMap.get("id")) : null; + String instanceId = responseMap != null && responseMap.containsKey("id") ? String.valueOf(responseMap.get("id")) + : null; if (StringUtils.isNotBlank(instanceId)) { return true; } return false; } - private OAuth2RestTemplate getOAuth2RestTemplate() { - ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails (); + ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails(); resourceDetails.setClientId(clientCredentialProperties.getProperty("registration.keycloak.client-id")); resourceDetails.setClientSecret(clientCredentialProperties.getProperty("registration.keycloak.client-secret")); resourceDetails.setAccessTokenUri(clientCredentialProperties.getProperty("registration.keycloak.token-uri")); @@ -180,35 +183,36 @@ private OAuth2RestTemplate getOAuth2RestTemplate() { return new OAuth2RestTemplate(resourceDetails); } - private Map prepareRequestVariableMap(String formXML) throws IOException { - Map variables = new HashMap<>(); - if(StringUtils.isNotBlank(formXML)) { + private Map prepareRequestVariableMap(String formXML) throws IOException { + Map variables = new HashMap<>(); + if (StringUtils.isNotBlank(formXML)) { XmlMapper xmlMapper = new XmlMapper(); JsonNode node = xmlMapper.readTree(formXML.getBytes()); ObjectMapper mapper = new ObjectMapper(); - Map values = mapper.readValue(node.get("Main").toString(), HashMap.class); - for(Map.Entry entry : values.entrySet()) { - LOGGER.info("KEY: "+entry.getKey()+" VALUE : "+entry.getValue().toString()); - variables.put(entry.getKey(),new VariableData(entry.getValue())); + Map values = mapper.readValue(node.get("Main").toString(), HashMap.class); + for (Map.Entry entry : values.entrySet()) { + LOGGER.info("KEY: " + entry.getKey() + " VALUE : " + entry.getValue().toString()); + variables.put(entry.getKey(), new VariableData(entry.getValue())); } - //Inject custom attributes + // Inject custom attributes variables.put("form_key", new VariableData("CCII")); variables.put("entity_key", new VariableData("CCII")); variables.put("subprocess_entity_key", new VariableData("cciiissue")); variables.put("files_entity_key", new VariableData("cciifiles")); variables.put("submit_date_time", new VariableData(new DateTime().toString())); variables.put("entered_by", new VariableData("orbeon")); - VariableData serviceMethodData = (VariableData)variables.get("service_method"); + VariableData serviceMethodData = (VariableData) variables.get("service_method"); variables.put("engagement_source", new VariableData(serviceMethodData.getValue().toString())); - VariableData serviceChannelData = (VariableData)variables.get("service_channel"); - if(serviceChannelData.getValue().toString().equals("Service BC Location")){ + variables.put("service_channel", new VariableData("Service BC Location")); + VariableData serviceChannelData = (VariableData) variables.get("service_channel"); + if (serviceChannelData.getValue().toString().equals("Service BC Location")) { variables.put("service_location_type", new VariableData("service_centre")); - }else if(serviceChannelData.getValue().equals("Mobile Outreach Location")){ + } else if (serviceChannelData.getValue().equals("Mobile Outreach Location")) { variables.put("service_location_type", new VariableData("mobile_outreach")); } - variables.put("service_channel", new VariableData("Service BC Location")); - // Check if Orbeon is submitted with a value for "mobile-location" - if(variables.containsKey("mobile_location")) { + // variables.put("service_channel", new VariableData("Service BC Location")); + // Check if Orbeon is submitted with a value for "mobile-location" + if (variables.containsKey("mobile_location")) { // Set location parameter to "Mobile Outreach" variables.put("location", new VariableData("Mobile Outreach")); } @@ -216,46 +220,72 @@ private Map prepareRequestVariableMap(String formXML) throws IOEx return variables; } - public class CreateProcessRequest{ - Map variables; - public Map getVariables() { return variables; } - public void setVariables(Map variables) { this.variables = variables; } + public class CreateProcessRequest { + Map variables; + + public Map getVariables() { + return variables; + } + + public void setVariables(Map variables) { + this.variables = variables; + } } - public class CreateProcessMessageRequest{ + public class CreateProcessMessageRequest { private String messageName; - Map processVariables; - public String getMessageName() { return messageName; } - public void setMessageName(String messageName) { this.messageName = messageName; } - public Map getProcessVariables() { return processVariables; } - public void setProcessVariables(Map processVariables) { this.processVariables = processVariables; } + Map processVariables; + + public String getMessageName() { + return messageName; + } + + public void setMessageName(String messageName) { + this.messageName = messageName; + } + + public Map getProcessVariables() { + return processVariables; + } + + public void setProcessVariables(Map processVariables) { + this.processVariables = processVariables; + } } public class VariableData { private Object value; + VariableData(Object value) { // To handle incoming boolean parameters as string values - if(value!=null && (value.toString().toLowerCase().equals("true") || value.toString().toLowerCase().equals("false"))) { + if (value != null && (value.toString().toLowerCase().equals("true") + || value.toString().toLowerCase().equals("false"))) { this.value = Boolean.parseBoolean(value.toString()); - }else { - this.value=value; + } else { + this.value = value; } } - public Object getValue() { return value; } - public void setValue(Object value) { this.value = value; } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } } private String getAPIClientUsername() { - return StringUtils.substringBefore(StringUtils.substringBetween(appcontexturl,"://","@"),":"); + return StringUtils.substringBefore(StringUtils.substringBetween(appcontexturl, "://", "@"), ":"); } private String getAPIClientPassword() { - return StringUtils.substringAfter(StringUtils.substringBetween(appcontexturl,"://","@"),":"); + return StringUtils.substringAfter(StringUtils.substringBetween(appcontexturl, "://", "@"), ":"); } private String getAPIContextURL() { - return StringUtils.remove(StringUtils.remove(appcontexturl, StringUtils.substringBetween(appcontexturl,"://","@")),"@"); + return StringUtils.remove( + StringUtils.remove(appcontexturl, StringUtils.substringBetween(appcontexturl, "://", "@")), "@"); } - } diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/AnalyticsListener.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/AnalyticsListener.java index 891cbe67..d2cc5049 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/AnalyticsListener.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/AnalyticsListener.java @@ -12,6 +12,7 @@ import org.camunda.bpm.extension.hooks.services.analytics.SimpleDBDataPipeline; import org.glassfish.jersey.internal.util.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.camunda.bpm.extension.hooks.services.UserService; import javax.inject.Named; import java.util.HashMap; @@ -21,10 +22,11 @@ import java.util.logging.Logger; /** - * Java component for pushing the data to downstream analytics system. - * This invokes the database pipeline component for publishing data. + * Java component for pushing the data to downstream analytics system. + * This invokes the database pipeline component for publishing data. * - * Futuristic place-holder to inject the appropriate pipeline using service location pattern. + * Futuristic place-holder to inject the appropriate pipeline using service + * location pattern. * * @author sumathi.thirumani@aot-technologies.com */ @@ -34,17 +36,19 @@ public class AnalyticsListener implements TaskListener, ExecutionListener, IMess @Autowired private SimpleDBDataPipeline dbdatapipeline; + @Autowired + private UserService userService; private final Logger LOGGER = Logger.getLogger(AnalyticsListener.class.getName()); - /** * This would be invoked by all the user based task services. * Invocation point is marked with value "COMPLETE" in process diagram. + * * @param task */ @Override - public void notify(DelegateTask task) { + public void notify(DelegateTask task) { LOGGER.info("\n\n ... AnalyticsDelegate invoked by task listener for " + "processDefinitionId=" + task.getProcessDefinitionId() + ", assignee=" + task.getAssignee() @@ -53,9 +57,10 @@ public void notify(DelegateTask task) { + " \n\n"); DelegateExecution execution = task.getExecution(); try { - Map rspVariableMap = dbdatapipeline.execute(injectAdditionalProcessingFields(execution,execution.getVariables())); + Map rspVariableMap = dbdatapipeline + .execute(injectAdditionalProcessingFields(execution, execution.getVariables())); if (IDataPipeline.ResponseStatus.FAILURE.name().equals(rspVariableMap.get("code"))) { - notifyForAttention(execution,rspVariableMap); + notifyForAttention(execution, rspVariableMap); } } catch (Exception e) { LOGGER.warning("Unable to insert record into analytics DB"); @@ -67,6 +72,7 @@ public void notify(DelegateTask task) { /** * This would be invoked during form submission. * Invocation point is marked with value "END" in process diagram. + * * @param execution * @throws Exception */ @@ -78,9 +84,10 @@ public void notify(DelegateExecution execution) throws Exception { + ", variables=" + execution.getVariables() + " \n\n"); try { - Map rspVariableMap = dbdatapipeline.execute(injectAdditionalProcessingFields(execution,execution.getVariables())); + Map rspVariableMap = dbdatapipeline + .execute(injectAdditionalProcessingFields(execution, execution.getVariables())); if (IDataPipeline.ResponseStatus.FAILURE.name().equals(rspVariableMap.get("code"))) { - notifyForAttention(execution,rspVariableMap); + notifyForAttention(execution, rspVariableMap); } } catch (Exception e) { LOGGER.warning("Unable to insert record into analytics DB"); @@ -91,66 +98,83 @@ public void notify(DelegateExecution execution) throws Exception { /** * This method is intended to inject additional fields for processing. - * Injected Fields : File Name, MimeType, Size, Pid. + * Injected Fields : File Name, MimeType, Size, Pid. + * * @param execution * @param rawMap * @return */ - private Map injectAdditionalProcessingFields(DelegateExecution execution,Map rawMap) { - Map variables = injectPrimaryKey(execution,rawMap); - Map prcMap = new HashMap<>(); + private Map injectAdditionalProcessingFields(DelegateExecution execution, + Map rawMap) { + Map variables = injectPrimaryKey(execution, rawMap); + Map prcMap = new HashMap<>(); String pid = execution.getId(); try { // Handles file & authenticated user information. - for(Map.Entry entry : variables.entrySet()) { - if(entry.getKey().endsWith("_idir")) { + for (Map.Entry entry : variables.entrySet()) { + if (entry.getKey().endsWith("_idir")) { String idir = entry.getValue() != null ? String.valueOf(entry.getValue()) : null; if (StringUtils.isNotEmpty(idir)) { - prcMap.put(entry.getKey(),entry.getValue()); - if(!execution.getVariables().containsKey(StringUtils.substringBefore(entry.getKey(), "_idir").concat("_name"))) { - String idirName = getName(execution, variables.get("provider_idir_userid").toString()); - execution.setVariable(StringUtils.substringBefore(entry.getKey(), "_idir").concat("_name"), idirName); - prcMap.put(StringUtils.substringBefore(entry.getKey(), "_idir").concat("_name"),idirName); + prcMap.put(entry.getKey(), entry.getValue()); + if (!execution.getVariables() + .containsKey(StringUtils.substringBefore(entry.getKey(), "_idir").concat("_name"))) { + String idirName = getName(execution, userService, + idir); + execution.setVariable(StringUtils.substringBefore(entry.getKey(), "_idir").concat("_name"), + idirName); + prcMap.put(StringUtils.substringBefore(entry.getKey(), "_idir").concat("_name"), idirName); } } - } else if(!StringUtils.endsWith(entry.getKey(),"_file")) { - prcMap.put(entry.getKey(),entry.getValue()); + } else if (!StringUtils.endsWith(entry.getKey(), "_file")) { + LOGGER.info("ANALYTICS_VARIABLES" + + "key=" + entry.getKey() + + ", value=" + entry.getValue()); + prcMap.put(entry.getKey(), entry.getValue()); } // Commenting out the file handling for analytics listener. /** - if(StringUtils.endsWith(entry.getKey(),"_file")) { - if(!execution.getVariables().containsKey(StringUtils.substringBefore(entry.getKey(),"_file").concat("_stream_id"))) { - String filePrefix = StringUtils.substringBefore(entry.getKey(), "_file"); - FileValue retrievedTypedFileValue = execution.getProcessEngineServices().getRuntimeService().getVariableTyped(pid, entry.getKey()); - if (retrievedTypedFileValue != null && retrievedTypedFileValue.getValue() != null) { - InputStream fileContent = retrievedTypedFileValue.getValue(); - String fileName = retrievedTypedFileValue.getFilename(); - String mimeType = retrievedTypedFileValue.getMimeType(); - byte[] fileBytes = IOUtils.toByteArray(fileContent); - int fileSize = fileBytes.length; - if (StringUtils.isNotEmpty(fileName) && fileSize > 0) { - prcMap.put(filePrefix.concat("_name"), fileName); - prcMap.put(filePrefix.concat("_mimetype"), mimeType); - prcMap.put(entry.getKey(), fileBytes); - prcMap.put(filePrefix.concat("_size"), fileSize); - String fileId = getUniqueIdentifierForFile(); - prcMap.put(filePrefix.concat("_stream_id"), fileId); - execution.setVariable(filePrefix.concat("_stream_id"), fileId); - } - } - } - } else if(entry.getKey().endsWith("_idir")) { - String idir = entry.getValue() != null ? String.valueOf(entry.getValue()) : null; - if (StringUtils.isNotEmpty(idir) && - !execution.getVariables().containsKey(StringUtils.substringBefore(entry.getKey(), "_idir").concat("_name"))) { - String idirName = getName(execution, idir); - execution.setVariable(StringUtils.substringBefore(entry.getKey(), "_idir").concat("_name"), idirName); - prcMap.put(entry.getKey(),entry.getValue()); - prcMap.put(StringUtils.substringBefore(entry.getKey(), "_idir").concat("_name"),idirName); - } - } else { - prcMap.put(entry.getKey(),entry.getValue()); - } */ + * if(StringUtils.endsWith(entry.getKey(),"_file")) { + * if(!execution.getVariables().containsKey(StringUtils.substringBefore(entry.getKey(),"_file").concat("_stream_id"))) + * { + * String filePrefix = StringUtils.substringBefore(entry.getKey(), "_file"); + * FileValue retrievedTypedFileValue = + * execution.getProcessEngineServices().getRuntimeService().getVariableTyped(pid, + * entry.getKey()); + * if (retrievedTypedFileValue != null && retrievedTypedFileValue.getValue() != + * null) { + * InputStream fileContent = retrievedTypedFileValue.getValue(); + * String fileName = retrievedTypedFileValue.getFilename(); + * String mimeType = retrievedTypedFileValue.getMimeType(); + * byte[] fileBytes = IOUtils.toByteArray(fileContent); + * int fileSize = fileBytes.length; + * if (StringUtils.isNotEmpty(fileName) && fileSize > 0) { + * prcMap.put(filePrefix.concat("_name"), fileName); + * prcMap.put(filePrefix.concat("_mimetype"), mimeType); + * prcMap.put(entry.getKey(), fileBytes); + * prcMap.put(filePrefix.concat("_size"), fileSize); + * String fileId = getUniqueIdentifierForFile(); + * prcMap.put(filePrefix.concat("_stream_id"), fileId); + * execution.setVariable(filePrefix.concat("_stream_id"), fileId); + * } + * } + * } + * } else if(entry.getKey().endsWith("_idir")) { + * String idir = entry.getValue() != null ? String.valueOf(entry.getValue()) : + * null; + * if (StringUtils.isNotEmpty(idir) && + * !execution.getVariables().containsKey(StringUtils.substringBefore(entry.getKey(), + * "_idir").concat("_name"))) { + * String idirName = getName(execution, idir); + * execution.setVariable(StringUtils.substringBefore(entry.getKey(), + * "_idir").concat("_name"), idirName); + * prcMap.put(entry.getKey(),entry.getValue()); + * prcMap.put(StringUtils.substringBefore(entry.getKey(), + * "_idir").concat("_name"),idirName); + * } + * } else { + * prcMap.put(entry.getKey(),entry.getValue()); + * } + */ } } catch (Exception e) { e.printStackTrace(); @@ -160,50 +184,54 @@ private Map injectAdditionalProcessingFields(DelegateExecution ex /** * Evaluate this being injected from process diagram. + * * @return */ - private Map injectPrimaryKey(DelegateExecution execution,Map variables) { - if(execution.getVariables().containsKey("feature_by") && "task".equals(String.valueOf(execution.getVariable("feature_by")))) { - variables.put("pid",execution.getVariable("pid")); + private Map injectPrimaryKey(DelegateExecution execution, Map variables) { + if (execution.getVariables().containsKey("feature_by") + && "task".equals(String.valueOf(execution.getVariable("feature_by")))) { + variables.put("pid", execution.getVariable("pid")); } else { - if(!variables.containsKey("pid")) { + if (!variables.containsKey("pid")) { variables.put("pid", execution.getProcessInstanceId()); } } return variables; } - private void notifyForAttention(DelegateExecution execution, Exception exception){ - Map variables = new HashMap<>(); + private void notifyForAttention(DelegateExecution execution, Exception exception) { + Map variables = new HashMap<>(); try { - Map exVarMap = new HashMap<>(); - //Additional Response Fields - BEGIN - exVarMap.put("pid",execution.getId()); - exVarMap.put("subject",("Exception for ".concat(execution.getId()))); - exVarMap.put("category","analytics_service_exception"); - //Additional Response Fields - END - sendMessage(execution,exVarMap,getMessageName()); + Map exVarMap = new HashMap<>(); + // Additional Response Fields - BEGIN + exVarMap.put("pid", execution.getId()); + exVarMap.put("subject", ("Exception for ".concat(execution.getId()))); + exVarMap.put("category", "analytics_service_exception"); + // Additional Response Fields - END + sendMessage(execution, exVarMap, getMessageName()); LOGGER.info("\n\nMessage sent! " + "\n\n"); } catch (Exception ex) { - LOGGER.log(Level.SEVERE,"Exception occurred:"+ ExceptionUtils.exceptionStackTraceAsString(ex)); + LOGGER.log(Level.SEVERE, "Exception occurred:" + ExceptionUtils.exceptionStackTraceAsString(ex)); } } - private void notifyForAttention(DelegateExecution execution,Map rspVariableMap){ - if(IDataPipeline.ResponseStatus.FAILURE.name().equals(rspVariableMap.get("code"))) { - Map exVarMap = new HashMap<>(); - //Additional Response Fields - BEGIN - exVarMap.put("pid",execution.getId()); - exVarMap.put("subject",(String.valueOf(rspVariableMap.get("message")).concat(" for ").concat(execution.getId()))); - exVarMap.put("category","analytics_service_exception"); - for(Map.Entry entry : rspVariableMap.entrySet()) { - if(StringUtils.startsWith(entry.getKey(),"exception")) { - StringValue exceptionDataValue = Variables.stringValue(String.valueOf(rspVariableMap.get("exception")),true); - exVarMap.put(entry.getKey(),exceptionDataValue); + private void notifyForAttention(DelegateExecution execution, Map rspVariableMap) { + if (IDataPipeline.ResponseStatus.FAILURE.name().equals(rspVariableMap.get("code"))) { + Map exVarMap = new HashMap<>(); + // Additional Response Fields - BEGIN + exVarMap.put("pid", execution.getId()); + exVarMap.put("subject", + (String.valueOf(rspVariableMap.get("message")).concat(" for ").concat(execution.getId()))); + exVarMap.put("category", "analytics_service_exception"); + for (Map.Entry entry : rspVariableMap.entrySet()) { + if (StringUtils.startsWith(entry.getKey(), "exception")) { + StringValue exceptionDataValue = Variables + .stringValue(String.valueOf(rspVariableMap.get("exception")), true); + exVarMap.put(entry.getKey(), exceptionDataValue); } } - //Additional Response Fields - END - sendMessage(execution,exVarMap,getMessageName()); + // Additional Response Fields - END + sendMessage(execution, exVarMap, getMessageName()); LOGGER.info("\n\nMessage sent! " + "\n\n"); } } @@ -212,7 +240,7 @@ private String getUniqueIdentifierForFile() { return UUID.randomUUID().toString(); } - private String getMessageName(){ + private String getMessageName() { return "Service_Api_Message_Email"; } } diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/BPMFormDataPipelineListener.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/BPMFormDataPipelineListener.java index 05b0ae87..07f34a18 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/BPMFormDataPipelineListener.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/BPMFormDataPipelineListener.java @@ -1,6 +1,5 @@ package org.camunda.bpm.extension.hooks.listeners; - import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; @@ -10,46 +9,47 @@ import org.camunda.bpm.engine.delegate.TaskListener; import org.camunda.bpm.engine.delegate.DelegateTask; import org.camunda.bpm.extension.commons.connector.HTTPServiceInvoker; - import org.camunda.bpm.extension.hooks.exceptions.FormioServiceException; import org.camunda.bpm.extension.hooks.listeners.data.FormElement; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; - import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; - +import javax.annotation.Resource; import javax.inject.Named; - import java.io.IOException; import java.util.ArrayList; import java.util.List; - -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.Map; +import java.util.Properties; +import java.util.HashMap; +import static org.camunda.bpm.extension.commons.utils.VariableConstants.FORM_URL; /** - * This class transforms all the form document data into CAM variables - * - * @author sumathi.thirumani@aot-technologies.com + * BPM Form Data Pipeline Listener. + * This class copies all the CAM variables into form document data. */ @Named("BPMFormDataPipelineListener") public class BPMFormDataPipelineListener extends BaseListener implements TaskListener, ExecutionListener { - - private final Logger LOGGER = Logger.getLogger(BPMFormDataPipelineListener.class.getName()); - + // private final Logger LOGGER = + // Logger.getLogger(BPMFormDataPipelineListener.class.getName()); + private final Logger LOGGER = LoggerFactory.getLogger(BPMFormDataPipelineListener.class); private Expression fields; - + @Resource(name = "bpmObjectMapper") + private ObjectMapper bpmObjectMapper; @Autowired private HTTPServiceInvoker httpServiceInvoker; + @Autowired + private Properties integrationCredentialProperties; @Override public void notify(DelegateExecution execution) { try { patchFormAttributes(execution); } catch (IOException e) { - handleException(execution,ExceptionSource.EXECUTION, e); + handleException(execution, ExceptionSource.EXECUTION, e); } } @@ -63,38 +63,58 @@ public void notify(DelegateTask delegateTask) { } private void patchFormAttributes(DelegateExecution execution) throws IOException { - String formUrl= MapUtils.getString(execution.getVariables(),"formUrl", null); - if(StringUtils.isBlank(formUrl)) { - LOGGER.log(Level.SEVERE,"Unable to read submission for "+execution.getVariables().get("formUrl")); - return; - } - ResponseEntity response = httpServiceInvoker.execute(getUrl(execution), HttpMethod.PATCH, getModifiedFormElements(execution)); - if(response.getStatusCodeValue() != HttpStatus.OK.value()) { - throw new FormioServiceException("Unable to get patch values for: "+ formUrl+ ". Message Body: " + - response.getBody()); + String formUrl = MapUtils.getString(execution.getVariables(), FORM_URL, null); + ResponseEntity response = null; + Boolean enableCustomSubmission = Boolean + .valueOf(integrationCredentialProperties.getProperty("forms.enableCustomSubmission")); + if (StringUtils.isBlank(formUrl)) { + LOGGER.error("Unable to read submission for Empty Url string"); + } else { + if (enableCustomSubmission) { + // Form submission data to custom data store using custom url. + response = httpServiceInvoker.execute(getUrl(execution), HttpMethod.PATCH, + getModifiedFormElementsCustomSubmission(execution)); + } else { + response = httpServiceInvoker.execute(getUrl(execution), HttpMethod.PATCH, + getModifiedFormElements(execution)); + } + if (response.getStatusCodeValue() != HttpStatus.OK.value()) { + throw new FormioServiceException("Unable to get patch values for: " + formUrl + ". Message Body: " + + response.getBody()); + } } } - - private String getUrl(DelegateExecution execution){ - return String.valueOf(execution.getVariables().get("formUrl")); + private String getUrl(DelegateExecution execution) { + return String.valueOf(execution.getVariables().get(FORM_URL)); } private List getModifiedFormElements(DelegateExecution execution) throws IOException { List elements = new ArrayList<>(); - ObjectMapper objectMapper = new ObjectMapper(); - List injectableFields = this.fields != null && this.fields.getValue(execution) != null ? - objectMapper.readValue(String.valueOf(this.fields.getValue(execution)),List.class): null; + List injectableFields = this.fields != null && this.fields.getValue(execution) != null + ? bpmObjectMapper.readValue(String.valueOf(this.fields.getValue(execution)), List.class) + : null; LOGGER.info("Invoking BPMFormDataPipelineListener for applicationId::" + execution.getVariables().get("applicationId") + " process_pid::" + execution.getVariables().get("process_pid")); - for(String entry: injectableFields) { - elements.add(new FormElement(entry,String.valueOf(execution.getVariable(entry)))); + for (String entry : injectableFields) { + elements.add(new FormElement(entry, String.valueOf(execution.getVariable(entry)))); } - return elements; } - -} + private Map> getModifiedFormElementsCustomSubmission(DelegateExecution execution) + throws IOException { + Map paramMap = new HashMap<>(); + Map> dataMap = new HashMap<>(); + List injectableFields = this.fields != null && this.fields.getValue(execution) != null + ? bpmObjectMapper.readValue(String.valueOf(this.fields.getValue(execution)), List.class) + : null; + for (String entry : injectableFields) { + paramMap.put(entry, String.valueOf(execution.getVariable(entry))); + } + dataMap.put("data", paramMap); + return dataMap; + } +} \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/execution/ExternalSubmissionListener.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/execution/ExternalSubmissionListener.java index 3de3e36f..4d624be7 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/execution/ExternalSubmissionListener.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/execution/ExternalSubmissionListener.java @@ -18,15 +18,20 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import javax.annotation.Resource; import javax.inject.Named; - import java.io.IOException; import java.util.HashMap; import java.util.Map; +import static org.camunda.bpm.extension.commons.utils.VariableConstants.FORM_URL; +import static org.camunda.bpm.extension.commons.utils.VariableConstants.APPLICATION_ID; + /** - * This listener supports creation of form.io submission for instances created from external system + * This class supports creation of submission for instances created from + * external system + * * @author sumathi.thirumani@aot-technologies.com */ @Named("ExternalSubmissionListener") @@ -36,69 +41,70 @@ public class ExternalSubmissionListener extends BaseListener implements Executio @Autowired private FormSubmissionService formSubmissionService; - + @Resource(name = "bpmObjectMapper") + private ObjectMapper bpmObjectMapper; @Autowired private HTTPServiceInvoker httpServiceInvoker; private Expression formName; - /** - * Communicates with the form.io system to create a submission - * Also, if an applicationId is not available, an applicationId will be created - * and the same will be communicated to the form.io submission. - * - * @param execution The current execution instance - */ @Override public void notify(DelegateExecution execution) { try { String formUrl = getFormUrl(execution); - String submissionId = formSubmissionService.createSubmission(formUrl, formSubmissionService.createFormSubmissionData(execution.getVariables())); + String submissionId = formSubmissionService.createSubmission(formUrl, + formSubmissionService.createFormSubmissionData(execution.getVariables())); LOGGER.info("Creating submission::" + submissionId); - if(StringUtils.isNotBlank(submissionId)){ - execution.setVariable("formUrl", formUrl+"/"+submissionId); - if(execution.getVariable("applicationId") == null) { + if (StringUtils.isNotBlank(submissionId)) { + execution.setVariable(FORM_URL, formUrl + "/" + submissionId); + if (execution.getVariable("applicationId") == null) { createApplication(execution, true); - + // To update submissionId with applicationId - formSubmissionService.updateSubmission(formUrl+"/"+submissionId, formSubmissionService.createFormSubmissionData(execution.getVariables())); + formSubmissionService.updateSubmission(formUrl + "/" + submissionId, + formSubmissionService.createFormSubmissionData(execution.getVariables())); } } - } catch(IOException | RuntimeException ex) { + } catch (IOException | RuntimeException ex) { handleException(execution, ExceptionSource.EXECUTION, ex); } } private String getFormId(DelegateExecution execution) throws IOException { - String formName = String.valueOf(this.formName.getValue(execution)); - return formSubmissionService.getFormIdByName(httpServiceInvoker.getProperties().getProperty("formio.url")+"/"+formName); + String formName = String.valueOf(this.formName.getValue(execution)); + return formSubmissionService + .getFormIdByName(httpServiceInvoker.getProperties().getProperty("formio.url") + "/" + formName); } private String getFormUrl(DelegateExecution execution) throws IOException { - return httpServiceInvoker.getProperties().getProperty("formio.url")+"/form/"+getFormId(execution)+"/submission"; + return httpServiceInvoker.getProperties().getProperty("formio.url") + "/form/" + getFormId(execution) + + "/submission"; } /** * * @param execution - DelegateExecution data - * @param retryOnce - If formsflow api failed to respond 201 then the application will try once again and then it fail. + * @param retryOnce - If formsflow api failed to respond 201 then the + * application will try once again and then it fail. * @throws JsonProcessingException */ private void createApplication(DelegateExecution execution, boolean retryOnce) throws JsonProcessingException { - Map data = new HashMap<>(); - String formUrl = String.valueOf(execution.getVariable("formUrl")); - data.put("formUrl",formUrl); - data.put("formId",StringUtils.substringBetween(formUrl, "/form/", "/submission/")); - data.put("submissionId",StringUtils.substringAfter(formUrl, "/submission/")); - data.put("processInstanceId",execution.getProcessInstanceId()); - ResponseEntity response = httpServiceInvoker.execute(httpServiceInvoker.getProperties().getProperty("api.url")+"/application/create", HttpMethod.POST, getObjectMapper().writeValueAsString(data)); - if(response.getStatusCode().value() == HttpStatus.CREATED.value()) { - JsonNode jsonNode = getObjectMapper().readTree(response.getBody()); + Map data = new HashMap<>(); + String formUrl = String.valueOf(execution.getVariable(FORM_URL)); + data.put(FORM_URL, formUrl); + data.put("formId", StringUtils.substringBetween(formUrl, "/form/", "/submission/")); + data.put("submissionId", StringUtils.substringAfter(formUrl, "/submission/")); + data.put("processInstanceId", execution.getProcessInstanceId()); + ResponseEntity response = httpServiceInvoker.execute( + httpServiceInvoker.getProperties().getProperty("api.url") + "/application/create", HttpMethod.POST, + bpmObjectMapper.writeValueAsString(data)); + if (response.getStatusCode().value() == HttpStatus.CREATED.value()) { + JsonNode jsonNode = bpmObjectMapper.readTree(response.getBody()); String applicationId = jsonNode.get("id").asText(); - execution.setVariable("applicationId", applicationId); + execution.setVariable(APPLICATION_ID, applicationId); } else { - if(retryOnce) { + if (retryOnce) { LOGGER.warn("Retrying the application create once more due to previous failure"); createApplication(execution, false); } else { @@ -107,9 +113,4 @@ private void createApplication(DelegateExecution execution, boolean retryOnce) t } } } - - private ObjectMapper getObjectMapper(){ - return new ObjectMapper(); - } - } \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/execution/GroupNotifyListener.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/execution/GroupNotifyListener.java index 4bf05a0c..4c22cd0f 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/execution/GroupNotifyListener.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/execution/GroupNotifyListener.java @@ -8,6 +8,7 @@ import org.camunda.bpm.extension.hooks.services.IMessageEvent; import org.springframework.stereotype.Component; +import javax.annotation.Resource; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -19,7 +20,8 @@ * Assignment Task Listener to start a message event when a task is created * Extended to emails to additional groups of interest. * - * @author yichun.zhao@aot-technologies.com, sumathi.thirumani@aot-technologies.com + * @author yichun.zhao@aot-technologies.com, + * sumathi.thirumani@aot-technologies.com */ @Component public class GroupNotifyListener implements ExecutionListener, IMessageEvent { @@ -31,6 +33,9 @@ public class GroupNotifyListener implements ExecutionListener, IMessageEvent { private Expression emailGroups; + @Resource(name = "bpmObjectMapper") + private ObjectMapper bpmObjectMapper; + /** * This provides the necessary information to send message. * @@ -38,37 +43,38 @@ public class GroupNotifyListener implements ExecutionListener, IMessageEvent { */ @Override public void notify(DelegateExecution execution) throws Exception { - List toEmails = new ArrayList<>(); + List toEmails = new ArrayList<>(); if (CollectionUtils.isNotEmpty(getEmailGroups(execution))) { for (String entry : getEmailGroups(execution)) { toEmails.addAll(getEmailsForGroup(execution, entry)); } } - sendEmailNotification(execution, toEmails ,String.valueOf(execution.getVariables().get("taskId"))); - + sendEmailNotification(execution, toEmails, String.valueOf(execution.getVariables().get("taskId"))); } + /** * * @param execution * @param toEmails * @param taskId */ - private void sendEmailNotification(DelegateExecution execution,List toEmails,String taskId) { - String toAddress = CollectionUtils.isNotEmpty(toEmails) ? StringUtils.join(toEmails,",") : null; - if(StringUtils.isNotEmpty(toAddress)) { + private void sendEmailNotification(DelegateExecution execution, List toEmails, String taskId) { + String toAddress = CollectionUtils.isNotEmpty(toEmails) ? StringUtils.join(toEmails, ",") : null; + if (StringUtils.isNotEmpty(toAddress)) { Map emailAttributes = new HashMap<>(); emailAttributes.put("email_to", toAddress); emailAttributes.put("category", getCategory(execution)); - emailAttributes.put("name",getDefaultAddresseName()); - emailAttributes.put("taskid",taskId); - if(StringUtils.isNotBlank(toAddress) && StringUtils.indexOf(toAddress,"@") > 0) { - sendMessage(execution, emailAttributes,getMessageId(execution)); + emailAttributes.put("name", getDefaultAddresseName()); + emailAttributes.put("taskid", taskId); + if (StringUtils.isNotBlank(toAddress) && StringUtils.indexOf(toAddress, "@") > 0) { + sendMessage(execution, emailAttributes, getMessageId(execution)); } } } - private String getCategory(DelegateExecution delegateExecution){ + + private String getCategory(DelegateExecution delegateExecution) { return String.valueOf(this.category.getValue(delegateExecution)); } @@ -77,31 +83,32 @@ private String getCategory(DelegateExecution delegateExecution){ * @param delegateExecution * @return */ - private String getMessageId(DelegateExecution delegateExecution){ + private String getMessageId(DelegateExecution delegateExecution) { return String.valueOf(this.messageId.getValue(delegateExecution)); } - private List getEmailGroups(DelegateExecution delegateExecution){ + private List getEmailGroups(DelegateExecution delegateExecution) { List emailGroups = new ArrayList<>(); try { - if(this.emailGroups != null && + if (this.emailGroups != null && StringUtils.isNotBlank(String.valueOf(this.emailGroups.getValue(delegateExecution)))) { - emailGroups = this.emailGroups != null && this.emailGroups.getValue(delegateExecution) != null ? - getObjectMapper().readValue(String.valueOf(this.emailGroups.getValue(delegateExecution)), List.class) : null; + emailGroups = this.emailGroups != null && this.emailGroups.getValue(delegateExecution) != null + ? bpmObjectMapper.readValue(String.valueOf(this.emailGroups.getValue(delegateExecution)), + List.class) + : null; } } catch (JsonProcessingException e) { - LOGGER.log(Level.SEVERE, "Exception occured in reading additionalEmailGroups" , e); + LOGGER.log(Level.SEVERE, "Exception occured in reading additionalEmailGroups", e); } - return emailGroups; + return emailGroups; } /** * Returns Object Mapper Instance + * * @return */ - private ObjectMapper getObjectMapper(){ - return new ObjectMapper(); - } - + // private ObjectMapper getObjectMapper() { + // return new ObjectMapper(); } } diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/task/AccessGrantNotifyListener.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/task/AccessGrantNotifyListener.java index 0fcd09fd..0446a88b 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/task/AccessGrantNotifyListener.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/task/AccessGrantNotifyListener.java @@ -19,9 +19,10 @@ import java.util.logging.Logger; /** + * Access Grant Notify Listener. * This component is aimed at sending notification to Access Groups * - * @author sumathi.thirumani@aot-technologies.com + * @author sumathi.thirumani@aot-technologies.com */ @Component public class AccessGrantNotifyListener implements TaskListener, IMessageEvent { @@ -29,7 +30,6 @@ public class AccessGrantNotifyListener implements TaskListener, IMessageEvent { private Expression excludeGroup; private Expression messageId; - private static final Logger LOGGER = Logger.getLogger(AccessGrantNotifyListener.class.getName()); private Expression category; @@ -42,15 +42,17 @@ public class AccessGrantNotifyListener implements TaskListener, IMessageEvent { public void notify(DelegateTask delegateTask) { LOGGER.info("\n\nAccessGrantNotify listener invoked! " + delegateTask.getId()); List notifyGroup = new ArrayList<>(); - String excludeGroupValue = this.excludeGroup != null && this.excludeGroup.getValue(delegateTask.getExecution()) != null ? - String.valueOf(this.excludeGroup.getValue(delegateTask.getExecution())) : null; + String excludeGroupValue = this.excludeGroup != null + && this.excludeGroup.getValue(delegateTask.getExecution()) != null + ? String.valueOf(this.excludeGroup.getValue(delegateTask.getExecution())) + : null; List exclusionGroupList = new ArrayList<>(); LOGGER.info("Excluded group::" + excludeGroupValue); - if(StringUtils.isNotBlank(excludeGroupValue)) { - exclusionGroupList.add(excludeGroupValue.trim()); + if (StringUtils.isNotBlank(excludeGroupValue)) { + exclusionGroupList.add(excludeGroupValue.strip()); } List accessGroupList = getModifiedGroupsForTask(delegateTask, exclusionGroupList); - String accessGroupListString = String.join("|",accessGroupList); + String accessGroupListString = String.join("|", accessGroupList); for (String entry : accessGroupList) { List emailsForGroup = getEmailsForGroup(delegateTask.getExecution(), entry); notifyGroup.addAll(emailsForGroup); @@ -58,7 +60,8 @@ public void notify(DelegateTask delegateTask) { if (isNotify(delegateTask) && StringUtils.isBlank(delegateTask.getAssignee())) { if (CollectionUtils.isNotEmpty(notifyGroup)) { LOGGER.info("Sending an email to ::" + accessGroupListString); - sendEmailNotification(delegateTask.getExecution(), notifyGroup, delegateTask.getId(), getCategory(delegateTask.getExecution())); + sendEmailNotification(delegateTask.getExecution(), notifyGroup, delegateTask.getId(), + getCategory(delegateTask.getExecution())); delegateTask.getExecution().removeVariable("isNotify"); } } @@ -66,6 +69,7 @@ public void notify(DelegateTask delegateTask) { /** * Check if the current update event is a result of Notify action + * * @param delegateTask The current task in context * @return true - if the update event is a result of Notify; false - else */ @@ -76,23 +80,25 @@ private boolean isNotify(DelegateTask delegateTask) { /** * Sends an email. + * * @param execution The current execution instance. - * @param toEmails The recipients. - * @param taskId The task id. - * @param category The email category for the DMN. + * @param toEmails The recipients. + * @param taskId The task id. + * @param category The email category for the DMN. */ - private void sendEmailNotification(DelegateExecution execution, List toEmails, String taskId, String category) { + private void sendEmailNotification(DelegateExecution execution, List toEmails, String taskId, + String category) { Set emails = new HashSet<>(toEmails); - String toAddress = CollectionUtils.isNotEmpty(toEmails) ? StringUtils.join(emails,",") : null; - if(StringUtils.isNotEmpty(toAddress)) { + String toAddress = CollectionUtils.isNotEmpty(toEmails) ? StringUtils.join(emails, ",") : null; + if (StringUtils.isNotEmpty(toAddress)) { Map emailAttributes = new HashMap<>(); emailAttributes.put("to", toAddress); emailAttributes.put("category", category); - emailAttributes.put("name",getDefaultAddresseName()); - emailAttributes.put("taskid",taskId); + emailAttributes.put("name", getDefaultAddresseName()); + emailAttributes.put("taskid", taskId); log.info("Inside notify attributes:" + emailAttributes); - if(StringUtils.isNotBlank(toAddress) && StringUtils.indexOf(toAddress,"@") > 0) { - sendMessage(execution, emailAttributes,getMessageId(execution)); + if (StringUtils.isNotBlank(toAddress) && StringUtils.indexOf(toAddress, "@") > 0) { + sendMessage(execution, emailAttributes, getMessageId(execution)); } } } @@ -101,12 +107,12 @@ private void sendEmailNotification(DelegateExecution execution, List toE * @param execution The current execution instance * @return Returns the message category */ - private String getCategory(DelegateExecution execution){ + private String getCategory(DelegateExecution execution) { return String.valueOf(this.category.getValue(execution)); } /** - * @param delegateTask The task instance to send an email for. + * @param delegateTask The task instance to send an email for. * @param exclusionGroup The groups to be excluded from the emails. * @return The list of groups after removing the excluded groups. */ @@ -115,9 +121,9 @@ private List getModifiedGroupsForTask(DelegateTask delegateTask, List newGroupsAdded = new ArrayList<>(); if (CollectionUtils.isNotEmpty(identityLinks)) { for (IdentityLink entry : identityLinks) { - String grpId = entry.getGroupId().trim(); + String grpId = entry.getGroupId().strip(); if (!exclusionGroup.contains(grpId)) { - newGroupsAdded.add(entry.getGroupId().trim()); + newGroupsAdded.add(entry.getGroupId().strip()); } } } @@ -125,15 +131,17 @@ private List getModifiedGroupsForTask(DelegateTask delegateTask, List superVariables = new HashMap<>(); - List supFields = this.fields != null && this.fields.getValue(delegateTask) != null ? - objectMapper.readValue(String.valueOf(this.fields.getValue(delegateTask)),List.class): null; - for(String entry : supFields) { + Map superVariables = new HashMap<>(); + List supFields = this.fields != null && this.fields.getValue(delegateTask) != null + ? bpmObjectMapper.readValue(String.valueOf(this.fields.getValue(delegateTask)), List.class) + : null; + + for (String entry : supFields) { superVariables.put(entry, delegateTask.getExecution().getVariables().get(entry)); } LOGGER.info("Invoking FormConnectorListener for applicationId::" + delegateTask.getExecution().getVariables().get("applicationId") + " process_pid::" + delegateTask.getExecution().getVariables().get("process_pid")); - return formSubmissionService.createSubmission(targetFormUrl, createFormSubmissionData(submission, superVariables, getPropogateData(delegateTask))); + + return formSubmissionService.createSubmission(targetFormUrl, + createFormSubmissionData(submission, superVariables, getPropogateData(delegateTask))); } /** - * + * * @param submission * @param superVariables * @param propogateData * @return */ - private String createFormSubmissionData(String submission, Map superVariables, String propogateData) { + private String createFormSubmissionData(String submission, Map superVariables, + String propogateData) { try { - JsonNode submissionNode = getObjectMapper().readTree(submission); - JsonNode dataNode =submissionNode.get("data"); - if("Y".equals(propogateData)) { - for(Map.Entry entry : superVariables.entrySet()) { - ((ObjectNode)dataNode).put(entry.getKey(), getObjectMapper().convertValue(entry.getValue(), JsonNode.class)); + JsonNode submissionNode = bpmObjectMapper.readTree(submission); + JsonNode dataNode = submissionNode.get("data"); + if ("Y".equals(propogateData)) { + for (Map.Entry entry : superVariables.entrySet()) { + ((ObjectNode) dataNode).put(entry.getKey(), + bpmObjectMapper.convertValue(entry.getValue(), JsonNode.class)); } - return getObjectMapper().writeValueAsString(new FormSubmission(dataNode)); + return bpmObjectMapper.writeValueAsString(new FormSubmission(dataNode)); } else { - return getObjectMapper().writeValueAsString(new FormSubmission(getObjectMapper().convertValue(superVariables, JsonNode.class))); + return bpmObjectMapper.writeValueAsString( + new FormSubmission(bpmObjectMapper.convertValue(superVariables, JsonNode.class))); } - } catch (JsonProcessingException e) { e.printStackTrace(); } - return null; + return null; } /** * Get the formID for associated name. - * + * * @param delegateTask * @return */ @@ -119,25 +129,28 @@ private String getFormId(DelegateTask delegateTask) throws IOException { .getExtensionElements() .getElementsQuery() .filterByType(CamundaProperties.class).singleResult(); - List userTaskExtensionProperties = camundaProperties.getCamundaProperties() .stream() - .filter(camundaProperty -> - camundaProperty.getCamundaName() - .equals(getFormIdProperty())) + .filter(camundaProperty -> camundaProperty.getCamundaName() + .equals(getFormIdProperty())) .collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(userTaskExtensionProperties)) { - if(CollectionUtils.isNotEmpty(userTaskExtensionProperties)) { String formName = userTaskExtensionProperties.get(0).getCamundaValue(); - return formSubmissionService.getFormIdByName(StringUtils.substringBefore(getFormUrl(delegateTask),"/form/")+"/"+formName); + if (StringUtils.startsWith(formName, "$")) { + formName = String.valueOf( + delegateTask.getExecution().getVariable(StringUtils.substringBetween(formName, "${", "}"))); + } + return formSubmissionService + .getFormIdByName(StringUtils.substringBefore(getFormUrl(delegateTask), "/form/") + "/" + formName); } - return null; } /** * Defines the form ID property. + * * @return */ private String getFormIdProperty() { @@ -146,40 +159,33 @@ private String getFormIdProperty() { /** * Returns the data propagation property value. - * + * * @param delegateTask * @return */ - private String getPropogateData(DelegateTask delegateTask){ - if(this.copyDataIndicator != null && + private String getPropogateData(DelegateTask delegateTask) { + if (this.copyDataIndicator != null && StringUtils.isNotBlank(String.valueOf(this.copyDataIndicator.getValue(delegateTask)))) { return String.valueOf(this.copyDataIndicator.getValue(delegateTask)); } return "N"; } - /** - * Returns Object Mapper Instance - * @return - */ - private ObjectMapper getObjectMapper(){ - return new ObjectMapper(); - } - /** * Returns new URL for submission. - * + * * @param delegateTask * @return */ private String getNewFormSubmissionUrl(DelegateTask delegateTask) throws IOException { String formUrl = getFormUrl(delegateTask); - return StringUtils.replace(formUrl, StringUtils.substringBetween(formUrl, "form/", "/submission"), getFormId(delegateTask)); + return StringUtils.replace(formUrl, StringUtils.substringBetween(formUrl, "form/", "/submission"), + getFormId(delegateTask)); } /** * Returns the formURl from execution context - * + * * @param delegateTask * @return */ @@ -189,14 +195,13 @@ private String getFormUrl(DelegateTask delegateTask) { /** * Returns the updated formUrl with new submission ID. - * + * * @param delegateTask * @param submissionId * @return */ private String getModifiedFormUrl(DelegateTask delegateTask, String submissionId) throws IOException { - String formUrl = StringUtils.substringBefore(getFormUrl(delegateTask),"/form/"); - return formUrl+ "/form/" + getFormId(delegateTask) + "/submission/" + submissionId; + String formUrl = StringUtils.substringBefore(getFormUrl(delegateTask), "/form/"); + return formUrl + "/form/" + getFormId(delegateTask) + "/submission/" + submissionId; } - } \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/task/TimeoutNotifyListener.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/task/TimeoutNotifyListener.java new file mode 100644 index 00000000..bd11beb2 --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/listeners/task/TimeoutNotifyListener.java @@ -0,0 +1,112 @@ +package org.camunda.bpm.extension.hooks.listeners.task; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import com.nimbusds.oauth2.sdk.util.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.camunda.bpm.engine.delegate.*; +import org.camunda.bpm.engine.identity.User; +import org.camunda.bpm.extension.hooks.listeners.BaseListener; +import org.camunda.bpm.extension.hooks.services.IMessageEvent; +import org.camunda.bpm.extension.hooks.services.UserService; +import org.joda.time.DateTime; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Timeout Notify Listener. + * Timeout Task Listener to start a message event when the deadline is due + */ +@Component +public class TimeoutNotifyListener extends BaseListener implements TaskListener, IMessageEvent { + + @Autowired + private UserService userService; + private Expression escalationGroup; + private Expression messageName; + private Expression currentDate; + private static final Logger log = Logger.getLogger(TimeoutNotifyListener.class.getName()); + + /** + * This calculates time logic + * and provides the necessary information to send message. + * + * @param delegateTask: The task which sends the message + */ + public void notify(DelegateTask delegateTask) { + DateTime currentDate = this.currentDate != null && this.currentDate.getValue(delegateTask) != null + ? new DateTime(String.valueOf(this.currentDate.getValue(delegateTask))) + : new DateTime(); + DateTime dueDate = delegateTask.getDueDate() != null ? new DateTime(delegateTask.getDueDate()) + : new DateTime(delegateTask.getExecution().getVariable("task_due_date")); + String escalationGroup = this.escalationGroup != null && this.escalationGroup.getValue(delegateTask) != null + ? String.valueOf(this.escalationGroup.getValue(delegateTask)) + : null; + DateTime remindDate = addBusinessDays(dueDate, -1); + DateTime escalateDate = addBusinessDays(dueDate, 1); + log.info("Timeout Notify listener invoked for pid=" + delegateTask.getProcessInstanceId() + + "|due Date=" + dueDate.toLocalDate() + "|current Date=" + currentDate.toLocalDate() + + "|escalation Date=" + escalateDate.toLocalDate() + + "|reminder Date=" + remindDate.toLocalDate()); + + log.info("DELEGATE_TASK_USER_BEFORE_ESCALATE:" + delegateTask.getAssignee()); + // Check if escalate first because reminder date is before escalation date + if (currentDate.toLocalDate().equals(escalateDate.toLocalDate()) && StringUtils.isNotEmpty(escalationGroup)) { + validateAssigneeAndNotify(delegateTask, "activity_escalation", escalationGroup); + } else if (currentDate.toLocalDate().equals(remindDate.toLocalDate())) { + validateAssigneeAndNotify(delegateTask, "activity_reminder", null); + } + } + + private void validateAssigneeAndNotify(DelegateTask delegateTask, String category, String escalationGroup) { + List escalationGrpEmails = new ArrayList<>(); + log.info("DELEGATE_TASK_USER_AFTER_ESCALATE:" + delegateTask.getAssignee()); + if (StringUtils.isNotEmpty(escalationGroup)) { + escalationGrpEmails.addAll(getEmailsForGroup(delegateTask.getExecution(), escalationGroup)); + } + if (StringUtils.isNotEmpty(delegateTask.getAssignee())) { + User user = getUser(delegateTask.getExecution(), userService, delegateTask.getAssignee()); + delegateTask.getExecution().setVariable("firstname", user.getFirstName()); + delegateTask.getExecution().setVariable("lastname", user.getLastName()); + delegateTask.getExecution().setVariable("name", user.getFirstName() + " " + user.getLastName()); + List userEmails = new ArrayList<>(); + if (StringUtils.isNotEmpty(user.getEmail())) { + userEmails.add(user.getEmail()); + sendEmailNotification(delegateTask.getExecution(), category, userEmails, escalationGrpEmails, + delegateTask.getId()); + } + } else { + delegateTask.getExecution().setVariable("name", getDefaultAddresseName()); + sendEmailNotification(delegateTask.getExecution(), category, + getEmailsOfUnassignedTask(delegateTask), escalationGrpEmails, delegateTask.getId()); + } + } + + private void sendEmailNotification(DelegateExecution execution, String category, List toEmails, + List ccEmails, String taskId) { + String toAddress = CollectionUtils.isNotEmpty(toEmails) ? StringUtils.join(toEmails, ",") : null; + String ccAddress = CollectionUtils.isNotEmpty(ccEmails) ? StringUtils.join(ccEmails, ",") : null; + if (StringUtils.isNotEmpty(toAddress)) { + Map emailAttributes = new HashMap<>(); + emailAttributes.put("to", toAddress); + if (StringUtils.isNotEmpty(ccAddress)) { + emailAttributes.put("cc", ccAddress); + } + emailAttributes.put("category", category); + emailAttributes.put("taskid", taskId); + log.info("Inside notify attributes:" + emailAttributes); + if (StringUtils.isNotBlank(toAddress) && StringUtils.indexOf(toAddress, "@") > 0) { + sendMessage(execution, emailAttributes, getMessageName(execution)); + } + } + } + + private String getMessageName(DelegateExecution execution) { + return String.valueOf(this.messageName.getValue(execution)); + } + +} diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/rest/ProcessInstanceRestResource.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/rest/ProcessInstanceRestResource.java new file mode 100644 index 00000000..2252066d --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/rest/ProcessInstanceRestResource.java @@ -0,0 +1,44 @@ +package org.camunda.bpm.extension.hooks.rest; + +import java.util.Map; + +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.camunda.bpm.engine.rest.dto.VariableValueDto; +import org.springframework.hateoas.EntityModel; + +@Produces(MediaType.APPLICATION_JSON) +public interface ProcessInstanceRestResource extends RestResource { + + String PATH = "/process-instance"; + + public final static String DESERIALIZE_VALUE_QUERY_PARAM = "deserializeValue"; + + @GET + @Path("/{id}/activity-instances") + @Produces(MediaType.APPLICATION_JSON) + EntityModel getActivityInstanceTree( + @PathParam("id") String id); + + @GET + @Path("/{id}/variables") + @Produces(MediaType.APPLICATION_JSON) + Map getVariables( + @QueryParam(DESERIALIZE_VALUE_QUERY_PARAM) @DefaultValue("true") boolean deserializeValues, + @PathParam("id") String id); + + @GET + @Path("/{id}/variables/{variableName}") + @Produces(MediaType.APPLICATION_JSON) + VariableValueDto getVariable( + @PathParam("variableName") String variableName, + @QueryParam(DESERIALIZE_VALUE_QUERY_PARAM) @DefaultValue("true") boolean deserializeValue, + @PathParam("id") String id); + +} diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/rest/impl/ProcessInstanceRestResourceImpl.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/rest/impl/ProcessInstanceRestResourceImpl.java new file mode 100644 index 00000000..2c4cfd47 --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/rest/impl/ProcessInstanceRestResourceImpl.java @@ -0,0 +1,43 @@ +package org.camunda.bpm.extension.hooks.rest.impl; + +import org.camunda.bpm.engine.rest.ProcessInstanceRestService; +import org.camunda.bpm.engine.rest.dto.VariableValueDto; +import org.camunda.bpm.extension.hooks.rest.ProcessInstanceRestResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.hateoas.EntityModel; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +import java.util.Map; + +public class ProcessInstanceRestResourceImpl implements ProcessInstanceRestResource { + + private static final Logger LOG = LoggerFactory.getLogger(ProcessInstanceRestResourceImpl.class); + + private final ProcessInstanceRestService restService; + + public ProcessInstanceRestResourceImpl(ProcessInstanceRestService processInstanceRestService) { + this.restService = processInstanceRestService; + } + + @Override + public EntityModel getActivityInstanceTree(String id) { + + org.camunda.bpm.engine.rest.dto.runtime.ActivityInstanceDto responseEntity = restService.getProcessInstance(id) + .getActivityInstanceTree(); + return EntityModel.of(responseEntity, + linkTo(methodOn(ProcessInstanceRestResourceImpl.class).getActivityInstanceTree(id)).withSelfRel()); + } + + @Override + public Map getVariables(boolean deserializeValues, String id) { + return restService.getProcessInstance(id).getVariablesResource().getVariables(deserializeValues); + } + + @Override + public VariableValueDto getVariable(String variableName, boolean deserializeValue, String id) { + return restService.getProcessInstance(id).getVariablesResource().getVariable(variableName, deserializeValue); + } +} diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/FormSubmissionService.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/FormSubmissionService.java index 658702ee..3a01af73 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/FormSubmissionService.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/FormSubmissionService.java @@ -1,6 +1,5 @@ package org.camunda.bpm.extension.hooks.services; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -9,7 +8,7 @@ import org.camunda.bpm.engine.variable.Variables; import org.camunda.bpm.engine.variable.value.FileValue; import org.camunda.bpm.extension.commons.connector.HTTPServiceInvoker; -import org.camunda.bpm.extension.commons.connector.support.FormTokenAccessHandler; +import org.camunda.bpm.extension.commons.connector.FormioTokenServiceProvider; import org.camunda.bpm.extension.hooks.exceptions.FormioServiceException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -18,7 +17,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; - +import javax.annotation.Resource; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -27,115 +26,122 @@ import java.util.logging.Logger; import java.util.stream.Collectors; - @Qualifier("formSubmissionService") @Service public class FormSubmissionService { private final Logger LOGGER = Logger.getLogger(FormSubmissionService.class.getName()); + @Resource(name = "bpmObjectMapper") + private ObjectMapper bpmObjectMapper; @Autowired - private FormTokenAccessHandler formTokenAccessHandler; + private FormioTokenServiceProvider formioTokenServiceProvider; @Autowired private HTTPServiceInvoker httpServiceInvoker; public String readSubmission(String formUrl) { - ResponseEntity response = httpServiceInvoker.execute(formUrl, HttpMethod.GET, null); - if(response.getStatusCode().value() == HttpStatus.OK.value()) { + ResponseEntity response = httpServiceInvoker.execute(formUrl, HttpMethod.GET, null); + if (response.getStatusCode().value() == HttpStatus.OK.value()) { return response.getBody(); } else { - throw new FormioServiceException("Unable to read submission for: "+ formUrl+ ". Message Body: " + + throw new FormioServiceException("Unable to read submission for: " + formUrl + ". Message Body: " + response.getBody()); } } public String createRevision(String formUrl) throws IOException { - String submission = readSubmission(formUrl); - if(StringUtils.isBlank(submission)) { - LOGGER.log(Level.SEVERE,"Unable to read submission for "+formUrl); + String submission = readSubmission(formUrl); + if (StringUtils.isBlank(submission)) { + LOGGER.log(Level.SEVERE, "Unable to read submission for " + formUrl); return null; } - ObjectMapper objectMapper = new ObjectMapper(); - ResponseEntity response = httpServiceInvoker.execute(getSubmissionUrl(formUrl), HttpMethod.POST, submission); - if(response.getStatusCode().value() == HttpStatus.CREATED.value()) { - JsonNode jsonNode = objectMapper.readTree(response.getBody()); + + ResponseEntity response = httpServiceInvoker.execute(getSubmissionUrl(formUrl), HttpMethod.POST, + submission); + if (response.getStatusCode().value() == HttpStatus.CREATED.value()) { + JsonNode jsonNode = bpmObjectMapper.readTree(response.getBody()); String submissionId = jsonNode.get("_id").asText(); return submissionId; } else { - throw new FormioServiceException("Unable to create revision for: "+ formUrl+ ". Message Body: " + + throw new FormioServiceException("Unable to create revision for: " + formUrl + ". Message Body: " + response.getBody()); } } public String createSubmission(String formUrl, String submission) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - ResponseEntity response = httpServiceInvoker.execute(getSubmissionUrl(formUrl), HttpMethod.POST, submission); - if(response.getStatusCode().value() == HttpStatus.CREATED.value()) { - JsonNode jsonNode = objectMapper.readTree(response.getBody()); + + ResponseEntity response = httpServiceInvoker.execute(getSubmissionUrl(formUrl), HttpMethod.POST, + submission); + if (response.getStatusCode().value() == HttpStatus.CREATED.value()) { + JsonNode jsonNode = bpmObjectMapper.readTree(response.getBody()); String submissionId = jsonNode.get("_id").asText(); return submissionId; } else { - throw new FormioServiceException("Unable to create submission for: "+ formUrl+ ". Message Body: " + + throw new FormioServiceException("Unable to create submission for: " + formUrl + ". Message Body: " + response.getBody()); } } public void updateSubmission(String submissionUrl, String submission) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - ResponseEntity response = httpServiceInvoker.execute(submissionUrl, HttpMethod.PUT, submission); - if(response.getStatusCode().value() != HttpStatus.OK.value()) { - LOGGER.log(Level.SEVERE,"Unable to update submission for "+submissionUrl + + + ResponseEntity response = httpServiceInvoker.execute(submissionUrl, HttpMethod.PUT, submission); + if (response.getStatusCode().value() != HttpStatus.OK.value()) { + LOGGER.log(Level.SEVERE, "Unable to update submission for " + submissionUrl + " Response code::" + response.getStatusCode().value()); - throw new FormioServiceException("Unable to update submission for: "+ submissionUrl + ". Message Body: " + + throw new FormioServiceException("Unable to update submission for: " + submissionUrl + ". Message Body: " + response.getBody()); } } public String getFormIdByName(String formUrl) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - ResponseEntity response = httpServiceInvoker.execute(formUrl, HttpMethod.GET, null); - if(response.getStatusCode().value() == HttpStatus.OK.value()) { - JsonNode jsonNode = objectMapper.readTree(response.getBody()); + + ResponseEntity response = httpServiceInvoker.execute(formUrl, HttpMethod.GET, null); + if (response.getStatusCode().value() == HttpStatus.OK.value()) { + JsonNode jsonNode = bpmObjectMapper.readTree(response.getBody()); String formId = jsonNode.get("_id").asText(); return formId; } else { - throw new FormioServiceException("Unable to get name for: "+ formUrl+ ". Message Body: " + + throw new FormioServiceException("Unable to get name for: " + formUrl + ". Message Body: " + response.getBody()); } } - private String getSubmissionUrl(String formUrl){ - if(StringUtils.endsWith(formUrl,"submission")) { + private String getSubmissionUrl(String formUrl) { + if (StringUtils.endsWith(formUrl, "submission")) { return formUrl; } - return StringUtils.substringBeforeLast(formUrl,"/"); + return StringUtils.substringBeforeLast(formUrl, "/"); } - public Map retrieveFormValues(String formUrl) throws IOException { - Map fieldValues = new HashMap(); + public Map retrieveFormValues(String formUrl) throws IOException { + Map fieldValues = new HashMap(); String submission = readSubmission(formUrl); - if(StringUtils.isNotEmpty(submission)) { - JsonNode dataNode = getObjectMapper().readTree(submission); + LOGGER.log(Level.INFO, "Submitted_payload" + submission); + // System.out.println("submission: " + submission); + if (StringUtils.isNotEmpty(submission)) { + JsonNode dataNode = bpmObjectMapper.readTree(submission); Iterator> dataElements = dataNode.findPath("data").fields(); while (dataElements.hasNext()) { Map.Entry entry = dataElements.next(); - if(StringUtils.endsWithIgnoreCase(entry.getKey(),"_file")) { + if (StringUtils.endsWithIgnoreCase(entry.getKey(), "_file")) { List fileNames = new ArrayList(); - if(entry.getValue().isArray()) { + if (entry.getValue().isArray()) { for (JsonNode fileNode : entry.getValue()) { - byte[] bytes = Base64.getDecoder().decode(StringUtils.substringAfterLast(fileNode.get("url").asText(), "base64,")); + byte[] bytes = Base64.getDecoder() + .decode(StringUtils.substringAfterLast(fileNode.get("url").asText(), "base64,")); FileValue fileValue = Variables.fileValue(fileNode.get("originalName").asText()) .file(bytes) .mimeType(fileNode.get("type").asText()) .create(); fileNames.add(fileNode.get("originalName").asText()); - fieldValues.put(StringUtils.substringBeforeLast(fileNode.get("originalName").asText(),".")+entry.getKey(), fileValue); - if(fileNames.size() > 0) { - fieldValues.put(entry.getKey()+"_uploadname", StringUtils.join(fileNames, ",")); + fieldValues.put(StringUtils.substringBeforeLast(fileNode.get("originalName").asText(), ".") + + entry.getKey(), fileValue); + if (fileNames.size() > 0) { + fieldValues.put(entry.getKey() + "_uploadname", StringUtils.join(fileNames, ",")); } } } - } else{ + } else { fieldValues.put(entry.getKey(), convertToOriginType(entry.getValue())); } } @@ -145,55 +151,51 @@ public Map retrieveFormValues(String formUrl) throws IOException private Object convertToOriginType(JsonNode value) throws IOException { Object fieldValue; - if(value.isNull()){ + if (value.isNull()) { fieldValue = null; - } else if(value.isBoolean()){ + } else if (value.isBoolean()) { fieldValue = value.booleanValue(); - } else if(value.isInt()){ + } else if (value.isInt()) { fieldValue = value.intValue(); - } else if(value.isBinary()){ + } else if (value.isBinary()) { fieldValue = value.binaryValue(); - } else if(value.isLong()){ + } else if (value.isLong()) { fieldValue = value.asLong(); - } else if(value.isDouble()){ + } else if (value.isDouble()) { fieldValue = value.asDouble(); - } else if(value.isBigDecimal()){ + } else if (value.isBigDecimal()) { fieldValue = value.decimalValue(); - } else if(value.isTextual()){ + } else if (value.isTextual()) { fieldValue = value.asText(); - } else{ + } else { fieldValue = value.toString(); } - if(Objects.equals(fieldValue, "")) { + if (Objects.equals(fieldValue, "")) { fieldValue = null; } return fieldValue; } - public String createFormSubmissionData(Map bpmVariables) throws IOException { + public String createFormSubmissionData(Map bpmVariables) throws IOException { List fileKeys = bpmVariables.keySet().stream() .filter((key) -> key.endsWith("_file")) .collect(Collectors.toList()); - for (String fileKey: fileKeys) { + for (String fileKey : fileKeys) { InputStream file = (InputStream) bpmVariables.get(fileKey); byte[] bytes = IOUtils.toByteArray(file); String fileString = new String(bytes, StandardCharsets.UTF_8); bpmVariables.put(fileKey, fileString); } - Map> data = new HashMap<>(); - data.put("data",bpmVariables); - return getObjectMapper().writeValueAsString(data); - } - - private ObjectMapper getObjectMapper(){ - return new ObjectMapper(); + Map> data = new HashMap<>(); + data.put("data", bpmVariables); + return bpmObjectMapper.writeValueAsString(data); } @Deprecated public String getAccessToken() { - return formTokenAccessHandler.getAccessToken(); + return formioTokenServiceProvider.getAccessToken(); } -} \ No newline at end of file +} diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/IUser.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/IUser.java index 539cdb95..577a317a 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/IUser.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/IUser.java @@ -6,32 +6,50 @@ import org.camunda.bpm.engine.delegate.DelegateTask; import org.camunda.bpm.engine.identity.User; import org.camunda.bpm.engine.task.IdentityLink; +import org.springframework.beans.factory.annotation.Autowired; import org.joda.time.DateTime; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; /** * This class aimed at centralizing all user related information. * - * @author sumathi.thirumani@aot-technologies.com, yichun.zhao@aot-technologies.com + * @author sumathi.thirumani@aot-technologies.com, + * yichun.zhao@aot-technologies.com */ public interface IUser { - default String getName(DelegateExecution execution, String userId) { - User user = getUser(execution, userId); - return user.getFirstName()+" "+user.getLastName(); + default String getName(DelegateExecution execution, UserService userService, String userId) { + User user = getUser(execution, userService, userId); + return user.getFirstName() + " " + user.getLastName(); } - default String getEmail(DelegateExecution execution, String userId) { - User user = getUser(execution, userId); + default String getEmail(DelegateExecution execution, UserService userService, String userId) { + User user = getUser(execution, userService, userId); return user.getEmail(); } - default User getUser(DelegateExecution execution, String userId) { - userId = execution.getVariable("provider_idir_userid").toString(); - return execution.getProcessEngine().getIdentityService().createUserQuery().userId(userId).singleResult(); + default User getUser(DelegateExecution execution, UserService userService, String userId) { + User user = userService.searchUserByAttribute("userid", userId); + Logger LOGGER = Logger.getLogger(IUser.class.getName()); + LOGGER.log(Level.INFO, "UNIQUE_USER" + user); + // String providerIdirUserId = + // execution.getVariable("provider_idir_userid").toString(); + // User user = + // execution.getProcessEngine().getIdentityService().createUserQuery().userId(providerIdirUserId) + // .singleResult(); + // // user = userService.searchUserByAttribute("userid", userId); + // Logger LOGGER = Logger.getLogger(IUser.class.getName()); + // LOGGER.log(Level.INFO, "user" + user); + // if (user == null) { + // user = userService.searchUserByAttribute("userid", userId); + // } + return user; } default String getDefaultAddresseName() { @@ -41,7 +59,7 @@ default String getDefaultAddresseName() { default List getEmailsOfUnassignedTask(DelegateTask delegateTask) { Set identityLinks = delegateTask.getCandidates(); List emails = new ArrayList<>(); - if(CollectionUtils.isNotEmpty(identityLinks)) { + if (CollectionUtils.isNotEmpty(identityLinks)) { for (IdentityLink entry : identityLinks) { if (StringUtils.isNotEmpty(entry.getGroupId())) { emails.addAll(getEmailsForGroup(delegateTask.getExecution(), entry.getGroupId())); @@ -53,10 +71,11 @@ default List getEmailsOfUnassignedTask(DelegateTask delegateTask) { default List getEmailsForGroup(DelegateExecution execution, String groupName) { List emails = new ArrayList<>(); - if(StringUtils.isNotBlank(groupName)) { - List users = execution.getProcessEngine().getIdentityService().createUserQuery().memberOfGroup(StringUtils.trim(groupName)).list(); - for(User entry : users) { - if(StringUtils.isNotEmpty(entry.getEmail())) { + if (StringUtils.isNotBlank(groupName)) { + List users = execution.getProcessEngine().getIdentityService().createUserQuery() + .memberOfGroup(StringUtils.trim(groupName)).list(); + for (User entry : users) { + if (StringUtils.isNotEmpty(entry.getEmail())) { emails.add(entry.getEmail()); } } @@ -66,18 +85,19 @@ default List getEmailsForGroup(DelegateExecution execution, String group /** * This adds business days to a Date object. - * Adapted from https://stackoverflow.com/questions/1044688/addbusinessdays-and-getbusinessdays + * Adapted from + * https://stackoverflow.com/questions/1044688/addbusinessdays-and-getbusinessdays * * @param date: The date to be changed * @param days: The number of days to be added */ default DateTime addBusinessDays(DateTime date, int days) { - if (days == 0) return date; + if (days == 0) + return date; if (date.getDayOfWeek() == 6) { date = date.plusDays(2); days -= 1; - } - else if (date.getDayOfWeek() == 7) { + } else if (date.getDayOfWeek() == 7) { date = date.plusDays(1); days -= 1; } @@ -90,5 +110,4 @@ else if (date.getDayOfWeek() == 7) { return date.plusDays(extraDays); } - } diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/UserService.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/UserService.java new file mode 100644 index 00000000..9b65c63d --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/UserService.java @@ -0,0 +1,66 @@ +package org.camunda.bpm.extension.hooks.services; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import org.camunda.bpm.engine.impl.identity.IdentityProviderException; +import org.camunda.bpm.engine.impl.persistence.entity.UserEntity; +import org.camunda.bpm.extension.keycloak.KeycloakConfiguration; +import org.camunda.bpm.extension.keycloak.rest.KeycloakRestTemplate; +import org.camunda.bpm.engine.identity.User; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.UserRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static org.camunda.bpm.extension.keycloak.json.JsonUtil.getJsonString; +import static org.camunda.bpm.extension.keycloak.json.JsonUtil.parseAsJsonArray; + +@Qualifier("userService") +@Service +public class UserService { + + private static final Logger LOG = LoggerFactory.getLogger(UserService.class); + + @Autowired + private Keycloak keycloak; + @Value("${keycloak.url.realm}") + private String keycloakRealm; + + public User searchUserByAttribute(String attributeName, String attributeValue) { + try { + // List users = keycloak + // .realm("servicebc") + // .users() + // .searchByAttributes(attributeName + ":" + attributeValue); + List users = keycloak + .realm(keycloakRealm) + .users() + .searchByAttributes(attributeName + ":" + attributeValue); + // .searchByUsername(attributeValue, true); + UserRepresentation user = users.get(0); + UserEntity result = new UserEntity(); + String username = user.getUsername(); + String email = user.getEmail(); + String firstName = user.getFirstName(); + String lastName = user.getLastName(); + result.setId(username); + result.setEmail(email); + result.setFirstName(firstName); + result.setLastName(lastName); + return result; + } catch (Exception e) { + e.printStackTrace(); + LOG.error(e.getMessage()); + } + return null; + } +} diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/analytics/SimpleDBDataPipeline.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/analytics/SimpleDBDataPipeline.java index dd0b9ee6..dba967f0 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/analytics/SimpleDBDataPipeline.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/analytics/SimpleDBDataPipeline.java @@ -1,6 +1,5 @@ package org.camunda.bpm.extension.hooks.services.analytics; - import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; @@ -23,7 +22,7 @@ /** * Service class for publishing data to downstream analytics system. * - * @author sumathi.thirumani@aot-technologies.com + * @author sumathi.thirumani@aot-technologies.com */ @Service("dbdatapipeline") public class SimpleDBDataPipeline extends AbstractDataPipeline { @@ -31,7 +30,7 @@ public class SimpleDBDataPipeline extends AbstractDataPipeline { private final Logger LOGGER = Logger.getLogger(SimpleDBDataPipeline.class.getName()); @Autowired - private NamedParameterJdbcTemplate analyticsJdbcTemplate; + private NamedParameterJdbcTemplate analyticsJdbcTemplate; /** * Transformation method. @@ -41,21 +40,23 @@ public class SimpleDBDataPipeline extends AbstractDataPipeline { */ @Override public Map prepare(Map variables) { - LOGGER.info("Inside transformation for pid :"+ getIdentityKey(variables) +" : map: "+variables); - Map dataMap = new HashMap<>(); - for(Map.Entry entry : variables.entrySet()) { - if(entry.getValue() != null) { - if(StringUtils.endsWith(entry.getKey(),"_date") || StringUtils.endsWith(entry.getKey(),"_date_time")) { + LOGGER.info("Inside transformation for pid :" + getIdentityKey(variables) + " : map: " + variables); + Map dataMap = new HashMap<>(); + for (Map.Entry entry : variables.entrySet()) { + if (entry.getValue() != null) { + if (StringUtils.endsWith(entry.getKey(), "_date") + || StringUtils.endsWith(entry.getKey(), "_date_time")) { if (entry.getKey().equals("action_claim_date_time")) { try { Timestamp timestamp = new Timestamp(Long.parseLong(String.valueOf(entry.getValue()))); DateTime ts = new DateTime(timestamp); dataMap.put(entry.getKey(), new Timestamp((ts.getMillis()))); - } catch(NumberFormatException e) { + } catch (NumberFormatException e) { DateTime ts = new DateTime(String.valueOf(entry.getValue())); dataMap.put(entry.getKey(), new Timestamp((ts.getMillis()))); } - } else if(entry.getValue() != null && !"null".equalsIgnoreCase(String.valueOf(entry.getValue())) && StringUtils.isNotBlank(String.valueOf(entry.getValue()))) { + } else if (entry.getValue() != null && !"null".equalsIgnoreCase(String.valueOf(entry.getValue())) + && StringUtils.isNotBlank(String.valueOf(entry.getValue()))) { DateTime ts = new DateTime(String.valueOf(entry.getValue())); dataMap.put(entry.getKey(), new Timestamp((ts.getMillis()))); } @@ -65,48 +66,52 @@ public Map prepare(Map variables) { } } - LOGGER.info("Post transformation:"+ dataMap); + LOGGER.info("Post transformation:" + dataMap); return dataMap; } /** - * Implementation method for direct database connectivity with downstream system. - * This method handles lob & non-lob objects separately to keep the thread span short. + * Implementation method for direct database connectivity with downstream + * system. + * This method handles lob & non-lob objects separately to keep the thread span + * short. * * @param data * @return */ @Override - public DataPipelineResponse publish(Map data) { + public DataPipelineResponse publish(Map data) { DataPipelineResponse response = new DataPipelineResponse(); - Map nonLobMap = new HashMap<>(); - Map> lobMap = new HashMap<>(); + Map nonLobMap = new HashMap<>(); + Map> lobMap = new HashMap<>(); try { - for(Map.Entry entry : data.entrySet()) { - if(StringUtils.endsWith(entry.getKey(),"_file")) { - String fileNamePrefix = StringUtils.substringBefore(entry.getKey(),"_file"); - if(!lobMap.containsKey(entry.getKey())) { - Map lobData = new HashMap<>(); - lobData.put("name",getDateWithoutSpecialCharacters(String.valueOf(data.get(fileNamePrefix.concat("_name"))))); - lobData.put("file_mimetype",data.get(fileNamePrefix.concat("_mimetype"))); - lobData.put("file_stream",entry.getValue()); - lobData.put("file_size",data.get(fileNamePrefix.concat("_size"))); - lobData.put("stream_id",data.get(fileNamePrefix.concat("_stream_id"))); - lobData.put("files_entity_key",data.get("files_entity_key")); - //nonLobMap.put(StringUtils.concat("_id"), data.get(fileNamePrefix.concat("_stream_id"))); - - lobMap.put(entry.getKey(),lobData); + for (Map.Entry entry : data.entrySet()) { + if (StringUtils.endsWith(entry.getKey(), "_file")) { + String fileNamePrefix = StringUtils.substringBefore(entry.getKey(), "_file"); + if (!lobMap.containsKey(entry.getKey())) { + Map lobData = new HashMap<>(); + lobData.put("name", getDateWithoutSpecialCharacters( + String.valueOf(data.get(fileNamePrefix.concat("_name"))))); + lobData.put("file_mimetype", data.get(fileNamePrefix.concat("_mimetype"))); + lobData.put("file_stream", entry.getValue()); + lobData.put("file_size", data.get(fileNamePrefix.concat("_size"))); + lobData.put("stream_id", data.get(fileNamePrefix.concat("_stream_id"))); + lobData.put("files_entity_key", data.get("files_entity_key")); + // nonLobMap.put(StringUtils.concat("_id"), + // data.get(fileNamePrefix.concat("_stream_id"))); + + lobMap.put(entry.getKey(), lobData); } } else { nonLobMap.put(entry.getKey(), entry.getValue()); } - } - for(Map.Entry entry : data.entrySet()) { + for (Map.Entry entry : data.entrySet()) { if (StringUtils.endsWith(entry.getKey(), "_uploadname")) { - if (entry.getValue() != null && StringUtils.isNotBlank(String.valueOf(entry.getValue())) && !"null".equals(String.valueOf(entry.getValue()))) { + if (entry.getValue() != null && StringUtils.isNotBlank(String.valueOf(entry.getValue())) + && !"null".equals(String.valueOf(entry.getValue()))) { String filename = String.valueOf(entry.getValue()); List fieldValue = new ArrayList<>(); for (String fentry : filename.split(",")) { @@ -114,21 +119,22 @@ public DataPipelineResponse publish(Map data) { String prefix = StringUtils.substringBefore(entry.getKey(), "_file_uploadname"); fieldValue.add(String.valueOf(data.get(name + prefix + "_stream_id"))); } - nonLobMap.put(StringUtils.substringBefore(entry.getKey(), "_file_uploadname") + "_file_id", String.join(",", fieldValue)); + nonLobMap.put(StringUtils.substringBefore(entry.getKey(), "_file_uploadname") + "_file_id", + String.join(",", fieldValue)); } } } - - //Non-lob objects block - String query = getQuery(String.valueOf(nonLobMap.get("entity_key")),nonLobMap,"pid",getIdentityKey(data)); - LOGGER.info("Non-lob query:"+ query); - analyticsJdbcTemplate.update(query,nonLobMap); + // Non-lob objects block + String query = getQuery(String.valueOf(nonLobMap.get("entity_key")), nonLobMap, "pid", + getIdentityKey(data)); + LOGGER.info("Non-lob query:" + query); + analyticsJdbcTemplate.update(query, nonLobMap); // Lob objects handleFileObject(lobMap); response.setStatus(ResponseStatus.SUCCESS); - } catch(Exception ex) { + } catch (Exception ex) { LOGGER.log(Level.SEVERE, "Exception occurred in publishing data for analytics system", ex); response.setStatus(ResponseStatus.FAILURE, ex); } @@ -136,13 +142,15 @@ public DataPipelineResponse publish(Map data) { } private static String getDateWithoutSpecialCharacters(String filename) { - String timestampVal = StringUtils.replace(StringUtils.replace( - StringUtils.replace(StringUtils.replace(StringUtils.substringBefore(new DateTime().toString(), "."), "-", ""), ":", "") - ,".","")," ",""); - return StringUtils.substringBeforeLast(filename, ".")+"_"+timestampVal+"."+StringUtils.substringAfterLast(filename, "."); + String timestampVal = StringUtils.replace(StringUtils.replace( + StringUtils.replace( + StringUtils.replace(StringUtils.substringBefore(new DateTime().toString(), "."), "-", ""), ":", + ""), + ".", ""), " ", ""); + return StringUtils.substringBeforeLast(filename, ".") + "_" + timestampVal + "." + + StringUtils.substringAfterLast(filename, "."); } - /** * Implementation method for notification of execution status. * @@ -150,12 +158,12 @@ private static String getDateWithoutSpecialCharacters(String filename) { * @return */ @Override - public Map notificationMessage(DataPipelineResponse response) { - Map rspVarMap = new HashMap<>(); - LOGGER.info("Data pipeline status:" +response.getResponseCode()); - rspVarMap.put("code",response.getResponseCode()); - rspVarMap.put("message",response.getResponseMessage()); - rspVarMap.put("exception",response.getException()); + public Map notificationMessage(DataPipelineResponse response) { + Map rspVarMap = new HashMap<>(); + LOGGER.info("Data pipeline status:" + response.getResponseCode()); + rspVarMap.put("code", response.getResponseCode()); + rspVarMap.put("message", response.getResponseMessage()); + rspVarMap.put("exception", response.getException()); return rspVarMap; } @@ -165,11 +173,12 @@ public Map notificationMessage(DataPipelineResponse response) { * @param lobMap * @throws SQLException */ - private void handleFileObject(Map> lobMap) throws SQLException { - for(Map.Entry> entry : lobMap.entrySet()) { - String query = getQuery(String.valueOf(entry.getValue().get("files_entity_key")),entry.getValue(),"stream_id",String.valueOf(entry.getValue().get("stream_id"))); - LOGGER.info("lob query:"+ query); - analyticsJdbcTemplate.update(query,entry.getValue()); + private void handleFileObject(Map> lobMap) throws SQLException { + for (Map.Entry> entry : lobMap.entrySet()) { + String query = getQuery(String.valueOf(entry.getValue().get("files_entity_key")), entry.getValue(), + "stream_id", String.valueOf(entry.getValue().get("stream_id"))); + LOGGER.info("lob query:" + query); + analyticsJdbcTemplate.update(query, entry.getValue()); } } @@ -180,64 +189,67 @@ private void handleFileObject(Map> lobMap) throws SQLE * @param pkColums * @return */ - private String getValidationQuery(String tableName,String... pkColums) { - return IQueryFactory.getValidationQuery(tableName,pkColums); + private String getValidationQuery(String tableName, String... pkColums) { + return IQueryFactory.getValidationQuery(tableName, pkColums); } /** - * Returns the query + * Returns the query + * * @param formKey * @param dataMap * @return * @throws SQLException */ - private String getQuery(String formKey, Map dataMap,String pkname, String pkvalue) throws SQLException { - Map cols = getColumns(formKey, pkname,pkvalue); + private String getQuery(String formKey, Map dataMap, String pkname, String pkvalue) + throws SQLException { + Map cols = getColumns(formKey, pkname, pkvalue); List filteredCols = new ArrayList<>(); - LOGGER.info("Prepare query for columns:"+cols); - for(Map.Entry entry : dataMap.entrySet()) { - if(cols.containsKey(entry.getKey().toLowerCase())) { + LOGGER.info("Prepare query for columns:" + cols); + for (Map.Entry entry : dataMap.entrySet()) { + if (cols.containsKey(entry.getKey().toLowerCase())) { filteredCols.add(entry.getKey()); } } - LOGGER.info("Value of expression"+StringUtils.isEmpty(getIdentityKey(cols))); - return IQueryFactory.prepareQuery(formKey,filteredCols,StringUtils.isEmpty(getIdentityKey(cols))? Boolean.FALSE : Boolean.TRUE,pkname); + LOGGER.info("Value of expression" + StringUtils.isEmpty(getIdentityKey(cols))); + return IQueryFactory.prepareQuery(formKey, filteredCols, + StringUtils.isEmpty(getIdentityKey(cols)) ? Boolean.FALSE : Boolean.TRUE, pkname); } - /** * This method returns the column metadata for preparing dynamic queries. + * * @param formKey * @param pkName * @param pkValue * @return * @throws SQLException */ - private Map getColumns(String formKey,String pkName, String pkValue) throws SQLException { + private Map getColumns(String formKey, String pkName, String pkValue) throws SQLException { SqlParameterSource namedParameters = new MapSqlParameterSource(pkName, pkValue); - Map resp = analyticsJdbcTemplate.query(getValidationQuery(formKey,pkName), namedParameters,new ResultSetExtractor>(){ - @Override - public Map extractData(ResultSet rs) throws SQLException, DataAccessException { - Map dataMap=new HashMap<>(); - ResultSetMetaData resultSetMetaData = rs.getMetaData(); - for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) { - dataMap.put(rs.getMetaData().getColumnName(i).toLowerCase(), null); - } - while(rs.next()) { - for (int j = 1; j <= resultSetMetaData.getColumnCount(); j++) { - if(StringUtils.endsWith(rs.getMetaData().getColumnName(j),"_file")) { - //Not-loading the lob objects on retrieve to keep the metadata lightweight. - dataMap.put(rs.getMetaData().getColumnName(j), null); - } else { - dataMap.put(rs.getMetaData().getColumnName(j), JdbcUtils.getResultSetValue(rs, j)); + Map resp = analyticsJdbcTemplate.query(getValidationQuery(formKey, pkName), namedParameters, + new ResultSetExtractor>() { + @Override + public Map extractData(ResultSet rs) throws SQLException, DataAccessException { + Map dataMap = new HashMap<>(); + ResultSetMetaData resultSetMetaData = rs.getMetaData(); + for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) { + dataMap.put(rs.getMetaData().getColumnName(i).toLowerCase(), null); + } + while (rs.next()) { + for (int j = 1; j <= resultSetMetaData.getColumnCount(); j++) { + if (StringUtils.endsWith(rs.getMetaData().getColumnName(j), "_file")) { + // Not-loading the lob objects on retrieve to keep the metadata lightweight. + dataMap.put(rs.getMetaData().getColumnName(j), null); + } else { + dataMap.put(rs.getMetaData().getColumnName(j), JdbcUtils.getResultSetValue(rs, j)); + } + } } + return dataMap; } - } - return dataMap; - } - }); + }); return resp; } - } diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/CamundaExtConfiguration.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/CamundaExtConfiguration.java index 2f004a61..feb73f79 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/CamundaExtConfiguration.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/CamundaExtConfiguration.java @@ -1,8 +1,10 @@ package org.camunda.bpm.extension.keycloak; - - +import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; @@ -12,40 +14,59 @@ import javax.sql.DataSource; import java.util.Properties; - /** * The Camunda Showcase Spring Boot application. */ @Configuration public class CamundaExtConfiguration { - /** - * Secondary datasource. - * This is used only for publishing data to analytics. - * @return - */ - @Bean("analyticsDS") - @ConfigurationProperties("analytics.datasource") - public DataSource analyticsDS(){ - return DataSourceBuilder.create().build(); - } - - - /** - * JDBC template for analytics datasource interaction. - * @param analyticsDS - * @return - */ - @Bean("analyticsJdbcTemplate") - public NamedParameterJdbcTemplate analyticsJdbcTemplate(@Qualifier("analyticsDS") DataSource analyticsDS) { - return new NamedParameterJdbcTemplate(analyticsDS); - } - - @Bean - @ConfigurationProperties(prefix = "websocket") - public Properties messageBrokerProperties() { - return new Properties(); - } + @Value("${keycloak.url}") + private String keycloakUrl; + @Value("${keycloak.url.realm}") + private String keycloakRealm; + @Value("${keycloak.clientId}") + private String keycloakClientId; + @Value("${keycloak.clientSecret}") + private String keycloakClientSecret; + + /** + * Secondary datasource. + * This is used only for publishing data to analytics. + * + * @return + */ + @Bean("analyticsDS") + @ConfigurationProperties("analytics.datasource") + public DataSource analyticsDS() { + return DataSourceBuilder.create().build(); + } + + /** + * JDBC template for analytics datasource interaction. + * + * @param analyticsDS + * @return + */ + @Bean("analyticsJdbcTemplate") + public NamedParameterJdbcTemplate analyticsJdbcTemplate(@Qualifier("analyticsDS") DataSource analyticsDS) { + return new NamedParameterJdbcTemplate(analyticsDS); + } + + @Bean + @ConfigurationProperties(prefix = "websocket") + public Properties messageBrokerProperties() { + return new Properties(); + } + @Bean + Keycloak keycloak() { + return KeycloakBuilder.builder() + .serverUrl(keycloakUrl + "/auth") + .realm(keycloakRealm) + .clientId(keycloakClientId) + .grantType(OAuth2Constants.CLIENT_CREDENTIALS) + .clientSecret(keycloakClientSecret) + .build(); + } } diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/plugin/KeycloakIdentityProviderSession.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/plugin/KeycloakIdentityProviderSession.java index 3f4ff681..1352d0e5 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/plugin/KeycloakIdentityProviderSession.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/plugin/KeycloakIdentityProviderSession.java @@ -4,6 +4,8 @@ import java.util.List; import org.camunda.bpm.engine.identity.Group; +import org.camunda.bpm.engine.identity.Tenant; +import org.camunda.bpm.engine.identity.TenantQuery; import org.camunda.bpm.engine.identity.User; import org.camunda.bpm.engine.impl.interceptor.CommandContext; import org.camunda.bpm.extension.keycloak.*; @@ -19,13 +21,21 @@ public class KeycloakIdentityProviderSession extends org.camunda.bpm.extension.keycloak.KeycloakIdentityProviderSession { + private CustomConfig config; + private TenantService tenantService; + protected QueryCache> tenantQueryCache; + public KeycloakIdentityProviderSession(KeycloakConfiguration keycloakConfiguration, KeycloakRestTemplate restTemplate, KeycloakContextProvider keycloakContextProvider, - QueryCache> userQueryCache, QueryCache> groupQueryCache, + QueryCache> userQueryCache, + QueryCache> groupQueryCache, + QueryCache> tenantQueryCache, QueryCache checkPasswordCache, - String webClientId, boolean enableClientAuth) { + CustomConfig config) { super(keycloakConfiguration, restTemplate, keycloakContextProvider, userQueryCache, groupQueryCache, checkPasswordCache); - this.groupService = new KeycloakGroupService(keycloakConfiguration, restTemplate, keycloakContextProvider, webClientId, enableClientAuth); - this.userService = new KeycloakUserService(keycloakConfiguration, restTemplate, keycloakContextProvider, webClientId, enableClientAuth); + this.config = config; + this.groupService = new KeycloakGroupService(keycloakConfiguration, restTemplate, keycloakContextProvider, config); + this.userService = new KeycloakUserService(keycloakConfiguration, restTemplate, keycloakContextProvider, config); + this.tenantQueryCache = tenantQueryCache; } /** @@ -75,4 +85,82 @@ private List doFindUserByQueryCriteria(CacheableKeycloakUserQuery userQuer this.userService.requestUsersWithoutGroupId(userQuery); } -} \ No newline at end of file + /** + * find groups meeting given group query criteria (with cache lookup and post + * processing). + * + * @param groupQuery the group query + * @return list of matching groups + */ + protected List findGroupByQueryCriteria(KeycloakGroupQuery groupQuery) { + StringBuilder resultLogger = new StringBuilder(); + + if (KeycloakPluginLogger.INSTANCE.isDebugEnabled()) { + resultLogger.append("Keycloak group query results: ["); + } + + List allMatchingGroups = groupQueryCache.getOrCompute(CacheableKeycloakGroupQuery.of(groupQuery), + this::doFindGroupByQueryCriteria); + + List processedGroups = groupService.postProcessResults(groupQuery, allMatchingGroups, resultLogger); + + if (KeycloakPluginLogger.INSTANCE.isDebugEnabled()) { + resultLogger.append("]"); + KeycloakPluginLogger.INSTANCE.groupQueryResult(resultLogger.toString()); + } + + return processedGroups; + } + + /** + * find all groups meeting given group query criteria (without cache lookup or + * post processing). + * + * @param groupQuery the group query + * @return list of matching groups + */ + private List doFindGroupByQueryCriteria(CacheableKeycloakGroupQuery groupQuery) { + if (StringUtils.hasLength(groupQuery.getUserId())) { + // if restriction on userId is provided, we're searching within the groups of a + // single user + return groupService.requestGroupsByUserId(groupQuery); + } else { + return groupService.requestGroupsWithoutUserId(groupQuery); + } + } + @Override + public TenantQuery createTenantQuery() { + return new CustomKeycloakTenantQuery(org.camunda.bpm.engine.impl.context.Context.getProcessEngineConfiguration() + .getCommandExecutorTxRequired()); + } + + @Override + public TenantQuery createTenantQuery(CommandContext commandContext) { + return new CustomKeycloakTenantQuery(); + } + + protected long findTenantCountByQueryCriteria(CustomKeycloakTenantQuery tenantQuery) { + return findTenantByQueryCriteria(tenantQuery).size(); + } + + private List doFindTenantByQueryCriteria(CacheableKeycloakTenantQuery tenantQuery) { + if (!config.isEnableMultiTenancy()) { + return Collections.emptyList(); + } + return tenantService.requestTenantsByUserId(tenantQuery.getUserId()); + } + + protected List findTenantByQueryCriteria(CustomKeycloakTenantQuery tenantQuery) { + + List allMatchingTenants = tenantQueryCache.getOrCompute(CacheableKeycloakTenantQuery.of(tenantQuery), + this::doFindTenantByQueryCriteria); + + return allMatchingTenants; + } + + @Override + public Tenant findTenantById(String id) { + return createTenantQuery(org.camunda.bpm.engine.impl.context.Context.getCommandContext()).tenantId(id) + .singleResult(); + } +} \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/plugin/KeycloakUserService.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/plugin/KeycloakUserService.java index 1122703c..f37e5405 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/plugin/KeycloakUserService.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/plugin/KeycloakUserService.java @@ -1,31 +1,36 @@ package org.camunda.bpm.extension.keycloak.plugin; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + import org.camunda.bpm.engine.identity.User; +import org.camunda.bpm.engine.impl.identity.IdentityProviderException; +import org.apache.commons.lang3.StringUtils; import org.camunda.bpm.engine.impl.persistence.entity.UserEntity; +import org.camunda.bpm.extension.keycloak.CacheableKeycloakUserQuery; import org.camunda.bpm.extension.keycloak.KeycloakConfiguration; -import org.camunda.bpm.extension.keycloak.rest.KeycloakRestTemplate; import org.camunda.bpm.extension.keycloak.KeycloakContextProvider; -import org.camunda.bpm.extension.keycloak.KeycloakGroupNotFoundException; -import org.camunda.bpm.extension.keycloak.CacheableKeycloakUserQuery; -import org.camunda.bpm.engine.impl.identity.IdentityProviderException; +import org.camunda.bpm.extension.keycloak.KeycloakUserNotFoundException; import org.camunda.bpm.extension.keycloak.json.JsonException; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestClientException; -import org.springframework.util.StringUtils; - -import java.util.logging.Logger; - -import org.springframework.http.ResponseEntity; +import org.camunda.bpm.extension.keycloak.rest.KeycloakRestTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.camunda.bpm.extension.keycloak.KeycloakGroupNotFoundException; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClientException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; import static org.camunda.bpm.extension.keycloak.json.JsonUtil.*; @@ -36,16 +41,23 @@ public class KeycloakUserService extends org.camunda.bpm.extension.keycloak.KeycloakUserService { /** This class' logger. */ - private final Logger LOGGER = Logger.getLogger(KeycloakUserService.class.getName()); + private static final Logger LOG = LoggerFactory.getLogger(KeycloakUserService.class); private String webClientId; private boolean enableClientAuth; + private boolean enableMultiTenancy; + private TenantService tenantService; public KeycloakUserService(KeycloakConfiguration keycloakConfiguration, KeycloakRestTemplate restTemplate, - KeycloakContextProvider keycloakContextProvider,String webClientId,boolean enableClientAuth) { + KeycloakContextProvider keycloakContextProvider, CustomConfig config) { super(keycloakConfiguration, restTemplate, keycloakContextProvider); - this.webClientId = webClientId; - this.enableClientAuth = enableClientAuth; + + this.webClientId = config.getWebClientId(); + this.enableClientAuth = config.isEnableClientAuth(); + this.enableMultiTenancy = config.isEnableMultiTenancy(); + if (this.enableMultiTenancy) { + this.tenantService = new TenantService(restTemplate, keycloakContextProvider, config); + } } @Override @@ -77,14 +89,14 @@ public List requestUsersByGroupId(CacheableKeycloakUserQuery query) { for (int i = 0; i < searchResult.size(); i++) { JsonObject keycloakUser = getJsonObjectAtIndex(searchResult, i); if (keycloakConfiguration.isUseEmailAsCamundaUserId() && - !StringUtils.hasLength(getJsonString(keycloakUser, "email"))) { + !StringUtils.isNotBlank(getJsonString(keycloakUser, "email"))) { continue; } if (keycloakConfiguration.isUseUsernameAsCamundaUserId() && - !StringUtils.hasLength(getJsonString(keycloakUser, "username"))) { + !StringUtils.isNotBlank(getJsonString(keycloakUser, "username"))) { continue; } - userList.add(transformUser(keycloakUser)); + userList.add(transformUser(keycloakUser, null)); } } catch (HttpClientErrorException hcee) { @@ -99,10 +111,155 @@ public List requestUsersByGroupId(CacheableKeycloakUserQuery query) { return userList; } + @Override + public List requestUsersWithoutGroupId(CacheableKeycloakUserQuery query) { + List users; + if (enableClientAuth) { + if (enableMultiTenancy) { + users = this.requestUsersByClientRoleAndTenantId(); + } else { + users = this.requestUsersByClientRole(); + } + } else { + users = super.requestUsersWithoutGroupId(query); + } + return users; + } + + /** + * requestUsersByClientRole + * @return + */ + protected List requestUsersByClientRole(){ + List userList = new ArrayList<>(); + try { + // get Keycloak specific userID + String keycloakClientID; + try { + keycloakClientID = getKeycloakClientID(webClientId); + } catch (KeycloakUserNotFoundException e) { + // user not found: empty search result + return Collections.emptyList(); + } + + // get groups of this user + ResponseEntity response = restTemplate.exchange(keycloakConfiguration.getKeycloakAdminUrl() + + "/clients/" + keycloakClientID + "/roles/formsflow-reviewer/users", HttpMethod.GET, + String.class); + if (!response.getStatusCode().equals(HttpStatus.OK)) { + throw new IdentityProviderException( + "Unable to read user data from " + keycloakConfiguration.getKeycloakAdminUrl() + + ": HTTP status code " + response.getStatusCodeValue()); + } - private UserEntity transformUser(JsonObject result) throws JsonException { + JsonArray searchResult = parseAsJsonArray(response.getBody()); + for (int i = 0; i < searchResult.size(); i++) { + userList.add(transformUser(getJsonObjectAtIndex(searchResult, i), null)); + } + + } catch (HttpClientErrorException hcee) { + // if userID is unknown server answers with HTTP 404 not found + if (hcee.getStatusCode().equals(HttpStatus.NOT_FOUND)) { + return Collections.emptyList(); + } + throw hcee; + } catch (RestClientException | JsonException rce) { + throw new IdentityProviderException("Unable to query roles for client " + webClientId, rce); + } + + return userList; + } + + + + protected List requestUsersByClientRoleAndTenantId(){ + List userList = new ArrayList<>(); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Map claims = null; + if (authentication instanceof JwtAuthenticationToken) { + claims = ((JwtAuthenticationToken)authentication).getToken().getClaims(); + } else if (authentication.getPrincipal() instanceof OidcUser) { + claims = ((OidcUser)authentication.getPrincipal()).getClaims(); + } + String tenantKey = null; + if (claims != null && claims.containsKey("tenantKey")) { + tenantKey = claims.get("tenantKey").toString(); + } + // IF tenantKey is null, it means user is logging into camunda cockpit. Return empty list. + if (tenantKey == null) + return userList; + + try { + // get Keycloak specific userID + String keycloakClientID; + try { + keycloakClientID = getKeycloakClientID(tenantKey+"-"+webClientId); + } catch (KeycloakUserNotFoundException e) { + // user not found: empty search result + return Collections.emptyList(); + } + + // get groups of this user + ResponseEntity response = restTemplate.exchange(keycloakConfiguration.getKeycloakAdminUrl() + + "/clients/" + keycloakClientID + "/roles/formsflow-reviewer/users", HttpMethod.GET, + String.class); + if (!response.getStatusCode().equals(HttpStatus.OK)) { + throw new IdentityProviderException( + "Unable to read user data from " + keycloakConfiguration.getKeycloakAdminUrl() + + ": HTTP status code " + response.getStatusCodeValue()); + } + + JsonArray searchResult = parseAsJsonArray(response.getBody()); + for (int i = 0; i < searchResult.size(); i++) { + User user = transformUser(getJsonObjectAtIndex(searchResult, i),tenantKey); + userList.add(user); + } + + } catch (HttpClientErrorException hcee) { + // if userID is unknown server answers with HTTP 404 not found + if (hcee.getStatusCode().equals(HttpStatus.NOT_FOUND)) { + return Collections.emptyList(); + } + throw hcee; + } catch (RestClientException | JsonException rce) { + throw new IdentityProviderException("Unable to query roles for client " + webClientId, rce); + } + return userList; + } + + + /** + * Gets the Keycloak internal ID of client. + * + * @param clientId the client ID + * @return the Keycloak internal ID + * @throws KeycloakUserNotFoundException in case the user cannot be found + * @throws RestClientException in case of technical errors + */ + protected String getKeycloakClientID(String clientId) throws KeycloakUserNotFoundException, RestClientException { + try { + ResponseEntity response = restTemplate.exchange( + keycloakConfiguration.getKeycloakAdminUrl() + "/clients?clientId="+clientId, HttpMethod.GET, + String.class); + JsonArray resultList = parseAsJsonArray(response.getBody()); + JsonObject result = resultList.get(0).getAsJsonObject(); + if (result != null) { + return getJsonString(result, "id"); + } + throw new KeycloakUserNotFoundException(clientId + ": Client Not found"); + } catch (JsonException je) { + throw new KeycloakUserNotFoundException(clientId + ": Client Not found"); + } + } + + private UserEntity transformUser(JsonObject result, String prefix) throws JsonException { UserEntity user = new UserEntity(); String userId = getJsonString(result, "username"); + if(prefix != null){ + if(userId.contains(prefix)){ + userId = StringUtils.substringAfter(userId, prefix+"-"); + } + } JsonObject attributes = getJsonObject(result, "attributes"); if(attributes != null) { JsonArray userIds = attributes.getAsJsonArray("userid"); @@ -120,4 +277,4 @@ private UserEntity transformUser(JsonObject result) throws JsonException { user.setLastName(lastName); return user; } -} \ No newline at end of file +} diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/rest/RestApiSecurityConfig.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/rest/RestApiSecurityConfig.java index be500b74..309be0d4 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/rest/RestApiSecurityConfig.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/keycloak/rest/RestApiSecurityConfig.java @@ -24,7 +24,6 @@ import javax.ws.rs.HttpMethod; import java.util.Properties; - /** * Optional Security Configuration for Camunda REST Api. */ @@ -61,11 +60,12 @@ public void configure(final HttpSecurity http) throws Exception { String jwkSetUri = applicationContext.getEnvironment().getRequiredProperty( "spring.security.oauth2.client.provider." + configProps.getProvider() + ".jwk-set-uri"); - http.requestMatchers().antMatchers("/engine-rest/**","/engine-rest-ext/**","/forms-flow-bpm-socket/**", "/actuator/**"). - and().authorizeRequests().antMatchers(HttpMethod.OPTIONS,"/engine-rest/**").permitAll() - .and().authorizeRequests().antMatchers(HttpMethod.OPTIONS,"/engine-rest-ext/**").permitAll() - .and().authorizeRequests().antMatchers(HttpMethod.OPTIONS,"/forms-flow-bpm-socket/**").permitAll() - .antMatchers("/engine-rest/**","/engine-rest-ext/**") + http.requestMatchers() + .antMatchers("/engine-rest/**", "/engine-rest-ext/**", "/forms-flow-bpm-socket/**", "/actuator/**").and() + .authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/engine-rest/**").permitAll() + .and().authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/engine-rest-ext/**").permitAll() + .and().authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/forms-flow-bpm-socket/**").permitAll() + .antMatchers("/engine-rest/**", "/engine-rest-ext/**") .authenticated().and().csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() @@ -74,9 +74,9 @@ public void configure(final HttpSecurity http) throws Exception { .jwkSetUri(jwkSetUri); } - /** * Create a JWT decoder with issuer and audience claim validation. + * * @return the JWT decoder */ @Bean @@ -84,8 +84,7 @@ public JwtDecoder jwtDecoder() { String issuerUri = applicationContext.getEnvironment().getRequiredProperty( "spring.security.oauth2.client.provider." + configProps.getProvider() + ".issuer-uri"); - NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) - JwtDecoders.fromOidcIssuerLocation(issuerUri); + NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromOidcIssuerLocation(issuerUri); OAuth2TokenValidator audienceValidator = new AudienceValidator(configProps.getRequiredAudience()); OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri); @@ -96,24 +95,25 @@ public JwtDecoder jwtDecoder() { return jwtDecoder; } - /** * Registers the REST Api Keycloak Authentication Filter. + * * @return filter registration */ @SuppressWarnings({ "unchecked", "rawtypes" }) @Bean - public FilterRegistrationBean keycloakAuthenticationFilter(){ + public FilterRegistrationBean keycloakAuthenticationFilter() { FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); filterRegistration.setFilter(new KeycloakAuthenticationFilter(identityService, clientService)); filterRegistration.setOrder(102); // make sure the filter is registered after the Spring Security Filter Chain filterRegistration.addUrlPatterns("/engine-rest/*"); + filterRegistration.addUrlPatterns("/engine-rest-ext/*"); return filterRegistration; } @Bean public OAuth2RestTemplate getOAuth2RestTemplate() { - ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails (); + ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails(); resourceDetails.setClientId(clientCredentialProperties.getProperty("registration.keycloak.client-id")); resourceDetails.setClientSecret(clientCredentialProperties.getProperty("registration.keycloak.client-secret")); resourceDetails.setAccessTokenUri(clientCredentialProperties.getProperty("provider.keycloak.token-uri")); diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/main/resources/application.yaml b/apps/forms-flow-ai/forms-flow-bpm/src/main/resources/application.yaml index e37f43a5..004ba795 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/main/resources/application.yaml +++ b/apps/forms-flow-ai/forms-flow-bpm/src/main/resources/application.yaml @@ -9,17 +9,6 @@ formbuilder.pipeline.service.username: ${CAMUNDA_FORMBUILDER_PIPELINE_USERNAME} formbuilder.pipeline.service.password: ${CAMUNDA_FORMBUILDER_PIPELINE_PASSWORD} formbuilder.pipeline.service.bpm-url: ${CAMUNDA_FORMBUILDER_PIPELINE_BPM_URL} -spring.datasource: - jdbc-url: ${CAMUNDA_JDBC_URL:jdbc:h2:./camunda-db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE} - username: ${CAMUNDA_JDBC_USER:sa} - password: ${CAMUNDA_JDBC_PASSWORD:sa} - driverClassName: ${CAMUNDA_JDBC_DRIVER:org.h2.Driver} - type: com.zaxxer.hikari.HikariDataSource - connectionTimeout: ${CAMUNDA_HIKARI_CONN_TIMEOUT:30000} - idleTimeout: ${CAMUNDA_HIKARI_IDLE_TIMEOUT:600000} - maximumPoolSize: ${CAMUNDA_HIKARI_MAX_POOLSIZE:10} - validationTimeout: ${CAMUNDA_HIKARI_VALID_TIMEOUT:5000} - analytics.datasource: jdbc-url: ${CAMUNDA_JDBC_URL} username: ${CAMUNDA_JDBC_USER} @@ -32,9 +21,27 @@ analytics.datasource: validationTimeout: ${CAMUNDA_HIKARI_VALID_TIMEOUT:5000} +server: + error: + include-message: always + port: 8080 + servlet: + context-path: /camunda + session: + cookie: + secure: ${SESSION_COOKIE_SECURE:true} + max-age: 1800 + http-only: true + formsflow.ai: + forms: + enableCustomSubmission: ${CUSTOM_SUBMISSION_ENABLED:false} + custom_submission: + url: ${CUSTOM_SUBMISSION_URL} api: url: ${FORMSFLOW_API_URL} + analysis: + url: ${DATA_ANALYSIS_URL} formio: url: ${FORMIO_URL} security: @@ -47,45 +54,7 @@ formsflow.ai: maxInMemorySize: ${DATA_BUFFER_SIZE} connectionTimeout: 5000 - -# session: -# jdbc.initialize-schema: always -# store-type: ${CAMUNDA_SESSION_STORE_TYPE:none} -# timeout.seconds: ${CAMUNDA_SESSION_STORE_TIMEOUT:28800} - -management: - health: - db: - enabled: true - endpoints: - web: - exposure: - include: "health,info,loggers" - endpoint: - loggers: - enabled: true - -info: - app: - name: "Camunda" - description: "formsflow.ai Engine" - version: "7.15" - java: - version: "11" - -# Enable the below given block for session management of camunda. This is not required for externalised tasklist. -#session.datasource: -# jdbc-url: ${CAMUNDA_SESSION_JDBC_URL:jdbc:h2:./camunda-db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE} -# username: ${CAMUNDA_SESSION_JDBC_USER:sa} -# password: ${CAMUNDA_SESSION_JDBC_PASSWORD:sa} -# driverClassName: ${CAMUNDA_SESSION_JDBC_DRIVER:org.h2.Driver} -# type: com.zaxxer.hikari.HikariDataSource -# connectionTimeout: ${CAMUNDA_SESSION_HIKARI_CONN_TIMEOUT:30000} -# idleTimeout: ${CAMUNDA_SESSION_HIKARI_IDLE_TIMEOUT:600000} -# maximumPoolSize: ${CAMUNDA_SESSION_HIKARI_MAX_POOLSIZE:10} -# validationTimeout: ${CAMUNDA_SESSION_HIKARI_VALID_TIMEOUT:5000} - -camunda.bpm: +camunda.bpm: history-level: ${CAMUNDA_BPM_HISTORY_LEVEL:none} authorization: enabled: ${CAMUNDA_AUTHORIZATION_FLAG:true} @@ -95,7 +64,7 @@ camunda.bpm: webapp: application-path: / csrf: - enable-secure-cookie: true + enable-secure-cookie: ${SESSION_COOKIE_SECURE:true} header-security: content-security-policy-disabled: false content-security-policy-value: base-uri 'self'; @@ -105,6 +74,7 @@ camunda.bpm: frame-ancestors 'none'; object-src 'none' job-execution: + enabled: true core-pool-size: ${CAMUNDA_JOB_CORE_POOL_SIZE:3} lock-time-in-millis: ${CAMUNDA_JOB_LOCK_TIME_MILLIS:300000} max-jobs-per-acquisition: ${CAMUNDA_JOB_MAXJOBS_PER_ACQUISITION:3} @@ -114,25 +84,21 @@ camunda.bpm: max-wait: ${CAMUNDA_JOB_MAX_WAIT:60000} metrics: enabled: ${CAMUNDA_METRICS_FLAG:true} -server: - error: - include-message: always - port: 8080 - servlet.context-path: /camunda - session: - cookie: - secure: true -# Camunda Rest API -rest.security: - enabled: true - provider: keycloak - required-audience: camunda-rest-api - spring: - jersey: - application-path: /engine-rest + datasource: + jdbc-url: ${CAMUNDA_JDBC_URL:jdbc:h2:./camunda-db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE} + username: ${CAMUNDA_JDBC_USER:sa} + password: ${CAMUNDA_JDBC_PASSWORD:sa} + driverClassName: ${CAMUNDA_JDBC_DRIVER:org.h2.Driver} + type: com.zaxxer.hikari.HikariDataSource + minimum-idle: 10 + connectionTimeout: ${CAMUNDA_HIKARI_CONN_TIMEOUT:30000} + idleTimeout: ${CAMUNDA_HIKARI_IDLE_TIMEOUT:600000} + maximumPoolSize: ${CAMUNDA_HIKARI_MAX_POOLSIZE:10} + validationTimeout: ${CAMUNDA_HIKARI_VALID_TIMEOUT:5000} + security: oauth2: client: @@ -167,13 +133,13 @@ spring: main: allow-bean-definition-overriding: true -logging: - level: - org.springframework.security: ${CAMUNDA_APP_ROOT_LOG_FLAG:ERROR} - org.springframework.web: ${CAMUNDA_APP_ROOT_LOG_FLAG:ERROR} - org.springframework.jdbc: ${CAMUNDA_APP_ROOT_LOG_FLAG:ERROR} - org.camunda: ${CAMUNDA_APP_ROOT_LOG_FLAG:ERROR} - org.camunda.bpm.extension: ${CAMUNDA_APP_ROOT_LOG_FLAG:ERROR} + +# Camunda Rest API +rest.security: + enabled: true + provider: keycloak + required-audience: camunda-rest-api + plugin.identity.keycloak.rest: userNameClaim: preferred_username @@ -207,3 +173,53 @@ websocket: port: ${WEBSOCKET_BROKER_PORT} passcode: ${WEBSOCKET_BROKER_PASSCODE} +# disable redis +spring.data.redis.repositories.enabled: false +spring.autoconfigure.exclude: + - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration + +# session: +# jdbc.initialize-schema: always +# store-type: ${CAMUNDA_SESSION_STORE_TYPE:none} +# timeout.seconds: ${CAMUNDA_SESSION_STORE_TIMEOUT:28800} + +management: + health: + db: + enabled: true + endpoints: + web: + exposure: + include: "health,info,loggers" + endpoint: + loggers: + enabled: true + +info: + app: + name: "Camunda" + description: "formsflow.ai Engine" + version: "7.17" + java: + version: "17" + + +logging: + level: + org.springframework.security: ${CAMUNDA_APP_ROOT_LOG_FLAG:ERROR} + org.springframework.web: ${CAMUNDA_APP_ROOT_LOG_FLAG:ERROR} + org.springframework.jdbc: ${CAMUNDA_APP_ROOT_LOG_FLAG:ERROR} + org.camunda: ${CAMUNDA_APP_ROOT_LOG_FLAG:ERROR} + org.camunda.bpm.extension: ${CAMUNDA_APP_ROOT_LOG_FLAG:ERROR} + +# Enable the below given block for session management of camunda. This is not required for externalised tasklist. +#session.datasource: +# jdbc-url: ${CAMUNDA_SESSION_JDBC_URL:jdbc:h2:./camunda-db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE} +# username: ${CAMUNDA_SESSION_JDBC_USER:sa} +# password: ${CAMUNDA_SESSION_JDBC_PASSWORD:sa} +# driverClassName: ${CAMUNDA_SESSION_JDBC_DRIVER:org.h2.Driver} +# type: com.zaxxer.hikari.HikariDataSource +# connectionTimeout: ${CAMUNDA_SESSION_HIKARI_CONN_TIMEOUT:30000} +# idleTimeout: ${CAMUNDA_SESSION_HIKARI_IDLE_TIMEOUT:600000} +# maximumPoolSize: ${CAMUNDA_SESSION_HIKARI_MAX_POOLSIZE:10} +# validationTimeout: ${CAMUNDA_SESSION_HIKARI_VALID_TIMEOUT:5000} \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/commons/connector/support/ApplicationAccessHandlerTest.java b/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/commons/connector/support/ApplicationAccessHandlerTest.java index c20f7207..37dc3eca 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/commons/connector/support/ApplicationAccessHandlerTest.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/commons/connector/support/ApplicationAccessHandlerTest.java @@ -2,11 +2,14 @@ import static org.junit.Assert.assertEquals; +import org.camunda.bpm.extension.commons.ro.req.IRequest; +import org.camunda.bpm.extension.commons.ro.res.IResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.OAuth2RestTemplate; @@ -17,7 +20,7 @@ import java.util.function.Consumer; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -28,47 +31,47 @@ @ExtendWith(SpringExtension.class) class ApplicationAccessHandlerTest { - @InjectMocks - private ApplicationAccessHandler applicationAccessHandler; + @InjectMocks + private ApplicationAccessHandler applicationAccessHandler; - @Mock - private WebClient unAuthenticatedWebClient; + @Mock + private WebClient unAuthenticatedWebClient; - @Mock - private OAuth2RestTemplate oAuth2RestTemplate; + @Mock + private OAuth2RestTemplate oAuth2RestTemplate; - /** - * This test perform a positive test over ApplicationAccessHandler - * This will validate the response entity is Success - */ - @Test - public void testExchangeSuccess() { - final String apiUrl = "http://localhost:5000/api/application/123"; - WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class); - when(unAuthenticatedWebClient.method(any(HttpMethod.class))) - .thenReturn(requestBodyUriSpec); - when(requestBodyUriSpec.uri(anyString())) - .thenReturn(requestBodyUriSpec); - when(requestBodyUriSpec.accept(any(MediaType.class))) - .thenReturn(requestBodyUriSpec); - when(requestBodyUriSpec.header(anyString(), anyString())) - .thenReturn(requestBodyUriSpec); - OAuth2AccessToken oAuth2AccessToken = mock(OAuth2AccessToken.class); - when(oAuth2RestTemplate.getAccessToken()) - .thenReturn(oAuth2AccessToken); - when(requestBodyUriSpec.headers(any(Consumer.class))) - .thenReturn(requestBodyUriSpec); - WebClient.RequestHeadersSpec requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class); - when(requestBodyUriSpec.body(any(Mono.class), any(Class.class))) - .thenReturn(requestHeadersSpec); - WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class); - when(requestHeadersSpec.retrieve()).thenReturn(responseSpec); - Mono> response = Mono.just(ResponseEntity.ok("Success")); - when(responseSpec.toEntity(String.class)) - .thenReturn(response); + /** + * This test perform a positive test over ApplicationAccessHandler + * This will validate the response entity is Success + */ + @Test + public void testExchangeSuccess() { + final String apiUrl = "http://localhost:5000/api/application/123"; + WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class); + when(unAuthenticatedWebClient.method(any(HttpMethod.class))) + .thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.uri(anyString())) + .thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.accept(any(MediaType.class))) + .thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.header(anyString(), anyString())) + .thenReturn(requestBodyUriSpec); + OAuth2AccessToken oAuth2AccessToken = mock(OAuth2AccessToken.class); + when(oAuth2RestTemplate.getAccessToken()) + .thenReturn(oAuth2AccessToken); + when(requestBodyUriSpec.headers(any(Consumer.class))) + .thenReturn(requestBodyUriSpec); + WebClient.RequestHeadersSpec requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class); + when(requestBodyUriSpec.body(any(Mono.class), any(Class.class))) + .thenReturn(requestHeadersSpec); + WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class); + when(requestHeadersSpec.retrieve()).thenReturn(responseSpec); + Mono> response = Mono.just(ResponseEntity.ok("Success")); + when(responseSpec.toEntity(String.class)) + .thenReturn(response); - ResponseEntity data = applicationAccessHandler.exchange(apiUrl, HttpMethod.GET, "{}"); - assertEquals(data.getBody(), "Success"); - } + ResponseEntity data = applicationAccessHandler.exchange(apiUrl, HttpMethod.GET, "{}"); + assertEquals(data.getBody(), "Success"); + } } \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/commons/io/event/CamundaEventListenerTest.java b/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/commons/io/event/CamundaEventListenerTest.java index c650e825..0333ecb5 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/commons/io/event/CamundaEventListenerTest.java +++ b/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/commons/io/event/CamundaEventListenerTest.java @@ -1,110 +1,128 @@ package org.camunda.bpm.extension.commons.io.event; +import com.fasterxml.jackson.databind.ObjectMapper; import org.camunda.bpm.engine.delegate.DelegateTask; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.springframework.data.redis.core.StringRedisTemplate; +//import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.util.ReflectionTestUtils; - +import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; +import static org.camunda.bpm.extension.commons.utils.VariableConstants.FORM_URL; +import static org.camunda.bpm.extension.commons.utils.VariableConstants.APPLICATION_STATUS; +import static org.camunda.bpm.extension.commons.utils.VariableConstants.APPLICATION_ID; /** + * Camunda EventListener Test. * Test class for CamundaEventListener */ @ExtendWith(SpringExtension.class) public class CamundaEventListenerTest { + @InjectMocks + public CamundaEventListener camundaEventListener; - @InjectMocks - public CamundaEventListener camundaEventListener; + @Mock + private StringRedisTemplate template; - @Mock - private StringRedisTemplate template; + // @Mock + // private SimpMessagingTemplate template; - /** - * Test perform a positive test over onTaskEventListener - * This test will validate the template - */ - @Test - public void onTaskEventListener_with_defaultEvents(){ - DelegateTask delegateTask = mock(DelegateTask.class); - when(delegateTask.getEventName()) - .thenReturn("create"); + @BeforeEach + public void setup() { + try { + Field field = camundaEventListener.getClass().getDeclaredField("bpmObjectMapper"); + field.setAccessible(true); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + ObjectMapper objectMapper = new ObjectMapper(); + ReflectionTestUtils.setField(this.camundaEventListener, "bpmObjectMapper", objectMapper); + } - ReflectionTestUtils.setField(camundaEventListener, "messageCategory", "TASK_EVENT_DETAILS,TASK_EVENT"); - ReflectionTestUtils.setField(camundaEventListener, "messageEvents", "ALL"); - Map variables = new HashMap<>(); - variables.put("applicationId" , "id1"); - variables.put("formUrl" , "http://localhost:3001"); - variables.put("applicationStatus" , "New"); - when(delegateTask.getVariables()) - .thenReturn(variables); - camundaEventListener.onTaskEventListener(delegateTask); - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(template, times(2)).convertAndSend(anyString(), captor.capture()); - assertEquals("{\"assignee\":null,\"createTime\":null,\"deleteReason\":null,\"description\":null,\"dueDate\":null,\"eventName\":\"create\",\"executionId\":null,\"followUpDate\":null,\"id\":null,\"name\":null,\"owner\":null,\"priority\":0,\"processDefinitionId\":null,\"processInstanceId\":null,\"taskDefinitionKey\":null,\"variables\":{\"applicationStatus\":null,\"formUrl\":null,\"applicationId\":null}}", - captor.getAllValues().get(0)); - assertEquals("{\"id\":null,\"eventName\":\"create\"}", captor.getAllValues().get(1)); - } + /** + * Test perform a positive test over onTaskEventListener + * This test will validate the template + */ + @Test + public void onTaskEventListener_with_defaultEvents() { + DelegateTask delegateTask = mock(DelegateTask.class); + when(delegateTask.getEventName()) + .thenReturn("create"); + ReflectionTestUtils.setField(camundaEventListener, "messageCategory", "TASK_EVENT_DETAILS,TASK_EVENT"); + ReflectionTestUtils.setField(camundaEventListener, "messageEvents", "ALL"); + Map variables = new HashMap<>(); + variables.put(APPLICATION_ID, "id1"); + variables.put(FORM_URL, "http://localhost:3001"); + variables.put(APPLICATION_STATUS, "New"); + when(delegateTask.getVariables()) + .thenReturn(variables); + camundaEventListener.onTaskEventListener(delegateTask); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(template, times(2)).convertAndSend(anyString(), captor.capture()); + assertEquals("{\"assignee\":null,\"createTime\":null,\"deleteReason\":null,\"description\":null,\"dueDate\":null,\"eventName\":\"create\",\"executionId\":null,\"followUpDate\":null,\"id\":null,\"name\":null,\"owner\":null,\"priority\":0,\"processDefinitionId\":null,\"processInstanceId\":null,\"taskDefinitionKey\":null,\"variables\":{\"applicationStatus\":null,\"formUrl\":null,\"applicationId\":null},\"tenantId\":null}", + captor.getAllValues().get(0)); + assertEquals("{\"id\":null,\"eventName\":\"create\",\"tenantId\":null}", captor.getAllValues().get(1)); + } - /** - * Test perform a positive test with default message events - * This test will validate the template - */ - @Test - public void onTaskEventListener_with_default_messageEvents(){ - DelegateTask delegateTask = mock(DelegateTask.class); - when(delegateTask.getEventName()) - .thenReturn("create"); + /** + * Test perform a positive test with default message events + * This test will validate the template + */ + @Test + public void onTaskEventListener_with_default_messageEvents() { + DelegateTask delegateTask = mock(DelegateTask.class); + when(delegateTask.getEventName()) + .thenReturn("create"); + ReflectionTestUtils.setField(camundaEventListener, "messageCategory", "TASK_EVENT_DETAILS,TASK_EVENT"); + ReflectionTestUtils.setField(camundaEventListener, "messageEvents", "DEFAULT"); + Map variables = new HashMap<>(); + variables.put(APPLICATION_ID, "id1"); + variables.put(FORM_URL, "http://localhost:3001"); + variables.put(APPLICATION_STATUS, "New"); + when(delegateTask.getVariables()) + .thenReturn(variables); + camundaEventListener.onTaskEventListener(delegateTask); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(template, times(2)).convertAndSend(anyString(), captor.capture()); + assertEquals("{\"assignee\":null,\"createTime\":null,\"deleteReason\":null,\"description\":null,\"dueDate\":null,\"eventName\":\"create\",\"executionId\":null,\"followUpDate\":null,\"id\":null,\"name\":null,\"owner\":null,\"priority\":0,\"processDefinitionId\":null,\"processInstanceId\":null,\"taskDefinitionKey\":null,\"variables\":{\"applicationStatus\":null,\"formUrl\":null,\"applicationId\":null},\"tenantId\":null}", + captor.getAllValues().get(0)); + assertEquals("{\"id\":null,\"eventName\":\"create\",\"tenantId\":null}", captor.getAllValues().get(1)); + } - ReflectionTestUtils.setField(camundaEventListener, "messageCategory", "TASK_EVENT_DETAILS,TASK_EVENT"); - ReflectionTestUtils.setField(camundaEventListener, "messageEvents", "DEFAULT"); - Map variables = new HashMap<>(); - variables.put("applicationId" , "id1"); - variables.put("formUrl" , "http://localhost:3001"); - variables.put("applicationStatus" , "New"); - when(delegateTask.getVariables()) - .thenReturn(variables); - camundaEventListener.onTaskEventListener(delegateTask); - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(template, times(2)).convertAndSend(anyString(), captor.capture()); - assertEquals("{\"assignee\":null,\"createTime\":null,\"deleteReason\":null,\"description\":null,\"dueDate\":null,\"eventName\":\"create\",\"executionId\":null,\"followUpDate\":null,\"id\":null,\"name\":null,\"owner\":null,\"priority\":0,\"processDefinitionId\":null,\"processInstanceId\":null,\"taskDefinitionKey\":null,\"variables\":{\"applicationStatus\":null,\"formUrl\":null,\"applicationId\":null}}", - captor.getAllValues().get(0)); - assertEquals("{\"id\":null,\"eventName\":\"create\"}", captor.getAllValues().get(1)); - } + /** + * Test perform a positive test with custom message events + * This test will validate the template + */ + @Test + public void onTaskEventListener_with_custom_messageEvents() { + DelegateTask delegateTask = mock(DelegateTask.class); + when(delegateTask.getEventName()) + .thenReturn("create"); + ReflectionTestUtils.setField(camundaEventListener, "messageCategory", "TASK_EVENT_DETAILS,TASK_EVENT"); + ReflectionTestUtils.setField(camundaEventListener, "messageEvents", "create"); + Map variables = new HashMap<>(); + variables.put(APPLICATION_ID, "id1"); + variables.put(FORM_URL, "http://localhost:3001"); + variables.put(APPLICATION_STATUS, "New"); + when(delegateTask.getVariables()) + .thenReturn(variables); + camundaEventListener.onTaskEventListener(delegateTask); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(template, times(2)).convertAndSend(anyString(), captor.capture()); + assertEquals( + "{\"assignee\":null,\"createTime\":null,\"deleteReason\":null,\"description\":null,\"dueDate\":null,\"eventName\":\"create\",\"executionId\":null,\"followUpDate\":null,\"id\":null,\"name\":null,\"owner\":null,\"priority\":0,\"processDefinitionId\":null,\"processInstanceId\":null,\"taskDefinitionKey\":null,\"variables\":{\"applicationStatus\":null,\"formUrl\":null,\"applicationId\":null},\"tenantId\":null}", + captor.getAllValues().get(0)); + assertEquals("{\"id\":null,\"eventName\":\"create\",\"tenantId\":null}", captor.getAllValues().get(1)); - /** - * Test perform a positive test with custom message events - * This test will validate the template - */ - @Test - public void onTaskEventListener_with_custom_messageEvents() { - DelegateTask delegateTask = mock(DelegateTask.class); - when(delegateTask.getEventName()) - .thenReturn("create"); + } - ReflectionTestUtils.setField(camundaEventListener, "messageCategory", "TASK_EVENT_DETAILS,TASK_EVENT"); - ReflectionTestUtils.setField(camundaEventListener, "messageEvents", "create"); - Map variables = new HashMap<>(); - variables.put("applicationId", "id1"); - variables.put("formUrl", "http://localhost:3001"); - variables.put("applicationStatus", "New"); - when(delegateTask.getVariables()) - .thenReturn(variables); - camundaEventListener.onTaskEventListener(delegateTask); - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(template, times(2)).convertAndSend(anyString(), captor.capture()); - assertEquals( - "{\"assignee\":null,\"createTime\":null,\"deleteReason\":null,\"description\":null,\"dueDate\":null,\"eventName\":\"create\",\"executionId\":null,\"followUpDate\":null,\"id\":null,\"name\":null,\"owner\":null,\"priority\":0,\"processDefinitionId\":null,\"processInstanceId\":null,\"taskDefinitionKey\":null,\"variables\":{\"applicationStatus\":null,\"formUrl\":null,\"applicationId\":null}}", - captor.getAllValues().get(0)); - assertEquals("{\"id\":null,\"eventName\":\"create\"}", captor.getAllValues().get(1)); - } - } \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/hooks/listeners/task/AccessGrantNotifyListenerTest.java b/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/hooks/listeners/task/AccessGrantNotifyListenerTest.java new file mode 100644 index 00000000..981ba946 --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/hooks/listeners/task/AccessGrantNotifyListenerTest.java @@ -0,0 +1,125 @@ +package org.camunda.bpm.extension.hooks.listeners.task; + +import org.camunda.bpm.engine.IdentityService; +import org.camunda.bpm.engine.ProcessEngine; +import org.camunda.bpm.engine.ProcessEngineServices; +import org.camunda.bpm.engine.RuntimeService; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.DelegateTask; +import org.camunda.bpm.engine.delegate.Expression; +import org.camunda.bpm.engine.identity.User; +import org.camunda.bpm.engine.identity.UserQuery; +import org.camunda.bpm.engine.task.IdentityLink; +import org.camunda.bpm.extension.hooks.listeners.stubs.IdentityStub; +import org.camunda.bpm.extension.hooks.listeners.stubs.UserStub; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(SpringExtension.class) +public class AccessGrantNotifyListenerTest { + + @InjectMocks + private AccessGrantNotifyListener accessGrantNotifyListener; + + @Mock + private DelegateTask delegateTask; + + @Mock + private DelegateExecution delegateExecution; + + @Mock + private Expression excludeGroup; + + @Mock + private Expression messageId; + + @Mock + private Expression category; + + @Mock + ProcessEngine processEngine; + + @Mock + IdentityService identityService; + + @Mock + ProcessEngineServices processEngineServices; + + @Mock + RuntimeService runtimeService; + + @Test + public void invoke_notify() { + String id = "id1"; + String exclusionGroup = "GROUP2"; + String assignee = ""; + Map variables = new HashMap<>(); + variables.put("key_notify_sent_to","GROUP3"); + Set identityList = new HashSet(); + identityList.add(new IdentityStub("1", "CANDIDATE", "GROUP1", "TASK1", "PROCESS1", "TENANT-1", "Honai")); + when(delegateTask.getCandidates()) + .thenReturn(identityList); + when(delegateTask.getExecution()) + .thenReturn(delegateExecution); + when(delegateTask.getExecution().getVariables()) + .thenReturn(variables); + when(delegateTask.getExecution().getVariable("isNotify")) + .thenReturn(true); + when(delegateTask.getId()) + .thenReturn(id); + when(excludeGroup.getValue(delegateTask.getExecution())) + .thenReturn(exclusionGroup); + when(delegateTask.getTaskDefinitionKey()) + .thenReturn("key"); + when(delegateTask.getAssignee()) + .thenReturn(assignee); + List userList = new ArrayList<>(); + userList.add(new UserStub("1", "John", "Honai", "john.honai@aot-technologies.com", "password")); + userList.add(new UserStub("2", "Peter", "Scots", "peter.scots@aot-technologies.com", "password")); + when(delegateExecution.getProcessEngine()) + .thenReturn(processEngine); + when(processEngine.getIdentityService()) + .thenReturn(identityService); + UserQuery userQuery = mock(UserQuery.class); + UserQuery userQuery1 = mock(UserQuery.class); + when(identityService.createUserQuery()) + .thenReturn(userQuery); + when(userQuery.memberOfGroup(anyString())) + .thenReturn(userQuery1); + when(userQuery1.list()) + .thenReturn(userList); + when(delegateExecution.getProcessEngineServices()) + .thenReturn(processEngineServices); + when(delegateExecution.getProcessEngineServices().getRuntimeService()) + .thenReturn(runtimeService); + when(messageId.getValue(delegateExecution)) + .thenReturn("id1"); + when(category.getValue(delegateExecution)) + .thenReturn("category1"); + + accessGrantNotifyListener.notify(delegateTask); + ArgumentCaptor messageIdCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor messageVariableCaptor = ArgumentCaptor.forClass(Map.class); + verify(runtimeService).startProcessInstanceByMessage(messageIdCaptor.capture(), + messageVariableCaptor.capture()); + Map eMessageVariables = new HashMap<>(); + eMessageVariables.put("to", "john.honai@aot-technologies.com,peter.scots@aot-technologies.com"); + eMessageVariables.put("name", "Team"); + eMessageVariables.put("category", "category1"); + eMessageVariables.put("taskid", "id1"); + eMessageVariables.put("key_notify_sent_to","GROUP3"); + assertEquals("id1", messageIdCaptor.getValue()); + assertEquals(eMessageVariables, messageVariableCaptor.getValue()); + } + +} diff --git a/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/hooks/listeners/task/TimeoutNotifyListenerTest.java b/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/hooks/listeners/task/TimeoutNotifyListenerTest.java new file mode 100644 index 00000000..8d2f523e --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/src/test/java/org/camunda/bpm/extension/hooks/listeners/task/TimeoutNotifyListenerTest.java @@ -0,0 +1,355 @@ +package org.camunda.bpm.extension.hooks.listeners.task; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.camunda.bpm.engine.IdentityService; +import org.camunda.bpm.engine.ProcessEngine; +import org.camunda.bpm.engine.ProcessEngineServices; +import org.camunda.bpm.engine.RuntimeService; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.DelegateTask; +import org.camunda.bpm.engine.delegate.Expression; +import org.camunda.bpm.engine.identity.User; +import org.camunda.bpm.engine.identity.UserQuery; +import org.camunda.bpm.engine.task.IdentityLink; +import org.camunda.bpm.extension.hooks.listeners.stubs.IdentityStub; +import org.camunda.bpm.extension.hooks.listeners.stubs.UserStub; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.camunda.bpm.extension.hooks.services.UserService; +import org.camunda.bpm.engine.impl.persistence.entity.UserEntity; + +/** + * Timeout Notify Listener Test. + * Test class for TimeoutNotifyListener + */ +@ExtendWith(SpringExtension.class) +public class TimeoutNotifyListenerTest { + + @InjectMocks + private TimeoutNotifyListener timeoutNotifyListener; + + @Mock + private Expression escalationGroup; + + @Mock + private Expression messageName; + + @Mock + private Expression currentDate; + + @Mock + private DelegateTask delegateTask; + + @Mock + private DelegateExecution delegateExecution; + + @Mock + private ProcessEngine processEngine; + + @Mock + private IdentityService identityService; + + @Mock + private UserService userService; + + @Mock + private UserQuery user; + + @Mock + private ProcessEngineServices processEngineServices; + + @Mock + private RuntimeService runtimeService; + + /** + * This test case perform a positive test over notify method in TimeoutNotifyListener + * Assignee is not blank and escalation true. + * This test will validate the send message. + */ + @Test + public void notify_with_DelegateTask_and_validateAssignee_escalation_assignee_not_blank() throws Exception { + when(currentDate.getValue(delegateTask)) + .thenReturn("2021-11-01T16:38:34.144+05:30"); + when(delegateTask.getExecution()) + .thenReturn(delegateExecution); + when(delegateTask.getExecution().getVariable(anyString())) + .thenReturn("2021-10-30T16:39:53.041+05:30"); + when(escalationGroup.getValue(delegateTask)) + .thenReturn("someGroup"); + + List userList = new ArrayList<>(); + userList.add(new UserStub("1", "John", "Honai", "john.honai@aot-technologies.com", "password")); + userList.add(new UserStub("2", "Peter", "Scots", "peter.scots@aot-technologies.com", "password")); + when(delegateExecution.getProcessEngine()) + .thenReturn(processEngine); + when(processEngine.getIdentityService()) + .thenReturn(identityService); + UserQuery userQuery = mock(UserQuery.class); + UserQuery userQuery1 = mock(UserQuery.class); + when(identityService.createUserQuery()) + .thenReturn(userQuery); + when(userQuery.memberOfGroup(anyString())) + .thenReturn(userQuery1); + when(userQuery1.list()) + .thenReturn(userList); + User userId = userList.get(0); + when(delegateTask.getAssignee()) + .thenReturn("assigneeId1"); + when(userQuery.userId(anyString())) + .thenReturn(user); + when(userService.searchUserByAttribute("userid","assigneeId1")) + .thenReturn(userId); + when(userQuery.userId(anyString()).singleResult()) + .thenReturn(userId); + when(delegateTask.getId()) + .thenReturn("taskId-1"); + + when(delegateExecution.getProcessEngineServices()) + .thenReturn(processEngineServices); + when(delegateExecution.getProcessEngineServices().getRuntimeService()) + .thenReturn(runtimeService); + Map variables = new HashMap<>(); + variables.put("_file", new Object()); + when(delegateExecution.getVariables()) + .thenReturn(variables); + when(messageName.getValue(delegateExecution)) + .thenReturn("Success"); + timeoutNotifyListener.notify(delegateTask); + + ArgumentCaptor messageNameCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor messageVariableCaptor = ArgumentCaptor.forClass(Map.class); + verify(runtimeService).startProcessInstanceByMessage(messageNameCaptor.capture(), + messageVariableCaptor.capture()); + Map eMessageVariables = new HashMap<>(); + eMessageVariables.put("cc", "john.honai@aot-technologies.com,peter.scots@aot-technologies.com"); + eMessageVariables.put("to", "john.honai@aot-technologies.com"); + eMessageVariables.put("category", "activity_escalation"); + eMessageVariables.put("taskid", "taskId-1"); + assertEquals("Success", messageNameCaptor.getValue()); + assertEquals(eMessageVariables, messageVariableCaptor.getValue()); + } + + /** + * This test case perform over notify method in TimeoutNotifyListener + * Assignee is blank and escalation true. + * This test will validate the send message. + */ + @Test + public void notify_with_DelegateTask_and_validateAssignee_escalation_and_assignee_blank() throws Exception { + when(currentDate.getValue(delegateTask)) + .thenReturn("2021-11-01T16:38:34.144+05:30"); + when(delegateTask.getExecution()) + .thenReturn(delegateExecution); + when(delegateTask.getExecution().getVariable(anyString())) + .thenReturn("2021-10-30T16:39:53.041+05:30"); + when(escalationGroup.getValue(delegateTask)) + .thenReturn("someGroup"); + + Set identityList = new HashSet(); + identityList.add(new IdentityStub("1", "CANDIDATE", "GROUP1", "TASK1", "PROCESS1", "TENANT-1", "Honai")); + when(delegateTask.getCandidates()) + .thenReturn(identityList); + + List userList = new ArrayList<>(); + userList.add(new UserStub("1", "John", "Honai", "john.honai@aot-technologies.com", "password")); + userList.add(new UserStub("2", "Peter", "Scots", "peter.scots@aot-technologies.com", "password")); + when(delegateExecution.getProcessEngine()) + .thenReturn(processEngine); + when(processEngine.getIdentityService()) + .thenReturn(identityService); + UserQuery userQuery = mock(UserQuery.class); + UserQuery userQuery1 = mock(UserQuery.class); + when(identityService.createUserQuery()) + .thenReturn(userQuery); + when(userQuery.memberOfGroup(anyString())) + .thenReturn(userQuery1); + when(userQuery1.list()) + .thenReturn(userList); + User userId = userList.get(0); + when(delegateTask.getAssignee()) + .thenReturn(""); + when(userQuery.userId(anyString())) + .thenReturn(user); + when(userQuery.userId(anyString()).singleResult()) + .thenReturn(userId); + when(delegateTask.getId()) + .thenReturn("taskId-1"); + when(delegateExecution.getProcessEngineServices()) + .thenReturn(processEngineServices); + when(delegateExecution.getProcessEngineServices().getRuntimeService()) + .thenReturn(runtimeService); + Map variables = new HashMap<>(); + variables.put("_file", new Object()); + when(delegateExecution.getVariables()) + .thenReturn(variables); + when(messageName.getValue(delegateExecution)) + .thenReturn("Success"); + timeoutNotifyListener.notify(delegateTask); + + ArgumentCaptor messageNameCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor messageVariableCaptor = ArgumentCaptor.forClass(Map.class); + verify(runtimeService).startProcessInstanceByMessage(messageNameCaptor.capture(), + messageVariableCaptor.capture()); + Map eMessageVariables = new HashMap<>(); + eMessageVariables.put("cc", "john.honai@aot-technologies.com,peter.scots@aot-technologies.com"); + eMessageVariables.put("to", "john.honai@aot-technologies.com,peter.scots@aot-technologies.com"); + eMessageVariables.put("category", "activity_escalation"); + eMessageVariables.put("taskid", "taskId-1"); + assertEquals("Success", messageNameCaptor.getValue()); + assertEquals(eMessageVariables, messageVariableCaptor.getValue()); + } + + /** + * This test case perform a positive test over notify method in TimeoutNotifyListener + * Assignee is not blank and reminder true. + * This test will validate the send message. + */ + @Test + public void notify_with_DelegateTask_and_validateAssignee_reminder_and_assignee_not_blank() throws Exception { + when(currentDate.getValue(delegateTask)) + .thenReturn("2021-10-30T16:51:29.115+05:30"); + when(delegateTask.getExecution()) + .thenReturn(delegateExecution); + when(delegateTask.getExecution().getVariable(anyString())) + .thenReturn("2021-10-31T16:51:29.183+05:30"); + when(escalationGroup.getValue(delegateTask)) + .thenReturn("someGroup"); + + List userList = new ArrayList<>(); + userList.add(new UserStub("1", "John", "Honai", "john.honai@aot-technologies.com", "password")); + userList.add(new UserStub("2", "Peter", "Scots", "peter.scots@aot-technologies.com", "password")); + when(delegateExecution.getProcessEngine()) + .thenReturn(processEngine); + when(processEngine.getIdentityService()) + .thenReturn(identityService); + UserQuery userQuery = mock(UserQuery.class); + UserQuery userQuery1 = mock(UserQuery.class); + when(identityService.createUserQuery()) + .thenReturn(userQuery); + when(userQuery.memberOfGroup(anyString())) + .thenReturn(userQuery1); + when(userQuery1.list()) + .thenReturn(userList); + User userId = userList.get(0); + when(delegateTask.getAssignee()) + .thenReturn("assigneeId1"); + when(userQuery.userId(anyString())) + .thenReturn(user); + when(userService.searchUserByAttribute("userid","assigneeId1")) + .thenReturn(userId); + when(userQuery.userId(anyString()).singleResult()) + .thenReturn(userId); + when(delegateTask.getId()) + .thenReturn("taskId-1"); + + when(delegateExecution.getProcessEngineServices()) + .thenReturn(processEngineServices); + when(delegateExecution.getProcessEngineServices().getRuntimeService()) + .thenReturn(runtimeService); + Map variables = new HashMap<>(); + variables.put("_file", new Object()); + when(delegateExecution.getVariables()) + .thenReturn(variables); + when(messageName.getValue(delegateExecution)) + .thenReturn("Success"); + timeoutNotifyListener.notify(delegateTask); + + ArgumentCaptor messageNameCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor messageVariableCaptor = ArgumentCaptor.forClass(Map.class); + verify(runtimeService).startProcessInstanceByMessage(messageNameCaptor.capture(), + messageVariableCaptor.capture()); + Map eMessageVariables = new HashMap<>(); + eMessageVariables.put("to", "john.honai@aot-technologies.com"); + eMessageVariables.put("category", "activity_reminder"); + eMessageVariables.put("taskid", "taskId-1"); + assertEquals("Success", messageNameCaptor.getValue()); + assertEquals(eMessageVariables, messageVariableCaptor.getValue()); + } + + /** + * This test case perform over notify method in TimeoutNotifyListener + * Assignee is blank and reminder true. + * This test will validate the send message. + */ + @Test + public void notify_with_DelegateTask_and_validateAssignee_reminder_and_assignee_blank() throws Exception { + when(currentDate.getValue(delegateTask)) + .thenReturn("2021-10-30T16:51:29.115+05:30"); + when(delegateTask.getExecution()) + .thenReturn(delegateExecution); + when(delegateTask.getExecution().getVariable(anyString())) + .thenReturn("2021-10-31T16:51:29.183+05:30"); + when(escalationGroup.getValue(delegateTask)) + .thenReturn("someGroup"); + + Set identityList = new HashSet(); + identityList.add(new IdentityStub("1", "CANDIDATE", "GROUP1", "TASK1", "PROCESS1", "TENANT-1", "Honai")); + when(delegateTask.getCandidates()) + .thenReturn(identityList); + + List userList = new ArrayList<>(); + userList.add(new UserStub("1", "John", "Honai", "john.honai@aot-technologies.com", "password")); + userList.add(new UserStub("2", "Peter", "Scots", "peter.scots@aot-technologies.com", "password")); + when(delegateExecution.getProcessEngine()) + .thenReturn(processEngine); + when(processEngine.getIdentityService()) + .thenReturn(identityService); + UserQuery userQuery = mock(UserQuery.class); + UserQuery userQuery1 = mock(UserQuery.class); + when(identityService.createUserQuery()) + .thenReturn(userQuery); + when(userQuery.memberOfGroup(anyString())) + .thenReturn(userQuery1); + when(userQuery1.list()) + .thenReturn(userList); + User userId = userList.get(0); + when(delegateTask.getAssignee()) + .thenReturn(""); + when(userQuery.userId(anyString())) + .thenReturn(user); + when(userQuery.userId(anyString()).singleResult()) + .thenReturn(userId); + when(delegateTask.getId()) + .thenReturn("taskId-1"); + + when(delegateExecution.getProcessEngineServices()) + .thenReturn(processEngineServices); + when(delegateExecution.getProcessEngineServices().getRuntimeService()) + .thenReturn(runtimeService); + Map variables = new HashMap<>(); + variables.put("_file", new Object()); + when(delegateExecution.getVariables()) + .thenReturn(variables); + when(messageName.getValue(delegateExecution)) + .thenReturn("Success"); + timeoutNotifyListener.notify(delegateTask); + + ArgumentCaptor messageNameCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor messageVariableCaptor = ArgumentCaptor.forClass(Map.class); + verify(runtimeService).startProcessInstanceByMessage(messageNameCaptor.capture(), + messageVariableCaptor.capture()); + Map eMessageVariables = new HashMap<>(); + eMessageVariables.put("to", "john.honai@aot-technologies.com,peter.scots@aot-technologies.com"); + eMessageVariables.put("category", "activity_reminder"); + eMessageVariables.put("taskid", "taskId-1"); + assertEquals("Success", messageNameCaptor.getValue()); + assertEquals(eMessageVariables, messageVariableCaptor.getValue()); + } + +} diff --git a/apps/forms-flow-ai/forms-flow-web/src/apiManager/services/bpmTaskServices.js b/apps/forms-flow-ai/forms-flow-web/src/apiManager/services/bpmTaskServices.js index db981638..bf81dd47 100644 --- a/apps/forms-flow-ai/forms-flow-web/src/apiManager/services/bpmTaskServices.js +++ b/apps/forms-flow-ai/forms-flow-web/src/apiManager/services/bpmTaskServices.js @@ -1,3 +1,4 @@ + /* istanbul ignore file */ import {httpGETRequest, httpPOSTRequest, httpPUTRequest, httpPOSTRequestWithHAL } from "../httpRequestHandler"; import API from "../endpoints"; @@ -36,29 +37,22 @@ let responseData = res.data; const _embedded = responseData['_embedded']; // data._embedded.task is where the task list is. if (!_embedded || !_embedded['task'] || !responseData['count']) { - if (responseData['count'] !== undefined && responseData['count'] === 0) { - const tasks = [] - dispatch(setBPMTaskCount(0)); - dispatch(setBPMTaskList(tasks)); - dispatch(setBPMTaskLoader(false)); - done(null, tasks); - } else { - // Display error if the necessary values are unavailable. - console.log("Error", res); - dispatch(serviceActionError(res)); - dispatch(setBPMTaskLoader(false)); - } + console.log("Error", res); + dispatch(setBPMTaskList([])); + dispatch(setBPMTaskCount(0)); + dispatch(serviceActionError(res)); + dispatch(setBPMTaskLoader(false)); } else { const taskListFromResponse = _embedded['task']; // Gets the task array const taskCount = { count: responseData['count'] }; let taskData = taskListFromResponse; - if(taskIdToRemove){ - console.log("task----",taskIdToRemove); + if (taskIdToRemove) { + console.log("task----", taskIdToRemove); //if the list has the task with taskIdToRemove remove that task and decrement - if(taskListFromResponse.find((task)=>task.id===taskIdToRemove)){ - taskData=taskListFromResponse.filter( (task)=>task.id!==taskIdToRemove); + if (taskListFromResponse.find((task) => task.id === taskIdToRemove)) { + taskData = taskListFromResponse.filter((task) => task.id !== taskIdToRemove); taskCount['count']--; // Count has to be decreased since one task id is removed. } } @@ -69,12 +63,16 @@ } } else { console.log("Error", res); + dispatch(setBPMTaskList([])); + dispatch(setBPMTaskCount(0)); dispatch(serviceActionError(res)); dispatch(setBPMTaskLoader(false)); } }) .catch((error) => { console.log("Error", error); + dispatch(setBPMTaskList([])); + dispatch(setBPMTaskCount(0)); dispatch(serviceActionError(error)); dispatch(setBPMTaskLoader(false)); done(error); @@ -94,6 +92,7 @@ } else { console.log("Error", res); dispatch(serviceActionError(res)); + dispatch(setBPMProcessList([])); //dispatch(setBPMTaskLoader(false)); } }) @@ -134,16 +133,16 @@ export const fetchUserListWithSearch = ({searchType,query},...rest) => { const done = rest.length ? rest[0] : () => {}; - const paramData={memberOfGroup:REVIEWER_GROUP}; + const paramData={memberOfGroup: REVIEWER_GROUP}; /*TODO search with query /user?lastNameLike=%${lastName}%&memberOfGroup=${group}*/ //let getReviewerUserListApi = `${API.GET_BPM_USER_LIST}?memberOfGroup=${REVIEWER_GROUP}`; if(searchType && query){ //getReviewerUserListApi = `${getReviewerUserListApi}&${searchType}=%${query||""}%` - paramData[searchType]=`%${query}%`; + paramData[searchType] = `${query}`; } return (dispatch) => { - httpGETRequest(API.GET_BPM_USER_LIST, paramData, UserService.getToken()) + httpGETRequest(API.GET_API_USER_LIST, paramData, UserService.getToken()) .then((res) => { if (res.data) { dispatch(setBPMUserList(res.data)); @@ -217,7 +216,10 @@ let taskDetail=responses[0].data; if(responses[1]?.data){ let taskDetailUpdates = responses[1]?.data; - taskDetail = {...taskDetail,...taskDetailVariableDataFormatter(taskDetailUpdates)}; + taskDetail = { + ...taskDetailVariableDataFormatter(taskDetailUpdates), + ...taskDetail, + }; } dispatch(setBPMTaskDetail(taskDetail)); @@ -445,4 +447,3 @@ }); }; }; - \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-web/src/components/ServiceFlow/details/TaskHeader.js b/apps/forms-flow-ai/forms-flow-web/src/components/ServiceFlow/details/TaskHeader.js new file mode 100644 index 00000000..8b19fe51 --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-web/src/components/ServiceFlow/details/TaskHeader.js @@ -0,0 +1,354 @@ +import React, { useEffect, useState } from "react"; +import { Row, Col } from "react-bootstrap"; +import { + getISODateTime, + getFormattedDateAndTime, + getProcessDataObjectFromList, +} from "../../../apiManager/services/formatterService"; +import { useDispatch, useSelector } from "react-redux"; +import DatePicker from "react-datepicker"; +import moment from "moment"; +import "react-datepicker/dist/react-datepicker.css"; +import "./../ServiceFlow.scss"; +import AddGroupModal from "./AddGroupModal"; +import { + claimBPMTask, + // fetchFilterList, + fetchServiceTaskList, + getBPMTaskDetail, + unClaimBPMTask, + updateAssigneeBPMTask, + updateBPMTask, +} from "../../../apiManager/services/bpmTaskServices"; +import { setBPMTaskDetailUpdating } from "../../../actions/bpmTaskActions"; +//import UserSelection from "./UserSelection"; +import UserSelectionDebounce from "./UserSelectionDebounce"; +import SocketIOService from "../../../services/SocketIOService"; +import { useTranslation } from "react-i18next"; + +const TaskHeader = React.memo(() => { + const task = useSelector((state) => state.bpmTasks.taskDetail); + const taskId = useSelector((state) => state.bpmTasks.taskId); + const processList = useSelector((state) => state.bpmTasks.processList); + const username = useSelector( + (state) => state.user?.userDetail?.preferred_username || "" + ); + const taskGroups = useSelector((state) => state.bpmTasks.taskGroups); + const selectedFilter = useSelector((state) => state.bpmTasks.selectedFilter); + const reqData = useSelector((state) => state.bpmTasks.listReqParams); + const firstResult = useSelector((state) => state.bpmTasks.firstResult); + const [followUpDate, setFollowUpDate] = useState(null); + const [dueDate, setDueDate] = useState(null); + const [showModal, setModal] = useState(false); + const [isEditAssignee, setIsEditAssignee] = useState(false); + const dispatch = useDispatch(); + const { t } = useTranslation(); + useEffect(() => { + const followUp = task?.followUp ? new Date(task?.followUp) : null; + setFollowUpDate(followUp); + }, [task?.followUp]); + + useEffect(() => { + const due = task?.due ? new Date(task?.due) : null; + setDueDate(due); + }, [task?.due]); + + const onClaim = () => { + dispatch(setBPMTaskDetailUpdating(true)); + dispatch( + // eslint-disable-next-line no-unused-vars + claimBPMTask(taskId, username, (err, response) => { + if (!err) { + /* '!SocketIOService.isConnected' commented out in all blocks below as when socket is connected, details are not sent as expected. + To be uncommented and used later when product team has released the fix for this issue */ + //if (!SocketIOService.isConnected()) { + dispatch(getBPMTaskDetail(taskId)); + }else { + dispatch(setBPMTaskDetailUpdating(false)); + } + + if(selectedFilter) { + dispatch( + fetchServiceTaskList(selectedFilter.id, firstResult, reqData) + ); + //} + } else { + dispatch(setBPMTaskDetailUpdating(false)); + } + })); + }; + + const onChangeClaim = (userId) => { + setIsEditAssignee(false); + if (userId && userId !== task.assignee) { + dispatch(setBPMTaskDetailUpdating(true)); + dispatch( + // eslint-disable-next-line no-unused-vars + updateAssigneeBPMTask(taskId, userId, (err, response) => { + if (!err) { + //if (!SocketIOService.isConnected()) { + //if (selectedFilter) { + dispatch(getBPMTaskDetail(taskId)); + } else { + dispatch(setBPMTaskDetailUpdating(false)); + } + + if(selectedFilter) { + dispatch( + fetchServiceTaskList(selectedFilter.id, firstResult, reqData) + ); + //} + + } else { + dispatch(setBPMTaskDetailUpdating(false)); + } + })); + } + }; + + const onUnClaimTask = () => { + dispatch(setBPMTaskDetailUpdating(true)); + dispatch( + // eslint-disable-next-line no-unused-vars + unClaimBPMTask(taskId, (err, response) => { + if (!err) { + //if (!SocketIOService.isConnected()) { + //if (selectedFilter) { + dispatch(getBPMTaskDetail(taskId)); + } else { + dispatch(setBPMTaskDetailUpdating(false)); + } + + if(selectedFilter){ + dispatch( + fetchServiceTaskList(selectedFilter.id, firstResult, reqData) + ); + //} + + } else { + dispatch(setBPMTaskDetailUpdating(false)); + } + })); + }; + + const onFollowUpDateUpdate = (followUpDate) => { + setFollowUpDate(followUpDate); + dispatch(setBPMTaskDetailUpdating(true)); + const updatedTask = { + ...task, + ...{ followUp: followUpDate ? getISODateTime(followUpDate) : null }, + }; + dispatch( + // eslint-disable-next-line no-unused-vars + updateBPMTask(taskId, updatedTask, (err, response) => { + if (!err) { + //if (!SocketIOService.isConnected()) { + dispatch(getBPMTaskDetail(taskId)); + dispatch( + fetchServiceTaskList(selectedFilter.id, firstResult, reqData) + ); + //} + } else { + dispatch(setBPMTaskDetailUpdating(false)); + } + }) + ); + }; + + const onDueDateUpdate = (dueDate) => { + setDueDate(dueDate); + dispatch(setBPMTaskDetailUpdating(true)); + const updatedTask = { + ...task, + ...{ due: dueDate ? getISODateTime(dueDate) : null }, + }; + dispatch( + // eslint-disable-next-line no-unused-vars + updateBPMTask(taskId, updatedTask, (err, response) => { + if (!err) { + //if (!SocketIOService.isConnected()) { + dispatch(getBPMTaskDetail(taskId)); + dispatch( + fetchServiceTaskList(selectedFilter.id, firstResult, reqData) + ); + //} + } else { + dispatch(setBPMTaskDetailUpdating(false)); + } + }) + ); + }; + + // eslint-disable-next-line no-unused-vars + const FollowUpDateInput = React.forwardRef(({ value, onClick }, ref) => { + return ( +
+ {" "} + {followUpDate ? ( + {moment(followUpDate).fromNow()} + ) : ( + t("Set follow-up Date") + )} +
+ ); + }); + + // eslint-disable-next-line no-unused-vars + const DueDateInput = React.forwardRef(({ value, onClick }, ref) => { + return ( +
+ {" "} + {dueDate ? ( + {moment(dueDate).fromNow()} + ) : ( + t("Set Due date") + )} +
+ ); + }); + + const getGroups = (groups) => { + return groups?.map((group) => group.groupId).join(", "); + }; + + return ( + <> + setModal(false)} + groups={taskGroups} + /> + {task?.name} + + + {" "} + { + getProcessDataObjectFromList(processList, task?.processDefinitionId) + ?.name + } + + + + + {t("Application ID")}# {task?.applicationId} + + + + + } + /> + + + } + /> + + setModal(true)} + data-title={t("groups")} + > + + {taskGroups.length === 0 ? ( + {t("Add groups")} + ) : ( + {getGroups(taskGroups)} + )} + + + {isEditAssignee ? ( + task?.assignee ? ( + + setIsEditAssignee(false)} + currentUser={task.assignee} + onChangeClaim={onChangeClaim} + /> + + ) : ( + + {" "} + {t("Claim")} + + ) + ) : ( + <> + + {task?.assignee ? ( + + setIsEditAssignee(true)} + data-title={t("Click to Change Assignee")} + > + {task.assignee} + + + + ) : ( + + {t("Claim")} + + )} + + )} + + + + ); +}); + +export default TaskHeader; diff --git a/apps/forms-flow-ai/forms-flow-web/src/components/ServiceFlow/filter/ServiceTaskFilterListDropDown.js b/apps/forms-flow-ai/forms-flow-web/src/components/ServiceFlow/filter/ServiceTaskFilterListDropDown.js index f5f5e567..e7569710 100644 --- a/apps/forms-flow-ai/forms-flow-web/src/components/ServiceFlow/filter/ServiceTaskFilterListDropDown.js +++ b/apps/forms-flow-ai/forms-flow-web/src/components/ServiceFlow/filter/ServiceTaskFilterListDropDown.js @@ -4,6 +4,8 @@ import {useDispatch, useSelector} from "react-redux"; import {setSelectedBPMFilter, setSelectedTaskID} from "../../../actions/bpmTaskActions"; import {Link} from "react-router-dom"; /*import {Link} from "react-router-dom";*/ +import { useTranslation } from "react-i18next"; +import { MULTITENANCY_ENABLED } from "../../../constants/constants"; const ServiceFlowFilterListDropDown = React.memo(() => { @@ -11,6 +13,10 @@ const ServiceFlowFilterListDropDown = React.memo(() => { const filterList = useSelector(state=> state.bpmTasks.filterList); const isFilterLoading = useSelector(state=> state.bpmTasks.isFilterLoading); const selectedFilter=useSelector(state=>state.bpmTasks.selectedFilter); + const { t } = useTranslation(); + const tenantKey = useSelector((state) => state.tenants?.tenantId); + const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/"; + const changeFilterSelection = (filter)=>{ dispatch(setSelectedBPMFilter(filter)); @@ -26,7 +32,7 @@ const ServiceFlowFilterListDropDown = React.memo(() => { changeFilterSelection(filter)}> { return ( - No Filters Found + {t("No Filters Found")} ) } - } - return <> - {isFilterLoading? Loading...: renderFilterList()} + }; + return ( + <> + {isFilterLoading ? ( + {t("Loading")}... + ) : ( + renderFilterList() + )} + ); }); -export default ServiceFlowFilterListDropDown; +export default ServiceFlowFilterListDropDown; \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-web/src/constants/groupConstants.js b/apps/forms-flow-ai/forms-flow-web/src/constants/groupConstants.js new file mode 100644 index 00000000..3782d41b --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-web/src/constants/groupConstants.js @@ -0,0 +1,4 @@ +export const GROUPS={ + applicationsAccess:["/formsflow/access-allow-applications","/formsflow/formsflow-client/access-allow-applications"], + viewSubmissionsAccess:["/formsflow/access-allow-submissions"] + }; \ No newline at end of file diff --git a/apps/forms-flow-ai/forms-flow-web/src/containers/NavBar.jsx b/apps/forms-flow-ai/forms-flow-web/src/containers/NavBar.jsx index 42f231a9..0832ca78 100644 --- a/apps/forms-flow-ai/forms-flow-web/src/containers/NavBar.jsx +++ b/apps/forms-flow-ai/forms-flow-web/src/containers/NavBar.jsx @@ -1,34 +1,84 @@ -import React from "react"; +import React, { useEffect, useMemo, useState } from "react"; import {Navbar, Dropdown, Container, Nav, NavDropdown} from "react-bootstrap"; import {Link, useLocation} from "react-router-dom"; import {useDispatch, useSelector} from "react-redux"; import UserService from "../services/UserService"; import {getUserRoleName, getUserRolePermission, getUserInsightsPermission} from "../helper/user"; +import createURLPathMatchExp from "../helper/regExp/pathMatch"; +import { useTranslation } from "react-i18next"; import "./styles.scss"; -import {CLIENT, STAFF_REVIEWER, APPLICATION_NAME, STAFF_DESIGNER} from "../constants/constants"; +import {CLIENT, STAFF_REVIEWER, APPLICATION_NAME, STAFF_DESIGNER, MULTITENANCY_ENABLED} from "../constants/constants"; import ServiceFlowFilterListDropDown from "../components/ServiceFlow/filter/ServiceTaskFilterListDropDown"; import {push} from "connected-react-router"; +import i18n from "../resourceBundles/i18n"; +import { setLanguage } from "../actions/languageSetAction"; +import { updateUserlang } from "../apiManager/services/userservices"; + +import { fetchSelectLanguages } from "../apiManager/services/languageServices"; const NavBar = React.memo(() => { const isAuthenticated = useSelector((state) => state.user.isAuthenticated); const location = useLocation(); const { pathname } = location; const user = useSelector((state) => state.user.userDetail); + const lang = useSelector((state) => state.user.lang); const userRoles = useSelector((state) => state.user.roles); const showApplications= useSelector((state) => state.user.showApplications); + const applicationTitle = useSelector( + (state) => state.tenants?.tenantData?.details?.applicationTitle + ); + const tenantKey = useSelector((state) => state.tenants?.tenantId); + const formTenant = useSelector((state)=>state.form?.form?.tenantKey); + const baseUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/"; + /** + * For anonymous forms the only way to identify the tenant is through the + * form data with current implementation. To redirect to the correact tenant + * we will use form as the data source for the tenantKey + */ + + const [loginUrl, setLoginUrl] = useState(baseUrl); + const selectLanguages = useSelector((state) => state.user.selectLanguages); const dispatch = useDispatch(); const logoPath = "/logo.svg"; - const appName = APPLICATION_NAME; + const getAppName = useMemo( + () => () => { + if (!MULTITENANCY_ENABLED) { + return APPLICATION_NAME; + } + // TODO: Need a propper fallback component prefered a skeleton. + return applicationTitle || ""; + }, + [MULTITENANCY_ENABLED, applicationTitle] + ); + const appName = getAppName(); + const { t } = useTranslation(); + useEffect(()=>{ + if(!isAuthenticated && formTenant && MULTITENANCY_ENABLED){ + setLoginUrl(`/tenant/${formTenant}/`); + } + },[isAuthenticated, formTenant]); + + useEffect(() => { + dispatch(fetchSelectLanguages()); + }, [dispatch]); + useEffect(() => { + i18n.changeLanguage(lang); + }, [lang]); + + const handleOnclick = (selectedLang) => { + dispatch(setLanguage(selectedLang)); + dispatch(updateUserlang(selectedLang)); + }; const logout = () => { - dispatch(push(`/`)); - UserService.userLogout(); - } + dispatch(push(baseUrl)); + UserService.userLogout(); + }; const goToTask = () => { - dispatch(push(`/task`)); - } + dispatch(push(`${baseUrl}task`)); + }; return (
@@ -67,61 +117,61 @@ const NavBar = React.memo(() => { {isAuthenticated?