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..83111883 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/Dockerfile +++ b/apps/forms-flow-ai/forms-flow-bpm/Dockerfile @@ -1,38 +1,47 @@ # 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/settings-docker.xml /usr/share/maven/ref/ -COPY ./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/ +# COPY ./pom.xml /tmp/pom.xml +# COPY ./pom-default.xml /tmp/pom-default.xml +COPY ./pom*.xml /tmp/ +COPY ./settings-docker.xml /usr/share/maven/ref/ # COPY ./settings-docker.xml /usr/share/maven/ref/ -WORKDIR /tmp/ +WORKDIR /tmp # This allows Docker to cache most of the maven dependencies 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/ +RUN cp -rf /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 +RUN mvn -s /usr/share/maven/ref/settings-docker.xml package -P default +# RUN mvn -s /usr/share/maven/ref/settings-docker.xml package + # 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 +52,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-default.xml b/apps/forms-flow-ai/forms-flow-bpm/pom-default.xml new file mode 100644 index 00000000..f22747cc --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/pom-default.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + + org.camunda.bpm.extension + formsflow-bpm + 5.1.0 + pom.xml + + + default + + + + \ 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.xml similarity index 78% rename from apps/forms-flow-ai/forms-flow-bpm/pom-docker.xml rename to apps/forms-flow-ai/forms-flow-bpm/pom.xml index 7392e00b..406af905 100644 --- a/apps/forms-flow-ai/forms-flow-bpm/pom-docker.xml +++ b/apps/forms-flow-ai/forms-flow-bpm/pom.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 @@ -71,13 +73,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 +123,7 @@ spring-security-oauth2-jose - + org.camunda.bpm.extension camunda-bpm-identity-keycloak @@ -179,8 +186,8 @@ org.postgresql postgresql - - 42.2.5 + + 42.4.3 @@ -249,7 +256,7 @@ org.jsoup jsoup - 1.13.1 + 1.15.3 @@ -290,15 +297,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 +350,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 +374,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 +402,7 @@ spring-boot-maven-plugin ${version.springBoot} + org.camunda.bpm.extension.CamundaApplication ZIP @@ -389,37 +427,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/settings-docker.xml b/apps/forms-flow-ai/forms-flow-bpm/settings-docker.xml new file mode 100644 index 00000000..f0a1a600 --- /dev/null +++ b/apps/forms-flow-ai/forms-flow-bpm/settings-docker.xml @@ -0,0 +1,27 @@ + + + + + + + + + defaultProfile + + true + + + + + \ 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..cfc9e0ca 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,6 +1,8 @@ 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; @@ -8,46 +10,61 @@ 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 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 { private final Logger LOGGER = LoggerFactory.getLogger(ApplicationAccessHandler.class); - + @Autowired private WebClient unAuthenticatedWebClient; @Autowired private OAuth2RestTemplate oAuth2RestTemplate; - public ResponseEntity exchange(String url, HttpMethod method, String payload) { payload = (payload == null) ? new JsonObject().toString() : payload; - Mono> entityMono = unAuthenticatedWebClient.method(method).uri(url) + 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); + .toEntity(String.class) + .block(); - ResponseEntity response = entityMono.block(); + // ResponseEntity response = entityMono.block(); return new ResponseEntity<>(response.getBody(), response.getStatusCode()); } + public ResponseEntity exchange(String url, HttpMethod method, IRequest payload, + Class responseClazz) { + + 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()); + } } \ 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/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..ac174547 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 @@ -8,22 +8,28 @@ import org.camunda.bpm.extension.commons.io.ITaskEvent; import org.camunda.bpm.extension.commons.io.socket.message.TaskEventMessage; import org.camunda.bpm.extension.commons.io.socket.message.TaskMessage; -import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.event.EventListener; - import org.springframework.data.redis.core.StringRedisTemplate; +//import org.springframework.messaging.simp.SimpMessagingTemplate; 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. + * Camunda Event Listener. + * This class intercepts all camunda task and push socket messages for web tier + * updates. * * @author sumathi.thirumani@aot-technologies.com */ @@ -33,32 +39,57 @@ public class CamundaEventListener implements ITaskEvent { @Autowired private StringRedisTemplate stringRedisTemplate; + // @Autowired + // private SimpMessagingTemplate template; + private final Logger LOGGER = Logger.getLogger(CamundaEventListener.class.getName()); @Value("${websocket.messageType}") private String messageCategory; - - @Value("${websocket.messageEvents}") + + @Value("${websocket.messageEvents}") private String messageEvents; + @Value("${websocket.enableRedis}") + private boolean redisEnabled; + + @Resource(name = "bpmObjectMapper") + private ObjectMapper bpmObjectMapper; @EventListener public void onTaskEventListener(DelegateTask taskDelegate) { - LOGGER.info("Event triggered:"+taskDelegate.getId() +"-"+ taskDelegate.getEventName() + "-"+ taskDelegate.getProcessInstanceId()); + LOGGER.info("Event triggered:" + taskDelegate.getId() + "-" + taskDelegate.getEventName() + "-" + + taskDelegate.getProcessInstanceId()); try { - if (isRegisteredEvent(taskDelegate.getEventName())) { - if(isAllowed(EventCategory.TASK_EVENT_DETAILS.name())) { - this.stringRedisTemplate.convertAndSend(getTopicNameForTaskDetail(), getObjectMapper().writeValueAsString(getTaskMessage(taskDelegate))); - } - if(isAllowed(EventCategory.TASK_EVENT.name())) { - this.stringRedisTemplate.convertAndSend(getTopicNameForTask(), getObjectMapper().writeValueAsString(getTaskEventMessage(taskDelegate))); - } - } + if (isRegisteredEvent(taskDelegate.getEventName())) { + if (isAllowed(EventCategory.TASK_EVENT_DETAILS.name())) { + this.stringRedisTemplate.convertAndSend(getTopicNameForTaskDetail(), + bpmObjectMapper.writeValueAsString(getTaskMessage(taskDelegate))); + } + if (isAllowed(EventCategory.TASK_EVENT.name())) { + this.stringRedisTemplate.convertAndSend(getTopicNameForTask(), + bpmObjectMapper.writeValueAsString(getTaskEventMessage(taskDelegate))); + } + // convertAndSendMessage(getTopicNameForTaskDetail(), + // getTaskMessage(taskDelegate)); + // if (isAllowed(EventCategory.TASK_EVENT.name())) { + // convertAndSendMessage(getTopicNameForTask(), + // getTaskEventMessage(taskDelegate)); + // } + } } catch (JsonProcessingException e) { e.printStackTrace(); } } + private void convertAndSendMessage(String topicName, Object message) throws JsonProcessingException { + if (redisEnabled) { + this.stringRedisTemplate.convertAndSend(topicName, bpmObjectMapper.writeValueAsString(message)); + } else { + this.stringRedisTemplate.convertAndSend(topicName, bpmObjectMapper.writeValueAsString(message)); + } + } + private TaskMessage getTaskMessage(DelegateTask taskDelegate) { TaskMessage taskObj = new TaskMessage(); BeanUtils.copyProperties(taskDelegate, taskObj); @@ -73,11 +104,13 @@ private TaskEventMessage getTaskEventMessage(DelegateTask taskDelegate) { } private boolean isAllowed(String category) { - return Arrays.asList(StringUtils.split(messageCategory,",")).contains(category); + return Arrays.asList(StringUtils.split(messageCategory, ",")).contains(category); } - - private boolean isRegisteredEvent(String eventName) { - if("ALL".equalsIgnoreCase(messageEvents)) { return true;} + + private boolean isRegisteredEvent(String eventName) { + if ("ALL".equalsIgnoreCase(messageEvents)) { + return true; + } return getRegisteredEvents().contains(eventName); } @@ -85,14 +118,14 @@ private List getRegisteredEvents() { if ("DEFAULT".equalsIgnoreCase(messageEvents)) { return getDefaultRegisteredEvents(); } - return Arrays.asList(StringUtils.split(messageEvents,",")); + return Arrays.asList(StringUtils.split(messageEvents, ",")); } - private Map getVariables(DelegateTask taskDelegate) { - List configMap =getElements(); - Map variables = new HashMap<>(); - for(String entry : configMap) { - if(taskDelegate.getVariables().containsKey(entry)) { + private Map getVariables(DelegateTask taskDelegate) { + List configMap = getElements(); + Map variables = new HashMap<>(); + for (String entry : configMap) { + if (taskDelegate.getVariables().containsKey(entry)) { variables.put(entry, taskDelegate.getVariable(entry)); } } @@ -100,23 +133,23 @@ 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() { + private List getDefaultRegisteredEvents() { return Arrays.asList(TaskListener.EVENTNAME_CREATE, - TaskListener.EVENTNAME_UPDATE, - TaskListener.EVENTNAME_COMPLETE - ); + TaskListener.EVENTNAME_UPDATE, + TaskListener.EVENTNAME_COMPLETE); } - private ObjectMapper getObjectMapper() { - return new ObjectMapper(); - } + /** + * private ObjectMapper getObjectMapper() { + * return new ObjectMapper(); + * } + */ enum EventCategory { - TASK_EVENT, - TASK_EVENT_DETAILS; + TASK_EVENT, TASK_EVENT_DETAILS; } } \ 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/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..f7f09bd5 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. @@ -22,29 +25,47 @@ * @author sumathi.thirumani@aot-technologies.com */ @Configuration +@ConditionalOnProperty(prefix = "websocket", name = "enableRedis", havingValue = "true", matchIfMissing = false) 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 +76,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/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/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/services/FormSubmissionService.java b/apps/forms-flow-ai/forms-flow-bpm/src/main/java/org/camunda/bpm/extension/hooks/services/FormSubmissionService.java index 658702ee..06831cdc 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,120 @@ 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); + 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 +149,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/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..e82cda01 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,47 @@ 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); + } + } +} \ 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..28808c12 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 @@ -13,6 +13,11 @@ import org.springframework.web.client.RestClientException; import org.springframework.util.StringUtils; +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 java.util.logging.Logger; import org.springframework.http.ResponseEntity; @@ -27,6 +32,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; + import static org.camunda.bpm.extension.keycloak.json.JsonUtil.*; /** @@ -40,12 +46,19 @@ public class KeycloakUserService extends org.camunda.bpm.extension.keycloak.Key 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 @@ -120,4 +133,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/resources/application.yaml b/apps/forms-flow-ai/forms-flow-bpm/src/main/resources/application.yaml index e37f43a5..bd7efd7c 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,16 +9,18 @@ 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} +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 + 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} analytics.datasource: jdbc-url: ${CAMUNDA_JDBC_URL} @@ -33,8 +35,14 @@ analytics.datasource: 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: @@ -69,9 +77,9 @@ info: app: name: "Camunda" description: "formsflow.ai Engine" - version: "7.15" + version: "7.17" java: - version: "11" + version: "17" # Enable the below given block for session management of camunda. This is not required for externalised tasklist. #session.datasource: @@ -86,6 +94,8 @@ info: # validationTimeout: ${CAMUNDA_SESSION_HIKARI_VALID_TIMEOUT:5000} camunda.bpm: + job-execution: + enabled: true history-level: ${CAMUNDA_BPM_HISTORY_LEVEL:none} authorization: enabled: ${CAMUNDA_AUTHORIZATION_FLAG:true} @@ -95,7 +105,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'; @@ -104,24 +114,27 @@ camunda.bpm: form-action 'self'; frame-ancestors 'none'; object-src 'none' - job-execution: - 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} - max-pool-size: ${CAMUNDA_JOB_MAX_POOL_SIZE:10} - queue-capacity: ${CAMUNDA_JOB_QUEUE_SIZE:3} - wait-time-in-millis: ${CAMUNDA_JOB_WAIT_TIME_MILLIS:5000} - max-wait: ${CAMUNDA_JOB_MAX_WAIT:60000} - metrics: - enabled: ${CAMUNDA_METRICS_FLAG:true} + # job-execution: + # 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} + # max-pool-size: ${CAMUNDA_JOB_MAX_POOL_SIZE:10} + # queue-capacity: ${CAMUNDA_JOB_QUEUE_SIZE:3} + # wait-time-in-millis: ${CAMUNDA_JOB_WAIT_TIME_MILLIS:5000} + # max-wait: ${CAMUNDA_JOB_MAX_WAIT:60000} + # metrics: + # enabled: ${CAMUNDA_METRICS_FLAG:true} server: error: include-message: always port: 8080 - servlet.context-path: /camunda + servlet: + context-path: /camunda session: cookie: - secure: true + secure: ${SESSION_COOKIE_SECURE:true} + max-age: 1800 + http-only: true # Camunda Rest API @@ -203,7 +216,13 @@ websocket: messageType: ${WEBSOCKET_MESSAGE_TYPE:TASK_EVENT} messageEvents: ${WEBSOCKET_MESSAGE_EVENTS:DEFAULT} messageBroker: - host: ${WEBSOCKET_BROKER_HOST} - port: ${WEBSOCKET_BROKER_PORT} - passcode: ${WEBSOCKET_BROKER_PASSCODE} + host: ${REDIS_HOST} + port: ${REDIS_PORT} + passcode: ${REDIS_PASSCODE} + enableRedis: ${REDIS_ENABLED:false} + +# disable redis +spring.data.redis.repositories.enabled: false +spring.autoconfigure.exclude: + - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration 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-web/src/apiManager/services/bpmTaskServices.js b/apps/forms-flow-ai/forms-flow-web/src/apiManager/services/bpmTaskServices.js index db981638..94600ef4 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)); } }) @@ -139,11 +138,11 @@ //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/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?