Gitlab migration
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# Maven / NPM
|
||||||
|
target/
|
||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
*.js.map
|
||||||
|
|
||||||
|
# IntelliJ IDEA
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
*.code-workspace
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# MacOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker/data/
|
||||||
|
|
||||||
|
# Flowable
|
||||||
|
h2-database/
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
Generated
+24
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile name="Maven default annotation processors profile" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<module name="flowable-local-design" />
|
||||||
|
<module name="flowable-local-work" />
|
||||||
|
<module name="flowable-local-control" />
|
||||||
|
<module name="flowable-local-design-angular" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
<component name="JavacSettings">
|
||||||
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||||
|
<module name="flowable-local-control" options="-parameters" />
|
||||||
|
<module name="flowable-local-design" options="-parameters" />
|
||||||
|
<module name="flowable-local-design-angular" options="-parameters" />
|
||||||
|
<module name="flowable-local-work" options="-parameters" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+17
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="postgres@localhost" uuid="2a1f6969-a9f0-4067-bce4-109ebe8e85d6">
|
||||||
|
<driver-ref>postgresql</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:postgresql://localhost:5432/postgres</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+11
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/flowable-local-control/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/flowable-local-design-angular/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/flowable-local-design/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/flowable-local-work/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+40
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Central Repository" />
|
||||||
|
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="flowable-repo" />
|
||||||
|
<option name="name" value="flowable-repo" />
|
||||||
|
<option name="url" value="https://repo.flowable.com/artifactory/flowable-maven-all" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central_flowable" />
|
||||||
|
<option name="name" value="flowable-maven-all" />
|
||||||
|
<option name="url" value="https://artifacts.flowable.com/artifactory/maven-all" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Central Repository" />
|
||||||
|
<option name="url" value="https://artifacts.flowable.com/artifactory/maven-all" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="snapshots_flowable" />
|
||||||
|
<option name="name" value="flowable-maven-all" />
|
||||||
|
<option name="url" value="https://artifacts.flowable.com/artifactory/maven-all" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="homebrew-11" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/3.14.iml" filepath="$PROJECT_DIR$/.idea/3.14.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
+307
@@ -0,0 +1,307 @@
|
|||||||
|
# Flowable Initializr Template Changelog
|
||||||
|
|
||||||
|
This file is a changelog for the template used by the Flowable Initializr to create this projects.
|
||||||
|
It contains information about template changes made in the past for better traceability. Versions
|
||||||
|
mentioned in this file refer to the template itself and are not represented withing the any project
|
||||||
|
generated by the Flowable Initializr.
|
||||||
|
|
||||||
|
It is totally safe and intended that this file will be deleted after project generation.
|
||||||
|
|
||||||
|
## Change Log
|
||||||
|
|
||||||
|
### 0.14.2
|
||||||
|
- Upgrade to Flowable 3.14.4
|
||||||
|
- Upgrade to Spring Boot 2.7.18
|
||||||
|
- Added password properties to Work/Design/Control application.properties files to encourage users to change them.
|
||||||
|
|
||||||
|
### 0.14.1
|
||||||
|
- Upgrade to Flowable 3.14.1
|
||||||
|
- Upgrade to Spring Boot 2.7.14
|
||||||
|
|
||||||
|
### 0.14.0
|
||||||
|
- Upgrade to Flowable 3.14.0
|
||||||
|
- Upgrade to Spring Boot 2.7.13
|
||||||
|
- Changed Angular Design palette dependencies from -legacy to -angular
|
||||||
|
|
||||||
|
### 0.14.0.RC1.1
|
||||||
|
- Renamed design-legacy to design-angular to stick to the suggested naming convention.
|
||||||
|
- Added warning message to README.md about design and design-angular sharing the same DB schema.
|
||||||
|
|
||||||
|
### 0.14.0
|
||||||
|
- Upgrade to Flowable 3.14.0.RC1
|
||||||
|
- Upgrade to Spring Boot 2.7.12
|
||||||
|
- Updated documentation to use `docker compose` instead of `docker-compose` command.
|
||||||
|
- Updated namespace for Flowable Design properties.
|
||||||
|
- Added SecurityConfiguration and SecurityActuatorConfiguration classes for new Flowable Design.
|
||||||
|
- Added flowable-spring-boot-starter-design-actuator dependency for new Flowable Design.
|
||||||
|
- Added new maven submodule representing the angular version of Flowable Design (running on port 8093).
|
||||||
|
- Updated README.md with documentation about angular Flowable Design.
|
||||||
|
- Removed redundant spring.database.username and .password from application-infraless.properties
|
||||||
|
|
||||||
|
### 0.13.5
|
||||||
|
- Upgrade to Flowable 3.13.2
|
||||||
|
|
||||||
|
### 0.13.4
|
||||||
|
- Upgrade to Flowable 3.13.1
|
||||||
|
- Upgrade to Spring Boot 2.7.8
|
||||||
|
- Re-added the micrometer elasticsearch registry dependency that is not part of the Spring Boot Starter anymore. (see https://documentation.flowable.com/latest/admin/release-notes/3.13.0-release/#upgrade-information)
|
||||||
|
|
||||||
|
### 0.13.3
|
||||||
|
- Upgrade to Flowable 3.13.0
|
||||||
|
- Upgrade to Spring Boot 2.7.6
|
||||||
|
|
||||||
|
### 0.13.1
|
||||||
|
- Updated the `SecurityConfiguration` to get rid of deprecated `WebSecurityConfigurerAdapter`.
|
||||||
|
- Updated the `SecurityActuatorConfiguration` to get rid of deprecated `WebSecurityConfigurerAdapter`.
|
||||||
|
- Removed the `StaticResourceConfiguration` as it is not needed anymore.
|
||||||
|
|
||||||
|
### 0.13.0
|
||||||
|
- Upgrade to Flowable 3.13.0.RC1
|
||||||
|
- Upgrade to Spring Boot 2.7.5
|
||||||
|
|
||||||
|
### 0.12.3
|
||||||
|
- Upgrade to Flowable 3.12.4
|
||||||
|
|
||||||
|
### 0.12.2
|
||||||
|
- Upgrade to Flowable 3.12.3
|
||||||
|
- Added deprecation suppress warning annotation to SecurityActuatorConfiguration
|
||||||
|
- Infraless H2 database is now stored within the project (`h2-database` directory) instead of the users profile.
|
||||||
|
|
||||||
|
### 0.12.1
|
||||||
|
- Upgrade to Flowable 3.12.2
|
||||||
|
- Readded missing maven-wrapper.properties
|
||||||
|
|
||||||
|
### 0.12.0
|
||||||
|
- Upgrade to Flowable 3.12.0
|
||||||
|
- Upgrade to Spring Boot 2.7.2
|
||||||
|
- Upgrade to Elasticsearch 7.17.5
|
||||||
|
- Updated SecurityConfiguration
|
||||||
|
- Updated docker compose file to use heathcheck/depends_on to enforce service start order and timing
|
||||||
|
|
||||||
|
### 0.11.8
|
||||||
|
- Upgrade to Flowable 3.11.7
|
||||||
|
|
||||||
|
### 0.11.7
|
||||||
|
- Added maven wrapper
|
||||||
|
- Removed Flowable maven repo from parent pom.xml and added it to settings-flowable.xml
|
||||||
|
- Updated test to work elastic-less and FlowableSpringExtension
|
||||||
|
|
||||||
|
### 0.11.6
|
||||||
|
- Changed `training` profile to `infraless` (also in Readme)
|
||||||
|
|
||||||
|
### 0.11.5
|
||||||
|
- Removed custom tenant, filter and user definitions - instead added the default-idm-models dependency
|
||||||
|
- Added `flowable.platform.idm.minimal-setup=true` property (commented so sample Engage users get created)
|
||||||
|
- Added application-training.properties file for Flowable Academy (Embedded H2 DB, Elastic-less)
|
||||||
|
- Changed H2 DB dependency from test to default scope.
|
||||||
|
- Updated README.md to reflect the idm model changes.
|
||||||
|
|
||||||
|
### 0.11.4
|
||||||
|
- Upgrade to Flowable 3.11.6
|
||||||
|
- Upgrade to Spring Boot 2.6.7
|
||||||
|
|
||||||
|
### 0.11.3
|
||||||
|
- Upgrade to Flowable 3.11.5
|
||||||
|
- Upgrade to Spring Boot 2.6.6
|
||||||
|
|
||||||
|
### 0.11.2
|
||||||
|
- Upgrade to Flowable 3.11.2
|
||||||
|
- Upgrade to Spring Boot 2.6.2
|
||||||
|
- Fixed XPack disable property without quotes that caused errors on some systems.
|
||||||
|
- Upgrade of Elasticsearch version to 7.16.2 (because of Log4Shell vulnerability fix)
|
||||||
|
|
||||||
|
### 0.11.1
|
||||||
|
- Upgrade to Flowable 3.11.1
|
||||||
|
- Disabled xpack security in elasticsearch to prevent ugly warnings from RestClient
|
||||||
|
|
||||||
|
### 0.11.0
|
||||||
|
- Upgrade to Java 11
|
||||||
|
- Upgrade to Flowable 3.11.0
|
||||||
|
- Upgrade to Spring Boot 2.6.1
|
||||||
|
- Upgrade to Elasticsearch 7.15.2
|
||||||
|
- Upgrade to Postgres 14
|
||||||
|
- Renamed the *-app module to *-work for both the Flowable Work and Flowable Engage templates (as Engage is mainly just an extension of work)
|
||||||
|
- Streamlined the naming in the entire template to represent the app module renaming.
|
||||||
|
- Added additional package level 'work' for main and test classes
|
||||||
|
- Renamed the Application class
|
||||||
|
- Renamed the Test Classes
|
||||||
|
- Renamed the 'elasticsearch' service in docker-compose.yml to 'flowable-index'.
|
||||||
|
- Renamed docker-compose data volume 'data_es' to 'data_index'
|
||||||
|
- Fixed elasticsearch URL in flowable-engage service in docker-compose.yml
|
||||||
|
- Prepared possibility to run elasticless.
|
||||||
|
- Fixed some typos
|
||||||
|
- Added themeManagement allowdFeature to admin user definitions.
|
||||||
|
- Added tenantKey property to tenant setup
|
||||||
|
|
||||||
|
### 0.10.2
|
||||||
|
- Upgrade to Flowable 3.10.2
|
||||||
|
|
||||||
|
### 0.10.1
|
||||||
|
- Upgrade to Flowable 3.10.1
|
||||||
|
- Removed Flowable License bugfix
|
||||||
|
- Added impersonateUser feature to admin user definitions
|
||||||
|
|
||||||
|
### 0.10.0
|
||||||
|
- Upgrade to Flowable 3.10.0
|
||||||
|
- Upgrade Spring Boot to 2.5.2 to be in line with Flowable 3.10.0
|
||||||
|
- Set DB Store for Flowable License as default for PROD configuration
|
||||||
|
- Added sample settings.xml to project root
|
||||||
|
- Improved README documentation with information on Flowable Enterprise Maven repository access
|
||||||
|
- Added additional hyphen to index prefix
|
||||||
|
|
||||||
|
### 0.9.2
|
||||||
|
- Upgrade to Flowable 3.9.2
|
||||||
|
- Added customized Flowable Design and Control as submodules
|
||||||
|
- Updated docker-compose.yml also allows running vanilla work as a container
|
||||||
|
- Updated several properties
|
||||||
|
- Updated README.md for new Design/Control submodules
|
||||||
|
- Bugfixes
|
||||||
|
|
||||||
|
### 0.9.1
|
||||||
|
- Upgrade to Flowable 3.9.1
|
||||||
|
|
||||||
|
### 0.9.0
|
||||||
|
- Upgrade to Flowable 3.9.0
|
||||||
|
- Fixed password property for Flowable Control
|
||||||
|
- Removed MVCC from H2 DB String (true is default)
|
||||||
|
- Disabled Elasticsearch persistence for Flowable Metrics by default (re-enabled in application-prod.properties)
|
||||||
|
|
||||||
|
### 0.8.2
|
||||||
|
- Upgrade to Flowable 3.8.2
|
||||||
|
- Switched from yaml list to map format for environment properties in docker-compose
|
||||||
|
|
||||||
|
### 0.8.1
|
||||||
|
- Upgrade to Flowable 3.8.1
|
||||||
|
- Added cluster type property to Flowable Control config in docker-compose.yml (set to work)
|
||||||
|
- Improved docker-compose services layout
|
||||||
|
|
||||||
|
### 0.8.0
|
||||||
|
- Upgrade to Flowable 3.8.0
|
||||||
|
- Upgrade Spring Boot to 2.3.5.RELEASE to be in line with Flowable 3.8.0
|
||||||
|
- Added `info.env.name` property to application configuration to show stage label in UI
|
||||||
|
- Added `management.metrics.export.elastic.enabled=true` property to persist metrics in elasticsearch.
|
||||||
|
|
||||||
|
### 0.7.2
|
||||||
|
- Upgrade to FLowable 3.7.2
|
||||||
|
- Updated repositories for new Flowable Artifactory
|
||||||
|
|
||||||
|
### 0.7.1
|
||||||
|
- Upgrade to Flowable 3.7.1
|
||||||
|
|
||||||
|
### 0.7.0
|
||||||
|
- Upgrade to Flowable 3.7.0
|
||||||
|
- Upgrade Spring Boot to 2.3.1.RELEASE to be in line with Flowable 3.7.0
|
||||||
|
- Added Flowable Inspect as a dependency.
|
||||||
|
- Removed entrypoints from docker-compose definition for engage and design as the images now are based on executables jars.
|
||||||
|
- Updated configuration classes to be in sync with the product.
|
||||||
|
- Updated Postgres DB in docker-compose to 12.3
|
||||||
|
|
||||||
|
### 0.6.5
|
||||||
|
- Upgrade to Flowable 3.6.5
|
||||||
|
- Changed maven packaging from executable .war to executable .jar file as the executable .war might cause issues on
|
||||||
|
Kubernetes setups (file access delaying servlet initialization).
|
||||||
|
|
||||||
|
### 0.6.1
|
||||||
|
- Upgrade to Flowable 3.6.1
|
||||||
|
- Disabled DB store for Flowable Design license (active by default since 3.6) to use mounted license file instead
|
||||||
|
- Updated user definitions with newest allowedFeatures
|
||||||
|
|
||||||
|
### 0.6.0
|
||||||
|
- Upgrade to Flowable 3.6.0
|
||||||
|
- Upgrade Spring Boot to 2.2.5.RELEASE to be in line with Flowable 3.6.0
|
||||||
|
- Updated documentation links in README.md for Flowable 3.6.0
|
||||||
|
- Removed JUnit Jupiter dependency as it is now part of spring-boot-starter-test
|
||||||
|
|
||||||
|
### 0.5.1
|
||||||
|
- Upgrade to Flowable 3.5.1
|
||||||
|
- Minor comment improvements in pom.xml
|
||||||
|
|
||||||
|
### 0.5.0
|
||||||
|
- Upgrade to Flowable 3.5.0
|
||||||
|
- Upgrade Spring Boot to 2.1.9.RELEASE to be in line with Flowable 3.5.0
|
||||||
|
- Maven repository config now points to Flowable Repo (instead of edrorasware).
|
||||||
|
- Added additional dash character to elasticsearch index prefix property to make the index names more readable.
|
||||||
|
- Added Info about Windows issues with the host.docker.internal URL.
|
||||||
|
- Upgrade Elasticsearch image in docker-compose to 7.5.0
|
||||||
|
|
||||||
|
### 0.4.1
|
||||||
|
- Upgrade to Flowable 3.4.1
|
||||||
|
- Removed unneeded static logger from SecurityActuatorConfiguration
|
||||||
|
|
||||||
|
### 0.4.0
|
||||||
|
- Upgrade to Flowable 3.4.0
|
||||||
|
|
||||||
|
### 0.3.0.1
|
||||||
|
- Upgrade Flowable Design Docker image to 3.3.0.1
|
||||||
|
- Configured Flowable Design to be able to get users/groups from Work/Engage
|
||||||
|
- Configured Flowable Control to allow user/group creation
|
||||||
|
- Minor .gitignore corrections
|
||||||
|
|
||||||
|
### 0.3.0
|
||||||
|
- Upgrade to Flowable 3.3.0
|
||||||
|
- Extended .gitignore with Visual Studio Code files
|
||||||
|
- Updated docu links
|
||||||
|
- Updated Docker image versions to 3.3.0
|
||||||
|
- Refactored Test classes
|
||||||
|
|
||||||
|
### 0.2.1
|
||||||
|
- Upgrade to Flowable 3.2.2
|
||||||
|
|
||||||
|
### 0.2.0
|
||||||
|
- Upgrade to Flowable 3.2.1
|
||||||
|
|
||||||
|
### 0.1.6
|
||||||
|
- Added static resource configuration class to configure browser caching for static resources.
|
||||||
|
|
||||||
|
### 0.1.5
|
||||||
|
- Upgrade to Flowable 3.1.2
|
||||||
|
|
||||||
|
### 0.1.4
|
||||||
|
- Added CHANGELOG.md file
|
||||||
|
- Changed bind mounts in docker-compose.yml to volumes to achieve better Docker for Windows compatibility - YOU WILL USE YOUR EXISTING DATA WITH THIS CHANGE!!
|
||||||
|
- Updated tenant and user definition files to reintroduce needed minimal setup (added `flowableUser` group)
|
||||||
|
- Updated README.md docu
|
||||||
|
- Re-added H2 dependency as in testing scope
|
||||||
|
|
||||||
|
### 0.1.3
|
||||||
|
- added docker/data folder to gitignore
|
||||||
|
- Corrected contact filters
|
||||||
|
- Added property for actuator pretty json
|
||||||
|
|
||||||
|
### 0.1.2
|
||||||
|
- Removed frontend title
|
||||||
|
- Added workaround for FLW-523
|
||||||
|
|
||||||
|
### 0.1.1
|
||||||
|
- Updated allowed features
|
||||||
|
- Added contact filter to the template
|
||||||
|
|
||||||
|
### 0.1.0
|
||||||
|
- Upgrade to Flowable 3.1.0
|
||||||
|
- Docu update
|
||||||
|
- Added data bind mount directory
|
||||||
|
|
||||||
|
### 0.0.5
|
||||||
|
- Added additional documentation on how to setup infrastructure manually
|
||||||
|
- Reconfigured maven to create an executable war file
|
||||||
|
- Docu update
|
||||||
|
|
||||||
|
### 0.0.4
|
||||||
|
- Fixed docker images for design and control to stable version 3.0.0, updated docu accordingly
|
||||||
|
- Readded flowableAdministrator group and user-definition as these seem to be references directly
|
||||||
|
- Removed certain properties that are already set by default by the product
|
||||||
|
- Removed embedded profile
|
||||||
|
|
||||||
|
### 0.0.3
|
||||||
|
- Fixed wrong port for control in README.md
|
||||||
|
- Fixed license file mapping that caused write error for uploadCache directory
|
||||||
|
- Fixed flowable-design URL in README.md
|
||||||
|
|
||||||
|
### 0.0.2
|
||||||
|
- Added template specific tenant files and user definitions
|
||||||
|
- Fixed main and test class names
|
||||||
|
- Fixed pox description
|
||||||
|
- Added context root property for flowable control
|
||||||
|
|
||||||
|
### 0.0.1
|
||||||
|
- initial commit
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# flowable local Flowable Work
|
||||||
|
|
||||||
|
## WARNING!
|
||||||
|
**This project contains the new version of Flowable Design (flowable-local-design) as well as the older angular version of Flowable Design (flowable-local-design-angular).
|
||||||
|
Both versions use the same database. flowable-local-design will install/upgrade to a new DB schema version as soon as being run.
|
||||||
|
After this, flowable-local-design-angular will NOT work anymore! The DB schema has to be deleted and recreated by starting flowable-local-design-angular
|
||||||
|
for it to work again. Use the `infraless` profile to run both design versions concurrently.**
|
||||||
|
|
||||||
|
## Preparation
|
||||||
|
### Building the project artifacts
|
||||||
|
Java and Maven are needed as a prerequisite to build this code.
|
||||||
|
The Maven configuration of the project tries to get the Flowable dependencies form the Flowable Enterprise Repository.
|
||||||
|
That Repository needs a Flowable enterprise account to be accessible. Get in contact with Flowable to get your Flowable subscription.
|
||||||
|
|
||||||
|
As soon as you got your username and password you can add it to the `settings-flowable.xml` file in the root of the project.
|
||||||
|
(Keep in mind, that using an unencrypted password in this file is indeed a fast but also not a suggested approach.
|
||||||
|
Check https://maven.apache.org/guides/mini/guide-encryption.html on how to encrypt your password.)
|
||||||
|
|
||||||
|
You can then create the Flowable artifacts by executing `mvn -s settings-flowable.xml clean package`.
|
||||||
|
|
||||||
|
### Flowable `infraless` profile
|
||||||
|
For a setup of a decent development infrastructure dedicated Postgres and Elasticsearch instances are heavily suggested
|
||||||
|
(e.g. by using Docker, see next chapter).
|
||||||
|
|
||||||
|
For participating a Flowable Training course it is possible to run Flowable with an `infraless` profile with an embedded H2
|
||||||
|
database and in a mode that works without Elasticsearch.
|
||||||
|
You can activate this `infraless` profile by adding it to the list of activated Spring profiles for Flowable Work, Design
|
||||||
|
and Control by either using the start configuration of your IDE or by setting `spring.profiles.active=infraless` as a
|
||||||
|
JVM parameter or environment variable.
|
||||||
|
|
||||||
|
**ATTENTION**: Keep in mind that the `infraless` profiles does not provide the full Flowable functionality and is absolutely
|
||||||
|
not suggested for development, test or even production purposes.
|
||||||
|
|
||||||
|
### Setting up the needed Infrastructure
|
||||||
|
Please check the following links on how to setup the infrastructure for Flowable manually without
|
||||||
|
using Docker:
|
||||||
|
|
||||||
|
- [Postgres Database](https://documentation.flowable.com/latest/admin/installs/engage-full/#database-1)
|
||||||
|
- [Elasticsearch](https://documentation.flowable.com/latest/admin/installs/engage-full/#elasticsearch-1)
|
||||||
|
|
||||||
|
For an easier setup with help of Docker navigate to `/docker`, then execute `docker compose up`. Needed services such as
|
||||||
|
Elasticsearch and a database will be started.
|
||||||
|
|
||||||
|
## Starting the project
|
||||||
|
Afterwards you can start the Spring Boot application defined in `com.flowable.local.work.FlowableLocalWorkApplication`. In order to achieve this,
|
||||||
|
you can build the executable war file with maven and execute `java -jar` pointing to the built jar or create a new IDE Run Configuration.
|
||||||
|
Then open `http://localhost:8090` in the browser and use one of the users specified below in this document.
|
||||||
|
|
||||||
|
You can start the Flowable Design and Flowable Control applications accordingly.
|
||||||
|
|
||||||
|
## Angular Version of Flowable Design
|
||||||
|
This project contains the new version of Flowable Design (flowable-local-design) available since 3.14 as well as the older
|
||||||
|
angular version of Flowable Design (flowable-local-design-angular).
|
||||||
|
|
||||||
|
**Usually only 1 of these 2 versions are needed. The other one can (and most probably should) be deleted.** Please keep in
|
||||||
|
mind, that both versions cannot be run on the same DB schema as the new Flowable Design will update the schema which is then
|
||||||
|
not compatible with the angular version anymore.
|
||||||
|
|
||||||
|
This restriction does not apply to the infraless profile that uses different databases for the versions.
|
||||||
|
With this profile active, both the new and the angular version of Flowable Design can be started and used concurrently.
|
||||||
|
|
||||||
|
## Helpful links
|
||||||
|
After both docker-compose and the application are started, these are the links for the different applications:
|
||||||
|
|
||||||
|
- http://localhost:8090 - Flowable Work
|
||||||
|
- http://localhost:8091 - Flowable Design
|
||||||
|
- http://localhost:8092 - Flowable Control
|
||||||
|
- http://localhost:8093 - Flowable Design Angular
|
||||||
|
|
||||||
|
## Setting up Flowable vanilla apps with Docker
|
||||||
|
The docker-compose.yml file also contains (commented out) preconfigured service definitions to run all Flowable apps as
|
||||||
|
vanilla docker images. Just uncomment the appropriate parts to start the apps together with the other infrastructure.
|
||||||
|
These vanilla services are preconfigured to run as a replacement of any of the customized apps within this project.
|
||||||
|
|
||||||
|
To be able to use them, log in to docker `docker login artifacts.flowable.com` by using your Flowable login.
|
||||||
|
You will also need an appropriate Flowable license stored in `~/.flowable/flowable.license`.
|
||||||
|
Also, a decent amount of memory is needed for your docker VM. At least 4 GB are suggested.
|
||||||
|
Navigate to `/docker`, then execute `docker compose up`.
|
||||||
|
|
||||||
|
## Infrastructure Containers
|
||||||
|
If you need to recreate the containers, perform the following actions:
|
||||||
|
- Make sure the application and the docker containers are stopped.
|
||||||
|
- Execute `docker compose down` inside the `/docker` directory. This will remove the created containers.
|
||||||
|
|
||||||
|
## Data
|
||||||
|
Data created by the database and Elasticsearch are stored in docker volumes `data_db` and `data_es`.
|
||||||
|
This allows you to purge and recreate the containers without loosing any data.
|
||||||
|
|
||||||
|
If you need a clean state for the database and elasticsearch just execute `docker compose down -v`inside the `/docker` directory.
|
||||||
|
The volumes will be recreated as soon as you restart the containers.
|
||||||
|
BE CAREFUL AS WITH THIS YOU WILL LOSE ALL YOUR DATA STORED IN THE DATABASE AND ELASTICSEARCH!
|
||||||
|
|
||||||
|
## Sample users
|
||||||
|
| User | User Definition Key | Login | Password |
|
||||||
|
| -------------| ------------- | ------------- | ------------- |
|
||||||
|
| Flowable Administrator | admin-flowable | admin | test |
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
COMPOSE_PROJECT_NAME=3-14
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
services:
|
||||||
|
flowable-db:
|
||||||
|
image: postgres:14
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: flowable
|
||||||
|
POSTGRES_USER: flowable
|
||||||
|
POSTGRES_PASSWORD: flowable
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
volumes:
|
||||||
|
- data_db:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "pg_isready", "-q", "-d", "flowable", "-U", "flowable" ]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
flowable-index:
|
||||||
|
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.5
|
||||||
|
environment:
|
||||||
|
discovery.type: single-node
|
||||||
|
node.name: flowable-node-01
|
||||||
|
cluster.name: flowable-cluster
|
||||||
|
xpack.security.enabled: "false"
|
||||||
|
ports:
|
||||||
|
- 9300:9300
|
||||||
|
- 9200:9200
|
||||||
|
mem_limit: 1024m
|
||||||
|
volumes:
|
||||||
|
- data_index:/usr/share/elasticsearch/data
|
||||||
|
ulimits:
|
||||||
|
memlock:
|
||||||
|
soft: -1
|
||||||
|
hard: -1
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://host.docker.internal:9200"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
|
# Vanilla Flowable Work, uncomment to activate
|
||||||
|
# This image requires to login to the Flowable Docker Repo (docker login artifacts.flowable.com)
|
||||||
|
# flowable-work:
|
||||||
|
# image: artifacts.flowable.com/flowable/flowable-work:3.14.4
|
||||||
|
# environment:
|
||||||
|
# flowable.license.db-store-enabled: "false"
|
||||||
|
# flowable.inspect.enabled: "true"
|
||||||
|
# flowable.security.impersonate.allowed: "true"
|
||||||
|
# flowable.platform.enable-latest-form-definition-lookup: "true"
|
||||||
|
# flowable.platform.idm.minimal-setup: "true"
|
||||||
|
# flowable.example.deploy-apps: "false"
|
||||||
|
# info.env.name: Development
|
||||||
|
# logging.file: flowable-work.log
|
||||||
|
# management.metrics.export.elastic.enabled: "true"
|
||||||
|
# management.endpoints.web.exposure.include: "*"
|
||||||
|
# management.endpoint.health.show-details: ALWAYS
|
||||||
|
# server.servlet.context-path: /
|
||||||
|
# spring.elasticsearch.rest.uris: http://host.docker.internal:9200
|
||||||
|
# spring.datasource.driver-class-name: org.postgresql.Driver
|
||||||
|
# spring.datasource.url: jdbc:postgresql://host.docker.internal:5432/flowable
|
||||||
|
# spring.datasource.username: flowable
|
||||||
|
# spring.datasource.password: flowable
|
||||||
|
# ports:
|
||||||
|
# - 8090:8080
|
||||||
|
# volumes:
|
||||||
|
# - ~/.flowable/flowable.license:/root/.flowable/flowable.license:ro
|
||||||
|
# depends_on:
|
||||||
|
# flowable-db:
|
||||||
|
# condition: service_healthy
|
||||||
|
# flowable-index:
|
||||||
|
# condition: service_healthy
|
||||||
|
|
||||||
|
# Vanilla Flowable Design, uncomment to activate
|
||||||
|
# This image requires to login to the Flowable Docker Repo (docker login artifacts.flowable.com)
|
||||||
|
# flowable-design:
|
||||||
|
# image: artifacts.flowable.com/docker-local/flowable/flowable-design:3.14.4
|
||||||
|
# environment:
|
||||||
|
# flowable.design.remote.authentication.user: admin
|
||||||
|
# flowable.design.remote.authentication.password: test
|
||||||
|
# flowable.design.remote.idm-url: http://host.docker.internal:8090
|
||||||
|
# flowable.design.deployment-api-url: http://host.docker.internal:8090/app-api # not yet working for linux systems, check https://github.com/docker/for-linux/issues/264 for PR state and workarounds
|
||||||
|
# flowable.design.undeployment-api-url: http://host.docker.internal:8090/platform-api/app-deployments # not yet working for linux systems, check https://github.com/docker/for-linux/issues/264 for PR state and workarounds
|
||||||
|
# flowable.design.db-store-enabled: "false"
|
||||||
|
# logging.file: flowable-design.log
|
||||||
|
# server.servlet.context-path: /
|
||||||
|
# spring.datasource.driver-class-name: org.postgresql.Driver
|
||||||
|
# spring.datasource.url: jdbc:postgresql://host.docker.internal:5432/flowable
|
||||||
|
# spring.datasource.username: flowable
|
||||||
|
# spring.datasource.password: flowable
|
||||||
|
# ports:
|
||||||
|
# - 8091:8080
|
||||||
|
# volumes:
|
||||||
|
# - ~/.flowable/flowable.license:/root/.flowable/flowable.license:ro
|
||||||
|
# depends_on:
|
||||||
|
# flowable-db:
|
||||||
|
# condition: service_healthy
|
||||||
|
|
||||||
|
# Vanilla Flowable Design Angular, uncomment to activate
|
||||||
|
# This image requires to login to the Flowable Docker Repo (docker login artifacts.flowable.com)
|
||||||
|
# flowable-design-angular:
|
||||||
|
# image: artifacts.flowable.com/docker-local/flowable/flowable-design-angular:3.14.4
|
||||||
|
# environment:
|
||||||
|
# flowable.common.app.idm-admin.user: admin
|
||||||
|
# flowable.common.app.idm-admin.password: test
|
||||||
|
# flowable.common.app.idm-url: http://host.docker.internal:8090
|
||||||
|
# flowable.modeler.app.deployment-api-url: http://host.docker.internal:8090/app-api # not yet working for linux systems, check https://github.com/docker/for-linux/issues/264 for PR state and workarounds
|
||||||
|
# flowable.modeler.app.undeployment-api-url: http://host.docker.internal:8090/platform-api/app-deployments # not yet working for linux systems, check https://github.com/docker/for-linux/issues/264 for PR state and workarounds
|
||||||
|
# flowable.modeler.app.db-store-enabled: "false"
|
||||||
|
# logging.file: flowable-design.log
|
||||||
|
# server.servlet.context-path: /
|
||||||
|
# spring.datasource.driver-class-name: org.postgresql.Driver
|
||||||
|
# spring.datasource.url: jdbc:postgresql://host.docker.internal:5432/flowable
|
||||||
|
# spring.datasource.username: flowable
|
||||||
|
# spring.datasource.password: flowable
|
||||||
|
# ports:
|
||||||
|
# - 8091:8080
|
||||||
|
# volumes:
|
||||||
|
# - ~/.flowable/flowable.license:/root/.flowable/flowable.license:ro
|
||||||
|
# depends_on:
|
||||||
|
# flowable-db:
|
||||||
|
# condition: service_healthy
|
||||||
|
|
||||||
|
# Vanilla Flowable Control, uncomment to activate
|
||||||
|
# This image requires to login to the Flowable Docker Repo (docker login artifacts.flowable.com)
|
||||||
|
# flowable-control:
|
||||||
|
# image: artifacts.flowable.com/docker-local/flowable/flowable-control:3.14.4
|
||||||
|
# environment:
|
||||||
|
# flowable.common.app.idm-admin.user: admin
|
||||||
|
# flowable.common.app.idm-admin.password: test
|
||||||
|
# flowable.control.app.cluster-config.server-address: http://host.docker.internal # not yet working for linux systems, check https://github.com/docker/for-linux/issues/264 for pr state and workarounds
|
||||||
|
# flowable.control.app.cluster-config.server-port: 8090
|
||||||
|
# flowable.control.app.cluster-config.context-root: /
|
||||||
|
# flowable.control.app.cluster-config.password: test
|
||||||
|
# flowable.control.app.db-store-enabled: "false"
|
||||||
|
# logging.file: flowable-control.log
|
||||||
|
# server.servlet.context-path: /
|
||||||
|
# spring.datasource.driver-class-name: org.postgresql.Driver
|
||||||
|
# spring.datasource.url: jdbc:postgresql://host.docker.internal:5432/flowable
|
||||||
|
# spring.datasource.username: flowable
|
||||||
|
# spring.datasource.password: flowable
|
||||||
|
# ports:
|
||||||
|
# - 8092:8080
|
||||||
|
# volumes:
|
||||||
|
# - ~/.flowable/flowable.license:/root/.flowable/flowable.license:ro
|
||||||
|
# depends_on:
|
||||||
|
# flowable-db:
|
||||||
|
# condition: service_healthy
|
||||||
|
|
||||||
|
# Volumes will get a prefix based on the `.env` file and data can be cleared by `docker-compose down -v`
|
||||||
|
volumes:
|
||||||
|
data_db:
|
||||||
|
data_index:
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>flowable-local-control</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>flowable-local-control</name>
|
||||||
|
<description>flowable local Flowable Control</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.flowable.local</groupId>
|
||||||
|
<artifactId>14</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- flowable local -->
|
||||||
|
<!-- =============== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>${org.postgresql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flowable Control -->
|
||||||
|
<!-- ================ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.control</groupId>
|
||||||
|
<artifactId>flowable-spring-boot-starter-control</artifactId>
|
||||||
|
<version>${com.flowable.platform.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot -->
|
||||||
|
<!-- =========== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<!-- ======= -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>${com.h2database.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.flowable.local.control;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class FlowableLocalControlApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(FlowableLocalControlApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# == Embedded DB ==
|
||||||
|
spring.datasource.driver-class-name=org.h2.Driver
|
||||||
|
spring.datasource.url=jdbc:h2:file:./h2-database/flowable-control;DB_CLOSE_ON_EXIT=FALSE
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
server.port=8092
|
||||||
|
server.servlet.context-path=/
|
||||||
|
|
||||||
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||||
|
spring.datasource.url=jdbc:postgresql://localhost:5432/flowable
|
||||||
|
spring.datasource.username=flowable
|
||||||
|
spring.datasource.password=flowable
|
||||||
|
|
||||||
|
# In order for hot swapping to work for the custom.js and custom.css
|
||||||
|
spring.thymeleaf.cache=false
|
||||||
|
spring.web.resources.chain.cache=false
|
||||||
|
|
||||||
|
# Enable all endpoints over HTTP
|
||||||
|
management.endpoints.web.exposure.include=*
|
||||||
|
management.endpoint.health.show-details=ALWAYS
|
||||||
|
# Pretty-print JSON responses
|
||||||
|
spring.jackson.serialization.indent_output=true
|
||||||
|
|
||||||
|
# TODO Change these to an other random string with a length of 16 characters
|
||||||
|
# Used to encrypt your passwords. After a change existing password become invalid!
|
||||||
|
flowable.control.app.security.encryption.credentials-i-v-spec=fh39chqoxjDF3fhb
|
||||||
|
flowable.control.app.security.encryption.credentials-secret-spec=lw91VtkPq84nGqiJ
|
||||||
|
|
||||||
|
flowable.control.app.cluster-config.name=Default Cluster
|
||||||
|
flowable.control.app.cluster-config.description=Default Flowable Cluster
|
||||||
|
flowable.control.app.cluster-config.server-address=http://localhost
|
||||||
|
flowable.control.app.cluster-config.port=8090
|
||||||
|
flowable.control.app.cluster-config.context-root=/
|
||||||
|
flowable.control.app.cluster-config.user-name=admin
|
||||||
|
flowable.control.app.cluster-config.password=test
|
||||||
|
|
||||||
|
flowable.control.app.db-store-enabled=false
|
||||||
|
flowable.control.app.cluster-type=work
|
||||||
|
flowable.control.app.user-store.password=test
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>flowable-local-design-angular</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>flowable-local-design-angular</name>
|
||||||
|
<description>flowable local Flowable Design Angular</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.flowable.local</groupId>
|
||||||
|
<artifactId>14</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- flowable local -->
|
||||||
|
<!-- =============== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>${org.postgresql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flowable Design -->
|
||||||
|
<!-- =============== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.design.angular</groupId>
|
||||||
|
<artifactId>flowable-spring-boot-starter-design</artifactId>
|
||||||
|
<version>${com.flowable.platform.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.platform</groupId>
|
||||||
|
<artifactId>flowable-platform-palette-angular</artifactId>
|
||||||
|
<version>${com.flowable.platform.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot -->
|
||||||
|
<!-- =========== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<!-- ======= -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>${com.h2database.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.flowable.local.design.angular;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class FlowableLocalDesignAngularApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(FlowableLocalDesignAngularApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# == Embedded DB ==
|
||||||
|
spring.datasource.driver-class-name=org.h2.Driver
|
||||||
|
spring.datasource.url=jdbc:h2:file:./h2-database/flowable-design-angular;DB_CLOSE_ON_EXIT=FALSE
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
server.port=8091
|
||||||
|
server.servlet.context-path=/
|
||||||
|
|
||||||
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||||
|
spring.datasource.url=jdbc:postgresql://localhost:5432/flowable
|
||||||
|
spring.datasource.username=flowable
|
||||||
|
spring.datasource.password=flowable
|
||||||
|
|
||||||
|
# In order for hot swapping to work for the custom.js and custom.css
|
||||||
|
spring.thymeleaf.cache=false
|
||||||
|
spring.web.resources.chain.cache=false
|
||||||
|
|
||||||
|
# Enable all endpoints over HTTP
|
||||||
|
management.endpoints.web.exposure.include=*
|
||||||
|
management.endpoint.health.show-details=ALWAYS
|
||||||
|
# Pretty-print JSON responses
|
||||||
|
spring.jackson.serialization.indent_output=true
|
||||||
|
|
||||||
|
flowable.modeler.app.user-store.password=test
|
||||||
|
flowable.common.app.idm-url=http://localhost:8090
|
||||||
|
flowable.common.app.idm-admin.user=admin
|
||||||
|
flowable.common.app.idm-admin.password=test
|
||||||
|
|
||||||
|
flowable.modeler.app.db-store-enabled=false
|
||||||
|
flowable.modeler.app.deployment-api-url=http://localhost:8090/app-api
|
||||||
|
flowable.modeler.app.undeployment-api-url=http://localhost:8090/platform-api/app-deployments
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>flowable-local-design</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>flowable-local-design</name>
|
||||||
|
<description>flowable local Flowable Design</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.flowable.local</groupId>
|
||||||
|
<artifactId>14</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- flowable local -->
|
||||||
|
<!-- =============== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>${org.postgresql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flowable Design -->
|
||||||
|
<!-- =============== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.design</groupId>
|
||||||
|
<artifactId>flowable-spring-boot-starter-design</artifactId>
|
||||||
|
<version>${com.flowable.platform.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.design</groupId>
|
||||||
|
<artifactId>flowable-spring-boot-starter-design-actuator</artifactId>
|
||||||
|
<version>${com.flowable.platform.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.platform</groupId>
|
||||||
|
<artifactId>flowable-platform-palette</artifactId>
|
||||||
|
<version>${com.flowable.platform.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot -->
|
||||||
|
<!-- =========== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<!-- ======= -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>${com.h2database.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.flowable.local.design;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class FlowableLocalDesignApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(FlowableLocalDesignApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package com.flowable.local.design.configuration;
|
||||||
|
|
||||||
|
import com.flowable.design.actuate.autoconfigure.security.servlet.ActuatorRequestMatcher;
|
||||||
|
import com.flowable.design.engine.api.idm.DesignPrivileges;
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
|
||||||
|
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||||
|
import org.springframework.boot.actuate.info.InfoEndpoint;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SecurityActuatorConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(6)
|
||||||
|
public SecurityFilterChain basicActuatorSecurity(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.sessionManagement()
|
||||||
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||||
|
.and()
|
||||||
|
.csrf()
|
||||||
|
.disable();
|
||||||
|
|
||||||
|
http
|
||||||
|
.requestMatcher(new ActuatorRequestMatcher())
|
||||||
|
.authorizeRequests()
|
||||||
|
.requestMatchers(EndpointRequest.to(InfoEndpoint.class, HealthEndpoint.class)).permitAll()
|
||||||
|
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasAuthority(DesignPrivileges.ACCESS_ADMIN)
|
||||||
|
.anyRequest().denyAll()
|
||||||
|
.and().httpBasic();
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
package com.flowable.local.design.configuration;
|
||||||
|
|
||||||
|
import com.flowable.autoconfigure.design.security.DesignHttpSecurityCustomizer;
|
||||||
|
import com.flowable.design.security.spring.web.authentication.AjaxAuthenticationFailureHandler;
|
||||||
|
import com.flowable.design.security.spring.web.authentication.AjaxAuthenticationSuccessHandler;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||||
|
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(10)
|
||||||
|
public SecurityFilterChain basicDefaultSecurity(HttpSecurity http, ObjectProvider<DesignHttpSecurityCustomizer> httpSecurityCustomizers) throws Exception {
|
||||||
|
for (DesignHttpSecurityCustomizer customizer : httpSecurityCustomizers.orderedStream()
|
||||||
|
.collect(Collectors.toList())) {
|
||||||
|
customizer.customize(http);
|
||||||
|
}
|
||||||
|
|
||||||
|
http
|
||||||
|
.sessionManagement()
|
||||||
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||||
|
http
|
||||||
|
.logout()
|
||||||
|
.logoutUrl("/auth/logout")
|
||||||
|
.logoutSuccessUrl("/");
|
||||||
|
|
||||||
|
http
|
||||||
|
.exceptionHandling()
|
||||||
|
.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), AnyRequestMatcher.INSTANCE)
|
||||||
|
.and()
|
||||||
|
.formLogin()
|
||||||
|
.loginProcessingUrl("/auth/login")
|
||||||
|
.successHandler(new AjaxAuthenticationSuccessHandler())
|
||||||
|
.failureHandler(new AjaxAuthenticationFailureHandler())
|
||||||
|
.and()
|
||||||
|
.authorizeRequests()
|
||||||
|
// allow context root for all (it triggers the loading of the initial page)
|
||||||
|
.antMatchers("/").permitAll()
|
||||||
|
.antMatchers(
|
||||||
|
"/**/*.svg", "/**/*.ico", "/**/*.png", "/**/*.woff2", "/**/*.css",
|
||||||
|
"/**/*.woff", "/**/*.html", "/**/*.js",
|
||||||
|
"/**/flowable-frontend-configuration",
|
||||||
|
"/**/index.html").permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.and()
|
||||||
|
.httpBasic();
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# == Embedded DB ==
|
||||||
|
spring.datasource.driver-class-name=org.h2.Driver
|
||||||
|
spring.datasource.url=jdbc:h2:file:./h2-database/flowable-design;DB_CLOSE_ON_EXIT=FALSE
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
server.port=8093
|
||||||
|
server.servlet.context-path=/
|
||||||
|
|
||||||
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||||
|
spring.datasource.url=jdbc:postgresql://localhost:5432/flowable
|
||||||
|
spring.datasource.username=flowable
|
||||||
|
spring.datasource.password=flowable
|
||||||
|
|
||||||
|
# Local Elasticsearch
|
||||||
|
spring.elasticsearch.uris=http://localhost:9200
|
||||||
|
|
||||||
|
# In order for hot swapping to work for the custom.js and custom.css
|
||||||
|
spring.thymeleaf.cache=false
|
||||||
|
spring.web.resources.chain.cache=false
|
||||||
|
|
||||||
|
# Enable all endpoints over HTTP
|
||||||
|
management.endpoints.web.exposure.include=*
|
||||||
|
management.endpoint.health.show-details=ALWAYS
|
||||||
|
# Pretty-print JSON responses
|
||||||
|
spring.jackson.serialization.indent_output=true
|
||||||
|
|
||||||
|
flowable.design.user-store.password=test
|
||||||
|
flowable.design.remote.idm-url=http://localhost:8090
|
||||||
|
flowable.design.remote.authentication.user=admin
|
||||||
|
flowable.design.remote.authentication.password=test
|
||||||
|
|
||||||
|
flowable.design.db-store-enabled=false
|
||||||
|
flowable.design.deployment-api-url=http://localhost:8090/app-api
|
||||||
|
flowable.design.undeployment-api-url=http://localhost:8090/platform-api/app-deployments
|
||||||
|
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>flowable-local-work</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>flowable-local-work</name>
|
||||||
|
<description>flowable local Flowable Work</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.flowable.local</groupId>
|
||||||
|
<artifactId>14</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- flowable local -->
|
||||||
|
<!-- =============== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>${org.postgresql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flowable Frontend -->
|
||||||
|
<!-- ================= -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.work</groupId>
|
||||||
|
<artifactId>flowable-work-frontend</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flowable Platform -->
|
||||||
|
<!-- ================= -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.platform</groupId>
|
||||||
|
<artifactId>flowable-spring-boot-starter-platform-rest</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.platform</groupId>
|
||||||
|
<artifactId>flowable-tenant-setup</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.platform</groupId>
|
||||||
|
<artifactId>flowable-platform-default-models</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.platform</groupId>
|
||||||
|
<artifactId>flowable-platform-default-idm-models</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flowable Inspect -->
|
||||||
|
<!-- ================ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.inspect</groupId>
|
||||||
|
<artifactId>flowable-spring-boot-starter-inspect-rest</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flowable Actuators -->
|
||||||
|
<!-- ================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable.platform</groupId>
|
||||||
|
<artifactId>flowable-spring-boot-starter-platform-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micrometer</groupId>
|
||||||
|
<artifactId>micrometer-registry-elastic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot -->
|
||||||
|
<!-- =========== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<!-- ======= -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>${com.h2database.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.wnameless.json</groupId>
|
||||||
|
<artifactId>json-flattener</artifactId>
|
||||||
|
<version>0.16.6</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.icegreen</groupId>
|
||||||
|
<artifactId>greenmail</artifactId>
|
||||||
|
<version>2.1.8</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.activation</groupId>
|
||||||
|
<artifactId>jakarta.activation-api</artifactId>
|
||||||
|
<version>2.1.4</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- AuditTrailController -->
|
||||||
|
<!-- ======= -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.validation</groupId>
|
||||||
|
<artifactId>validation-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi-ooxml</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package com.flowable.local.work;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
|
||||||
|
|
||||||
|
@SpringBootApplication(exclude = {FreeMarkerAutoConfiguration.class})
|
||||||
|
public class FlowableLocalWorkApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(FlowableLocalWorkApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package com.flowable.local.work.configuration;
|
||||||
|
|
||||||
|
import com.flowable.actuate.autoconfigure.security.servlet.ActuatorRequestMatcher;
|
||||||
|
import com.flowable.platform.common.security.SecurityConstants;
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
|
||||||
|
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||||
|
import org.springframework.boot.actuate.info.InfoEndpoint;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SecurityActuatorConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(6)
|
||||||
|
public SecurityFilterChain basicActuatorSecurity(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
http
|
||||||
|
.sessionManagement()
|
||||||
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||||
|
.and()
|
||||||
|
.csrf()
|
||||||
|
.disable();
|
||||||
|
|
||||||
|
http
|
||||||
|
.requestMatcher(new ActuatorRequestMatcher())
|
||||||
|
.authorizeRequests()
|
||||||
|
.requestMatchers(EndpointRequest.to(InfoEndpoint.class, HealthEndpoint.class)).permitAll()
|
||||||
|
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasAuthority(SecurityConstants.ACCESS_ACTUATORS)
|
||||||
|
.anyRequest().denyAll()
|
||||||
|
.and().httpBasic();
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
package com.flowable.local.work.configuration;
|
||||||
|
|
||||||
|
import com.flowable.autoconfigure.frontend.FrontendProperties;
|
||||||
|
import com.flowable.autoconfigure.security.FlowableHttpSecurityCustomizer;
|
||||||
|
import com.flowable.core.spring.security.web.authentication.AjaxAuthenticationFailureHandler;
|
||||||
|
import com.flowable.core.spring.security.web.authentication.AjaxAuthenticationSuccessHandler;
|
||||||
|
import com.flowable.platform.common.security.SecurityConstants;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||||
|
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
|
||||||
|
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected ObjectProvider<FrontendProperties> frontendPropertiesProvider;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(10)
|
||||||
|
public SecurityFilterChain basicDefaultSecurity(HttpSecurity http, ObjectProvider<FlowableHttpSecurityCustomizer> httpSecurityCustomizers) throws Exception {
|
||||||
|
for (FlowableHttpSecurityCustomizer customizer : httpSecurityCustomizers.orderedStream()
|
||||||
|
.collect(Collectors.toList())) {
|
||||||
|
customizer.customize(http);
|
||||||
|
}
|
||||||
|
|
||||||
|
http
|
||||||
|
.logout()
|
||||||
|
.logoutUrl("/auth/logout");
|
||||||
|
|
||||||
|
FrontendProperties frontendProperties = frontendPropertiesProvider.getIfAvailable();
|
||||||
|
if (frontendProperties != null && frontendProperties.getFeatures().containsKey("formBasedLogout")
|
||||||
|
&& frontendProperties.getFeatures().get("formBasedLogout")) {
|
||||||
|
http
|
||||||
|
.logout()
|
||||||
|
.logoutSuccessUrl("/");
|
||||||
|
} else {
|
||||||
|
http
|
||||||
|
.logout()
|
||||||
|
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
http
|
||||||
|
.exceptionHandling()
|
||||||
|
.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), AnyRequestMatcher.INSTANCE)
|
||||||
|
.and()
|
||||||
|
.formLogin()
|
||||||
|
.loginProcessingUrl("/auth/login")
|
||||||
|
.successHandler(new AjaxAuthenticationSuccessHandler())
|
||||||
|
.failureHandler(new AjaxAuthenticationFailureHandler())
|
||||||
|
.and()
|
||||||
|
.authorizeRequests()
|
||||||
|
.antMatchers("/analytics-api/**").hasAuthority(SecurityConstants.ACCESS_REPORTS_METRICS)
|
||||||
|
.antMatchers("/work-object-api/**").hasAuthority(SecurityConstants.ACCESS_WORKOBJECT_API)
|
||||||
|
// allow context root for all (it triggers the loading of the initial page)
|
||||||
|
.antMatchers("/").permitAll()
|
||||||
|
.antMatchers(
|
||||||
|
"/**/*.svg", "/**/*.ico", "/**/*.png", "/**/*.woff2", "/**/*.css",
|
||||||
|
"/**/*.woff", "/**/*.html", "/**/*.js",
|
||||||
|
"/**/flowable-frontend-configuration",
|
||||||
|
"/**/index.html").permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.and()
|
||||||
|
.httpBasic();
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package com.flowable.local.work.configuration;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.flowable.local.work.service.StartProcessBot;
|
||||||
|
import org.flowable.engine.HistoryService;
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ServiceConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public StartProcessBot activateTaskBot(RuntimeService runtimeService,
|
||||||
|
ObjectMapper objectMapper,
|
||||||
|
HistoryService historyService) {
|
||||||
|
return new StartProcessBot(runtimeService, objectMapper, historyService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
package com.flowable.local.work.configuration;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.CacheControl;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
public class StaticResourceConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
registry.setOrder(10)
|
||||||
|
.addResourceHandler("/ext/*.js", "/*/ext/*.js")
|
||||||
|
.addResourceLocations("classpath:/static/ext/", "classpath:/public/ext/")
|
||||||
|
.setCacheControl(CacheControl.noCache());
|
||||||
|
|
||||||
|
registry.setOrder(20)
|
||||||
|
.addResourceHandler("/ext/*.css", "/*/ext/*.css")
|
||||||
|
.addResourceLocations("classpath:/static/ext/", "classpath:/public/ext/")
|
||||||
|
.setCacheControl(CacheControl.noCache());
|
||||||
|
|
||||||
|
registry.setOrder(30)
|
||||||
|
.addResourceHandler("/*.js")
|
||||||
|
.addResourceLocations("classpath:/public/")
|
||||||
|
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
|
||||||
|
|
||||||
|
registry.setOrder(40)
|
||||||
|
.addResourceHandler("/*.css")
|
||||||
|
.addResourceLocations("classpath:/public/")
|
||||||
|
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
|
||||||
|
|
||||||
|
registry.setOrder(50)
|
||||||
|
.addResourceHandler("/*.woff")
|
||||||
|
.addResourceLocations("classpath:/public/")
|
||||||
|
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
|
||||||
|
|
||||||
|
registry.setOrder(60)
|
||||||
|
.addResourceHandler("/*.woff2")
|
||||||
|
.addResourceLocations("classpath:/public/")
|
||||||
|
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
|
||||||
|
|
||||||
|
registry.setOrder(70)
|
||||||
|
.addResourceHandler("/*.svg")
|
||||||
|
.addResourceLocations("classpath:/public/", "classpath:/public/twemoji/")
|
||||||
|
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
|
||||||
|
|
||||||
|
registry.setOrder(80)
|
||||||
|
.addResourceHandler("/*.map")
|
||||||
|
.addResourceLocations("classpath:/public/")
|
||||||
|
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
|
||||||
|
|
||||||
|
registry.setOrder(90)
|
||||||
|
.addResourceHandler("/*.png")
|
||||||
|
.addResourceLocations("classpath:/public/")
|
||||||
|
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
|
||||||
|
|
||||||
|
registry.setOrder(100)
|
||||||
|
.addResourceHandler("/*.ico")
|
||||||
|
.addResourceLocations("classpath:/public/")
|
||||||
|
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
|
||||||
|
}
|
||||||
|
}
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
package com.flowable.local.work.events;
|
||||||
|
|
||||||
|
import com.flowable.audit.api.AuditService;
|
||||||
|
import com.flowable.dataobject.api.event.FlowableDataObjectEventType;
|
||||||
|
import com.flowable.dataobject.api.runtime.DataObjectInstanceVariableContainer;
|
||||||
|
import com.flowable.dataobject.api.runtime.DataObjectRuntimeService;
|
||||||
|
import com.flowable.dataobject.engine.delegate.event.impl.FlowableDataObjectInstanceDeletedEventImpl;
|
||||||
|
import com.flowable.dataobject.engine.delegate.event.impl.FlowableDataObjectInstanceUpdatedEventImpl;
|
||||||
|
import org.flowable.common.engine.api.delegate.event.FlowableEvent;
|
||||||
|
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
|
||||||
|
import org.flowable.common.engine.api.delegate.event.FlowableEventType;
|
||||||
|
import org.flowable.common.engine.impl.cfg.TransactionState;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class CustomDataObjectEventHandler implements FlowableEventListener {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(CustomDataObjectEventHandler.class);
|
||||||
|
protected final AuditService auditService;
|
||||||
|
protected final DataObjectRuntimeService dataObjectRuntimeService;
|
||||||
|
|
||||||
|
public CustomDataObjectEventHandler(AuditService auditService, DataObjectRuntimeService dataObjectRuntimeService) {
|
||||||
|
this.auditService = auditService;
|
||||||
|
this.dataObjectRuntimeService = dataObjectRuntimeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(FlowableEvent flowableEvent) {
|
||||||
|
DataObjectInstanceVariableContainer dataObjectInstance;
|
||||||
|
String subType;
|
||||||
|
if (flowableEvent instanceof FlowableDataObjectInstanceUpdatedEventImpl) {
|
||||||
|
dataObjectInstance = ((FlowableDataObjectInstanceUpdatedEventImpl) flowableEvent).getDataObjectInstance();
|
||||||
|
subType = "updated";
|
||||||
|
} else if (flowableEvent instanceof FlowableDataObjectInstanceDeletedEventImpl) {
|
||||||
|
dataObjectInstance = ((FlowableDataObjectInstanceDeletedEventImpl) flowableEvent).getDataObjectInstance();
|
||||||
|
subType = "deleted";
|
||||||
|
} else {
|
||||||
|
LOGGER.warn("Unhandled FlowableDataObjectEvent type: {}", flowableEvent.getClass().getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<String, Object> data = (HashMap<String, Object>) dataObjectInstance.getData();
|
||||||
|
if (data == null) {
|
||||||
|
LOGGER.warn("FlowableDataObjectEvent: No data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an instance of TST_DO001 is deactivated, delete all references in other data objects.
|
||||||
|
String scopeDefinitionId = dataObjectInstance.getDefinitionKey();
|
||||||
|
if (scopeDefinitionId == null || !scopeDefinitionId.equals("TST_DO001")) return;
|
||||||
|
Object active = data.get("active");
|
||||||
|
if (!(active instanceof Boolean)) {
|
||||||
|
LOGGER.warn("FlowableDataObjectEvent: 'active' is not boolean");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (subType.equals("updated") && !((Boolean) active) || subType.equals("deleted")) {
|
||||||
|
String key = (String) data.get("key");
|
||||||
|
LOGGER.warn("delete all references of {}", key);
|
||||||
|
dataObjectRuntimeService.createDataObjectModificationBuilder()
|
||||||
|
.tenantId(dataObjectInstance.getTenantId())
|
||||||
|
.definitionKey("TST_DO002")
|
||||||
|
.operation("updateTeam")
|
||||||
|
.value("teamKey", key)
|
||||||
|
.value("team", null)
|
||||||
|
.modify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFailOnException() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFireOnTransactionLifecycleEvent() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOnTransaction() {
|
||||||
|
return TransactionState.COMMITTING.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends FlowableEventType> getTypes() {
|
||||||
|
List<FlowableEventType> eventTypes = new ArrayList<>();
|
||||||
|
eventTypes.add(FlowableDataObjectEventType.DATA_OBJECT_INSTANCE_UPDATED);
|
||||||
|
eventTypes.add(FlowableDataObjectEventType.DATA_OBJECT_INSTANCE_DELETED);
|
||||||
|
return eventTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
package com.flowable.local.work.events;
|
||||||
|
|
||||||
|
import com.flowable.audit.api.AuditService;
|
||||||
|
import com.flowable.core.common.api.security.SecurityScope;
|
||||||
|
import com.flowable.core.spring.security.SecurityUtils;
|
||||||
|
import com.flowable.dataobject.api.event.FlowableDataObjectEventType;
|
||||||
|
import com.flowable.dataobject.api.runtime.DataObjectInstanceVariableContainer;
|
||||||
|
import com.flowable.dataobject.engine.delegate.event.impl.*;
|
||||||
|
import org.flowable.common.engine.api.delegate.event.FlowableEvent;
|
||||||
|
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
|
||||||
|
import org.flowable.common.engine.api.delegate.event.FlowableEventType;
|
||||||
|
import org.flowable.common.engine.impl.cfg.TransactionState;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Writes audit trail entries on changes of data objects.
|
||||||
|
These entries must be retrieved by a specific REST endpoint (implemented in AuditTrailController),
|
||||||
|
because the flowable audit-trail API supports only audit trail entries with scopeType cmmn and bpmn.
|
||||||
|
The class writes the following data into the table FLW_AUDIT_INSTANCE:
|
||||||
|
- ID_ autogenerated ID
|
||||||
|
- CREATION_TIME_ now
|
||||||
|
- CREATOR_ID_ current user ID
|
||||||
|
- TYPE_ "user" or "system"
|
||||||
|
- SUB_TYPE_ "created" / "updated" / "deleted"
|
||||||
|
- SCOPE_DEFINITION_ID_ data object model key
|
||||||
|
- SCOPE_ID_ data object instance ID
|
||||||
|
- SCOPE_TYPE_ "dataObject"
|
||||||
|
- PAYLOAD_ {
|
||||||
|
<data object column name 1>: <content of data object field in column 1>
|
||||||
|
<data object column name 2>: <content of data object field in column 1>
|
||||||
|
etc.
|
||||||
|
}
|
||||||
|
- TENANT_ID_ tenant ID
|
||||||
|
- SUB_SCOPE_ID_ content of data object field in column "key", if column "key" exists
|
||||||
|
-> this mapping makes "key" filterable in a data table
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class GenericDataObjectAuditTrailEventHandler implements FlowableEventListener {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(GenericDataObjectAuditTrailEventHandler.class);
|
||||||
|
protected final AuditService auditService;
|
||||||
|
|
||||||
|
public GenericDataObjectAuditTrailEventHandler(AuditService auditService) {
|
||||||
|
this.auditService = auditService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(FlowableEvent flowableEvent) {
|
||||||
|
DataObjectInstanceVariableContainer dataObjectInstance = null;
|
||||||
|
String scopeDefinitionId = null;
|
||||||
|
String operation = null;
|
||||||
|
Map<String, Object> modificationData = Map.of();
|
||||||
|
String subType;
|
||||||
|
if (flowableEvent instanceof FlowableDataObjectInstanceCreatedEventImpl) {
|
||||||
|
dataObjectInstance = ((FlowableDataObjectInstanceCreatedEventImpl) flowableEvent).getDataObjectInstance();
|
||||||
|
subType = "created";
|
||||||
|
} else if (flowableEvent instanceof FlowableDataObjectInstanceUpdatedEventImpl) {
|
||||||
|
dataObjectInstance = ((FlowableDataObjectInstanceUpdatedEventImpl) flowableEvent).getDataObjectInstance();
|
||||||
|
subType = "updated";
|
||||||
|
} else if (flowableEvent instanceof FlowableDataObjectInstanceDeletedEventImpl) {
|
||||||
|
dataObjectInstance = ((FlowableDataObjectInstanceDeletedEventImpl) flowableEvent).getDataObjectInstance();
|
||||||
|
subType = "deleted";
|
||||||
|
} else if (flowableEvent instanceof FlowableDataObjectInstancesBulkUpdatedEventImpl) {
|
||||||
|
scopeDefinitionId = ((FlowableDataObjectInstancesBulkUpdatedEventImpl) flowableEvent).getDataObjectDefinition().getKey();
|
||||||
|
operation = ((FlowableDataObjectInstancesBulkUpdatedEventImpl) flowableEvent).getOperation();
|
||||||
|
modificationData = ((FlowableDataObjectInstancesBulkUpdatedEventImpl) flowableEvent).getModificationData();
|
||||||
|
subType = "updatedBulk";
|
||||||
|
} else if (flowableEvent instanceof FlowableDataObjectInstancesBulkDeletedEventImpl) {
|
||||||
|
scopeDefinitionId = ((FlowableDataObjectInstancesBulkDeletedEventImpl) flowableEvent).getDataObjectDefinition().getKey();
|
||||||
|
operation = ((FlowableDataObjectInstancesBulkDeletedEventImpl) flowableEvent).getOperation();
|
||||||
|
subType = "deletedBulk";
|
||||||
|
} else {
|
||||||
|
LOGGER.warn("Unhandled FlowableDataObjectEvent type: {}", flowableEvent.getClass().getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> payload = new HashMap<>();
|
||||||
|
String scopeId = null;
|
||||||
|
String subScopeId = null;
|
||||||
|
SecurityScope currentUserSecurityScope = SecurityUtils.getCurrentUserSecurityScopeSafe();
|
||||||
|
|
||||||
|
if (subType.equals("created") || subType.equals("updated") || subType.equals("deleted")) {
|
||||||
|
HashMap<String, Object> data = (HashMap<String, Object>) dataObjectInstance.getData();
|
||||||
|
if (data == null) {
|
||||||
|
LOGGER.warn("FlowableDataObjectEvent: No data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scopeDefinitionId = dataObjectInstance.getDefinitionKey();
|
||||||
|
|
||||||
|
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||||
|
if (entry.getKey().equals("id")) {
|
||||||
|
scopeId = (String) entry.getValue();
|
||||||
|
} else if (entry.getKey().equals("key")) {
|
||||||
|
// If there is a "key" in the data object, expose it as subScopeId for making it filterable.
|
||||||
|
subScopeId = (String) entry.getValue();
|
||||||
|
} else {
|
||||||
|
payload.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
payload.put("operation", operation);
|
||||||
|
payload.putAll(modificationData);
|
||||||
|
}
|
||||||
|
if (currentUserSecurityScope == null) {
|
||||||
|
auditService.createAuditInstanceBuilder()
|
||||||
|
.type("system")
|
||||||
|
.subType(subType)
|
||||||
|
.scopeType("dataObject")
|
||||||
|
.scopeDefinitionId(scopeDefinitionId)
|
||||||
|
.scopeId(scopeId)
|
||||||
|
.subScopeId(subScopeId)
|
||||||
|
.payload(payload)
|
||||||
|
.create();
|
||||||
|
} else {
|
||||||
|
auditService.createAuditInstanceBuilder()
|
||||||
|
.type("user")
|
||||||
|
.creatorId(currentUserSecurityScope.getUserId())
|
||||||
|
.tenantId(currentUserSecurityScope.getTenantId())
|
||||||
|
.subType(subType)
|
||||||
|
.scopeType("dataObject")
|
||||||
|
.scopeDefinitionId(scopeDefinitionId)
|
||||||
|
.scopeId(scopeId)
|
||||||
|
.subScopeId(subScopeId)
|
||||||
|
.payload(payload)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFailOnException() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFireOnTransactionLifecycleEvent() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOnTransaction() {
|
||||||
|
return TransactionState.COMMITTING.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends FlowableEventType> getTypes() {
|
||||||
|
List<FlowableEventType> eventTypes = new ArrayList<>();
|
||||||
|
eventTypes.add(FlowableDataObjectEventType.DATA_OBJECT_INSTANCE_CREATED);
|
||||||
|
eventTypes.add(FlowableDataObjectEventType.DATA_OBJECT_INSTANCE_UPDATED);
|
||||||
|
eventTypes.add(FlowableDataObjectEventType.DATA_OBJECT_INSTANCE_DELETED);
|
||||||
|
return eventTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
package com.flowable.local.work.rest;
|
||||||
|
|
||||||
|
import com.flowable.autoconfigure.platform.PropertyConfigurationService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
http://localhost:8090/custom-api/application-property/keyDoesNotExist -> null
|
||||||
|
http://localhost:8090/custom-api/application-property/string/keyDoesNotExist -> null
|
||||||
|
http://localhost:8090/custom-api/application-property/keyDoesNotExist?default=abc -> abc
|
||||||
|
http://localhost:8090/custom-api/application-property/string/keyDoesNotExist?default=abc -> abc
|
||||||
|
http://localhost:8090/custom-api/application-property/server.port -> 8090
|
||||||
|
http://localhost:8090/custom-api/application-property/string/server.port -> 8090
|
||||||
|
http://localhost:8090/custom-api/application-property/server.port?default=abc -> 8090
|
||||||
|
http://localhost:8090/custom-api/application-property/string/server.port?default=abc -> 8090
|
||||||
|
http://localhost:8090/custom-api/application-property/boolean/keyDoesNotExist -> false
|
||||||
|
http://localhost:8090/custom-api/application-property/boolean/keyDoesNotExist?default=true -> true
|
||||||
|
http://localhost:8090/custom-api/application-property/boolean/flowable.platform.idm.minimal-setup -> true
|
||||||
|
http://localhost:8090/custom-api/application-property/boolean/flowable.platform.idm.minimal-setup?default=false -> true
|
||||||
|
http://localhost:8090/custom-api/application-property/integer/keyDoesNotExist -> 0
|
||||||
|
http://localhost:8090/custom-api/application-property/integer/keyDoesNotExist?default=123 -> true
|
||||||
|
http://localhost:8090/custom-api/application-property/integer/server.port -> 8090
|
||||||
|
http://localhost:8090/custom-api/application-property/integer/server.port?default=123 -> 8090
|
||||||
|
|
||||||
|
http://localhost:8090/custom-api/application-property/integer/flowable.platform.idm.minimal-setup -> ConversionFailedException
|
||||||
|
*/
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/custom-api")
|
||||||
|
@Validated
|
||||||
|
public class ApplicationPropertyController {
|
||||||
|
|
||||||
|
protected final PropertyConfigurationService propertyConfigurationService;
|
||||||
|
|
||||||
|
public ApplicationPropertyController(PropertyConfigurationService propertyConfigurationService) {
|
||||||
|
this.propertyConfigurationService = propertyConfigurationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = {"/application-property/{key}", "/application-property/string/{key}"})
|
||||||
|
public ResponseEntity<String> getStringVariable(
|
||||||
|
@PathVariable String key,
|
||||||
|
@RequestParam(name = "default", required = false) String defaultValue) {
|
||||||
|
return ResponseEntity.ok(propertyConfigurationService.getProperty(key, defaultValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/application-property/boolean/{key}")
|
||||||
|
public ResponseEntity<Boolean> getBooleanVariable(
|
||||||
|
@PathVariable String key,
|
||||||
|
@RequestParam(name = "default", required = false) Boolean defaultValue) {
|
||||||
|
if (defaultValue == null) {
|
||||||
|
defaultValue = false;
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(propertyConfigurationService.getBooleanProperty(key, defaultValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/application-property/integer/{key}")
|
||||||
|
public ResponseEntity<Integer> getIntegerVariable(
|
||||||
|
@PathVariable String key,
|
||||||
|
@RequestParam(name = "default", required = false) Integer defaultValue) {
|
||||||
|
if (defaultValue == null) {
|
||||||
|
defaultValue = 0;
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(propertyConfigurationService.getIntegerProperty(key, defaultValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
package com.flowable.local.work.rest;
|
||||||
|
|
||||||
|
import com.flowable.local.work.service.AuditTrailService;
|
||||||
|
import org.flowable.common.rest.api.DataResponse;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Examples:
|
||||||
|
http://localhost:8090/custom-api/audit-trail?scopeType=dataObject&dataObjectModelKey=TST_DO001
|
||||||
|
http://localhost:8090/custom-api/audit-trail?scopeType=dataObject&dataObjectId=d05bd02b-3f8a-11f0-9e89-2e48f5a806de
|
||||||
|
http://localhost:8090/custom-api/audit-trail?scopeType=dataObject&scopeId=d05bd02b-3f8a-11f0-9e89-2e48f5a806de
|
||||||
|
http://localhost:8090/custom-api/audit-trail?scopeType=cmmn&instanceName=TST-000001
|
||||||
|
http://localhost:8090/custom-api/audit-trail?scopeType=cmmn&rootId=CAS-9270a424-3fa3-11f0-8c07-2e48f5a806de
|
||||||
|
http://localhost:8090/custom-api/audit-trail?scopeType=cmmn&scopeId=CAS-9270a424-3fa3-11f0-8c07-2e48f5a806de
|
||||||
|
*/
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/custom-api")
|
||||||
|
@Validated
|
||||||
|
public class AuditTrailController {
|
||||||
|
|
||||||
|
protected final AuditTrailService auditTrailService;
|
||||||
|
|
||||||
|
public AuditTrailController(AuditTrailService auditTrailService) {
|
||||||
|
this.auditTrailService = auditTrailService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = {"/audit-trail"}, produces = {"application/json"})
|
||||||
|
public DataResponse<HashMap<String, Object>> getAuditTrail(
|
||||||
|
@RequestParam(name = "scopeType") @NotNull @NotEmpty String scopeType,
|
||||||
|
@RequestParam(name = "scopeId", required = false) String scopeId,
|
||||||
|
@RequestParam(name = "rootId", required = false) String rootId,
|
||||||
|
@RequestParam(name = "instanceName", required = false) String instanceName,
|
||||||
|
@RequestParam(name = "dataObjectId", required = false) String dataObjectId,
|
||||||
|
@RequestParam(name = "dataObjectModelKey", required = false) String dataObjectModelKey,
|
||||||
|
@RequestParam(name = "start", required = false) Integer start,
|
||||||
|
@RequestParam(name = "size", required = false) Integer size,
|
||||||
|
@RequestParam(name = "createdAfter", required = false) String createdAfter,
|
||||||
|
@RequestParam(name = "createdBefore", required = false) String createdBefore,
|
||||||
|
@RequestParam(name = "creatorId", required = false) String creatorId,
|
||||||
|
@RequestParam(name = "subType", required = false) String subType,
|
||||||
|
@RequestParam(name = "subScopeId", required = false) String subScopeId) {
|
||||||
|
|
||||||
|
return this.auditTrailService.getAuditTrail(scopeType, scopeId, rootId, instanceName, dataObjectId, dataObjectModelKey,
|
||||||
|
start, size, createdAfter, createdBefore, creatorId, subType, subScopeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.flowable.local.work.rest;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.flowable.local.work.service.UserService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Examples:
|
||||||
|
http://localhost:8090/custom-api/current-user-groups
|
||||||
|
http://localhost:8090/custom-api/user-groups/{userId}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/custom-api")
|
||||||
|
@Validated
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
protected final UserService userService;
|
||||||
|
|
||||||
|
public UserController(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = {"/current-user-groups"}, produces = {"application/json"})
|
||||||
|
public ResponseEntity<ArrayNode> getCurrentUserGroups() {
|
||||||
|
return ResponseEntity.ok(this.userService.getCurrentUserGroups());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = {"/user-groups/{userId}"}, produces = {"application/json"})
|
||||||
|
public ResponseEntity<ArrayNode> getUserGroups(@PathVariable String userId) {
|
||||||
|
return ResponseEntity.ok(this.userService.getUserGroups(userId));
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package com.flowable.local.work.rest;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.flowable.local.work.service.VariableService;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
http://localhost:8090/custom-api/variable/CAS-e5d25542-0947-11f0-8b0d-e6be909bfdcd/caseNname
|
||||||
|
http://localhost:8090/custom-api/variable/CAS-e5d25542-0947-11f0-8b0d-e6be909bfdcd
|
||||||
|
http://localhost:8090/custom-api/variable/root
|
||||||
|
|
||||||
|
http://localhost:8090/platform-api/case-instances/CAS-5dc9c774-42d4-11f0-b269-2e48f5a806de/work-form/variables
|
||||||
|
http://localhost:8090/custom-api/variable/CAS-5dc9c774-42d4-11f0-b269-2e48f5a806de/name
|
||||||
|
*/
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/custom-api")
|
||||||
|
public class VariableController {
|
||||||
|
|
||||||
|
protected final VariableService variableService;
|
||||||
|
|
||||||
|
public VariableController(VariableService variableService) {
|
||||||
|
this.variableService = variableService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/variable/{caseInstanceId}")
|
||||||
|
public ResponseEntity<JsonNode> getRootVariable(@PathVariable String caseInstanceId) {
|
||||||
|
return ResponseEntity.ok(variableService.getVariable(caseInstanceId, "root"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/variable/{caseInstanceId}/{variableName}")
|
||||||
|
public ResponseEntity<JsonNode> getSpecificVariable(@PathVariable String caseInstanceId, @PathVariable String variableName) {
|
||||||
|
return ResponseEntity.ok(variableService.getVariable(caseInstanceId, variableName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public String handleMissingServletRequestParameterException() {
|
||||||
|
return "Variable not found";
|
||||||
|
}
|
||||||
|
}
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
package com.flowable.local.work.service;
|
||||||
|
|
||||||
|
import com.flowable.audit.api.AuditService;
|
||||||
|
import com.flowable.audit.api.runtime.AuditInstance;
|
||||||
|
import com.flowable.audit.api.runtime.AuditInstanceQuery;
|
||||||
|
import com.flowable.audit.engine.impl.persistence.entity.AuditInstanceEntity;
|
||||||
|
import org.flowable.cmmn.api.history.HistoricCaseInstance;
|
||||||
|
import org.flowable.cmmn.api.runtime.CaseInstance;
|
||||||
|
import org.flowable.cmmn.engine.CmmnEngine;
|
||||||
|
import org.flowable.common.rest.api.DataResponse;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns audit trail entries based on:
|
||||||
|
- scopeType cmmn
|
||||||
|
- rootId <root.id>
|
||||||
|
-> audit trail entries for case with specified id
|
||||||
|
- instanceName <root.name>
|
||||||
|
-> audit trail entries for case with specified name
|
||||||
|
- scopeType dataObject
|
||||||
|
- dataObjectId <data object instance id>
|
||||||
|
-> audit trail entries for data object with specified instance id, provided by DataObjectAuditTrailEventHandler
|
||||||
|
- dataObjectModelKey <data object model key>
|
||||||
|
-> audit trail entries for data object with specified model key, provided by DataObjectAuditTrailEventHandler
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class AuditTrailService {
|
||||||
|
protected static final Logger LOGGER = LoggerFactory.getLogger(AuditTrailService.class);
|
||||||
|
protected final CmmnEngine cmmnEngine;
|
||||||
|
protected final AuditService auditService;
|
||||||
|
|
||||||
|
public AuditTrailService(CmmnEngine cmmnEngine, AuditService auditService) {
|
||||||
|
this.cmmnEngine = cmmnEngine;
|
||||||
|
this.auditService = auditService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataResponse<HashMap<String, Object>> getAuditTrail(String scopeType, String scopeId, String rootId, String instanceName,
|
||||||
|
String dataObjectId, String dataObjectModelKey,
|
||||||
|
Integer start, Integer size,
|
||||||
|
String createdAfter, String createdBefore,
|
||||||
|
String creatorId, String subType, String subScopeId) {
|
||||||
|
String scopeDefinitionId = null;
|
||||||
|
switch (scopeType) {
|
||||||
|
case "cmmn":
|
||||||
|
// Check for rootId or instanceName if scopeId is not provided
|
||||||
|
if (scopeId == null || scopeId.trim().isEmpty()) {
|
||||||
|
if (rootId != null && !rootId.trim().isEmpty()) {
|
||||||
|
scopeId = rootId;
|
||||||
|
} else if (instanceName != null && !instanceName.trim().isEmpty()) {
|
||||||
|
CaseInstance caseInstance = this.cmmnEngine.getCmmnRuntimeService().createCaseInstanceQuery().caseInstanceBusinessKey("asdf").singleResult();
|
||||||
|
// CaseInstance caseInstance = this.cmmnEngine.getCmmnRuntimeService().createCaseInstanceQuery().caseInstanceName(instanceName).singleResult();
|
||||||
|
if (caseInstance != null) {
|
||||||
|
scopeId = caseInstance.getId();
|
||||||
|
} else {
|
||||||
|
HistoricCaseInstance historicCaseInstance = this.cmmnEngine.getCmmnHistoryService().createHistoricCaseInstanceQuery().caseInstanceName(instanceName).singleResult();
|
||||||
|
if (historicCaseInstance != null) {
|
||||||
|
scopeId = historicCaseInstance.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGGER.warn("Missing parameter rootId or instanceName for scopeType=cmmn");
|
||||||
|
return createAuditEntryDataResponse(null, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "dataObject":
|
||||||
|
// Check for dataObjectId or dataObjectModelKey if scopeId is not provided
|
||||||
|
if (scopeId == null || scopeId.trim().isEmpty()) {
|
||||||
|
if (dataObjectId != null && !dataObjectId.trim().isEmpty()) {
|
||||||
|
scopeId = dataObjectId;
|
||||||
|
} else if (dataObjectModelKey != null && !dataObjectModelKey.trim().isEmpty()) {
|
||||||
|
scopeDefinitionId = dataObjectModelKey;
|
||||||
|
} else {
|
||||||
|
LOGGER.warn("Missing parameter dataObjectId or dataObjectModelKey for scopeType=dataObject");
|
||||||
|
return createAuditEntryDataResponse(null, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGGER.warn("Invalid scopeType: {}", scopeType);
|
||||||
|
return createAuditEntryDataResponse(null, 0, 0);
|
||||||
|
}
|
||||||
|
return queryAuditTrail(scopeType, scopeId, scopeDefinitionId, start, size, createdAfter, createdBefore, creatorId, subType, subScopeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DataResponse<HashMap<String, Object>> queryAuditTrail(String scopeType, String scopeId,
|
||||||
|
String scopeDefinitionId,
|
||||||
|
Integer start, Integer size,
|
||||||
|
String createdAfter, String createdBefore,
|
||||||
|
String creatorId, String subType, String subScopeId) {
|
||||||
|
// Create query from parameters
|
||||||
|
AuditInstanceQuery query = this.auditService.createAuditInstanceQuery().orderByCreationTime().desc();
|
||||||
|
query.scopeType(scopeType);
|
||||||
|
query.scopeId(scopeId);
|
||||||
|
query.scopeDefinitionId(scopeDefinitionId);
|
||||||
|
if (createdAfter != null && !createdAfter.trim().isEmpty()) {
|
||||||
|
query.createdAfter(DateTime.parse(createdAfter).toDate());
|
||||||
|
}
|
||||||
|
if (createdBefore != null && !createdBefore.trim().isEmpty()) {
|
||||||
|
query.createdBefore(DateTime.parse(createdBefore).toDate());
|
||||||
|
}
|
||||||
|
query.creatorId(creatorId);
|
||||||
|
query.subType(subType);
|
||||||
|
query.subScopeId(subScopeId);
|
||||||
|
|
||||||
|
// Run paged query
|
||||||
|
int startIndex = start == null ? 0 : start;
|
||||||
|
List<AuditInstance> auditInstances;
|
||||||
|
if (size != null) {
|
||||||
|
auditInstances = query.listPage(startIndex, size);
|
||||||
|
} else {
|
||||||
|
auditInstances = query.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map query result into proper list
|
||||||
|
List<HashMap<String, Object>> entries = new ArrayList<>();
|
||||||
|
for (AuditInstance auditInstance : auditInstances) {
|
||||||
|
entries.add(convertAuditInstance(auditInstance));
|
||||||
|
}
|
||||||
|
return createAuditEntryDataResponse(entries, query.count(), startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DataResponse<HashMap<String, Object>> createAuditEntryDataResponse(List<HashMap<String, Object>> entries, long totalCount, int startIndex) {
|
||||||
|
DataResponse<HashMap<String, Object>> auditTrail = new DataResponse<>();
|
||||||
|
if (entries != null && !entries.isEmpty()) {
|
||||||
|
auditTrail.setData(entries);
|
||||||
|
auditTrail.setTotal(totalCount);
|
||||||
|
auditTrail.setStart(startIndex);
|
||||||
|
auditTrail.setSize(entries.size());
|
||||||
|
}
|
||||||
|
auditTrail.setSort("creationTime");
|
||||||
|
auditTrail.setOrder("desc");
|
||||||
|
return auditTrail;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HashMap<String, Object> convertAuditInstance(AuditInstance auditInstance) {
|
||||||
|
HashMap<String, Object> auditEntry = new HashMap<>();
|
||||||
|
AuditInstanceEntity auditInst = (AuditInstanceEntity) auditInstance;
|
||||||
|
auditEntry.put("id", auditInst.getId());
|
||||||
|
auditEntry.putAll((Map<String, String>) auditInst.getPersistentState());
|
||||||
|
|
||||||
|
HashMap<String, Object> auditInstPayload = (HashMap<String, Object>) auditInst.getPayload();
|
||||||
|
if (auditInstPayload != null) {
|
||||||
|
auditEntry.put("payload", new HashMap<>(auditInstPayload));
|
||||||
|
}
|
||||||
|
return auditEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package com.flowable.local.work.service;
|
||||||
|
|
||||||
|
import com.flowable.dataobject.api.runtime.DataObjectInstanceVariableContainer;
|
||||||
|
import com.flowable.dataobject.api.runtime.DataObjectInstanceVariableContainerBuilder;
|
||||||
|
import com.flowable.dataobject.api.runtime.DataObjectRuntimeService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DataObjectUtils {
|
||||||
|
|
||||||
|
protected final DataObjectRuntimeService dataObjectRuntimeService;
|
||||||
|
|
||||||
|
public DataObjectUtils(DataObjectRuntimeService dataObjectRuntimeService) {
|
||||||
|
this.dataObjectRuntimeService = dataObjectRuntimeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void create(String tenantId, String definitionKey) {
|
||||||
|
DataObjectInstanceVariableContainerBuilder dataObjectValueInstanceBuilder = dataObjectRuntimeService.createDataObjectValueInstanceBuilder();
|
||||||
|
dataObjectValueInstanceBuilder.tenantId(tenantId);
|
||||||
|
dataObjectValueInstanceBuilder.definitionKey(definitionKey);
|
||||||
|
/*TODO: iterate list
|
||||||
|
for (entry : list) {
|
||||||
|
dataObjectValueInstanceBuilder.value(entry.key(), entry.value());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
dataObjectValueInstanceBuilder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(String tenantId, String definitionKey) {
|
||||||
|
List<DataObjectInstanceVariableContainer> dataObjects = dataObjectRuntimeService.createDataObjectInstanceQuery()
|
||||||
|
.tenantId(tenantId)
|
||||||
|
.definitionKey(definitionKey)
|
||||||
|
.operation("searchAll")
|
||||||
|
.list();
|
||||||
|
for (DataObjectInstanceVariableContainer dataObjectInstanceVariableContainer : dataObjects) {
|
||||||
|
dataObjectRuntimeService.deleteDataObject(dataObjectInstanceVariableContainer.getLookupId().toString(),
|
||||||
|
dataObjectInstanceVariableContainer.getDefinitionId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.flowable.local.work.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.github.wnameless.json.flattener.JsonFlattener;
|
||||||
|
import com.github.wnameless.json.unflattener.JsonUnflattener;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JsonUtils {
|
||||||
|
|
||||||
|
protected static final Logger LOGGER = LoggerFactory.getLogger(JsonUtils.class);
|
||||||
|
protected final ObjectMapper objectMapper;
|
||||||
|
protected final JavaTimeModule javaTimeModule;
|
||||||
|
|
||||||
|
public JsonUtils(ObjectMapper objectMapper) {
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.javaTimeModule = new JavaTimeModule();
|
||||||
|
|
||||||
|
// Enable ObjectMapper for handling Instant as string
|
||||||
|
this.objectMapper.registerModule(javaTimeModule);
|
||||||
|
this.objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonNode convertListToJsonNode(List<Object> list) {
|
||||||
|
return objectMapper.valueToTree(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonNode convertMapToJsonNode(Map<String, Object> map) {
|
||||||
|
return objectMapper.valueToTree(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> convertJsonNodeToMap(JsonNode jsonNode) {
|
||||||
|
return objectMapper.convertValue(jsonNode, new TypeReference<Map<String, Object>>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> flatten(Object payload) {
|
||||||
|
try {
|
||||||
|
return JsonFlattener.flattenAsMap(objectMapper.writeValueAsString(payload));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> unflatten(Map<String, Object> flatVars) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(JsonUnflattener.unflatten(flatVars), Map.class);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonNode convertJsonStringToJsonNode(String jsonString) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readTree(jsonString);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> convertObjectNodeToMap(ObjectNode objectNode) {
|
||||||
|
return objectMapper.convertValue(objectNode, new TypeReference<Map<String, Object>>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectNode getEmptyObjectNode() {
|
||||||
|
return objectMapper.createObjectNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectNode loadObjectNodeFromFile(String path) {
|
||||||
|
try {
|
||||||
|
return (ObjectNode) this.objectMapper.readTree(new File(path));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.debug(e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayNode convertObjectToArrayNode(Object object) {
|
||||||
|
if (object == null) {
|
||||||
|
LOGGER.debug("{}: Argument is null", this.getClass().getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String obyTypeName = object.getClass().getName();
|
||||||
|
if (obyTypeName.equals("java.util.Collections$EmptyList") || obyTypeName.equals("java.util.ArrayList")) {
|
||||||
|
return objectMapper.convertValue(object, ArrayNode.class);
|
||||||
|
} else if (obyTypeName.equals("com.fasterxml.jackson.databind.node.ArrayNode")) {
|
||||||
|
return (ArrayNode) object;
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("{}: {} is not implemented}", this.getClass().getName(), obyTypeName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package com.flowable.local.work.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.flowable.action.api.bot.BaseBotActionResult;
|
||||||
|
import com.flowable.action.api.bot.BotActionResult;
|
||||||
|
import com.flowable.action.api.bot.BotService;
|
||||||
|
import com.flowable.action.api.history.HistoricActionInstance;
|
||||||
|
import com.flowable.action.api.intents.Intent;
|
||||||
|
import com.flowable.action.api.repository.ActionDefinition;
|
||||||
|
import org.flowable.engine.HistoryService;
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class StartProcessBot implements BotService {
|
||||||
|
protected static final Logger LOGGER = LoggerFactory.getLogger(StartProcessBot.class);
|
||||||
|
public static final String BOT_KEY = "start-process-bot";
|
||||||
|
public static final String BOT_NAME = "start process bot";
|
||||||
|
public static final String BOT_DESCRIPTION = "This bot starts a process.";
|
||||||
|
|
||||||
|
private final RuntimeService runtimeService;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final HistoryService historyService;
|
||||||
|
|
||||||
|
public StartProcessBot(RuntimeService runtimeService,
|
||||||
|
ObjectMapper objectMapper,
|
||||||
|
HistoryService historyService) {
|
||||||
|
this.runtimeService = runtimeService;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.historyService = historyService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKey() { return BOT_KEY; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return BOT_NAME; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() { return BOT_DESCRIPTION; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BotActionResult invokeBot(HistoricActionInstance actionInstance, ActionDefinition actionDefinition, Map<String, Object> payload) {
|
||||||
|
LOGGER.info(getKey() + " starting...");
|
||||||
|
String rootCaseId = actionInstance.getScopeId();
|
||||||
|
if (rootCaseId == null) {
|
||||||
|
throw new RuntimeException(getKey() + " cannot get Scope Id from action button");
|
||||||
|
}
|
||||||
|
String taskModelId = null;
|
||||||
|
try {
|
||||||
|
taskModelId = payload.get("taskModelId").toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
if (taskModelId == null) {
|
||||||
|
throw new RuntimeException(getKey() + " cannot get taskModelId from payload");
|
||||||
|
}
|
||||||
|
LOGGER.info(getKey() + " starting task instance with taskModelId " + taskModelId + "...");
|
||||||
|
ProcessInstance processInstance = this.runtimeService.createProcessInstanceBuilder()
|
||||||
|
.processDefinitionKey(taskModelId)
|
||||||
|
.tenantId(actionInstance.getTenantId())
|
||||||
|
.variables(payload)
|
||||||
|
.start();
|
||||||
|
if (processInstance == null) {
|
||||||
|
throw new RuntimeException(getKey() + " cannot find enabled task instance with taskModelId " + taskModelId);
|
||||||
|
}
|
||||||
|
LOGGER.info(getKey() + " task instance with taskModelId " + taskModelId + " successfully started");
|
||||||
|
|
||||||
|
Map<String, Object> processVariables = processInstance.getProcessVariables();
|
||||||
|
ObjectNode responsePayload = this.objectMapper.convertValue(processVariables, ObjectNode.class);
|
||||||
|
responsePayload.put("id", processInstance.getId());
|
||||||
|
return new BaseBotActionResult(responsePayload, Intent.NOOP);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package com.flowable.local.work.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import com.flowable.core.common.api.security.SecurityScope;
|
||||||
|
import com.flowable.core.idm.api.*;
|
||||||
|
import com.flowable.core.spring.security.SecurityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns user information:
|
||||||
|
- getCurrentUserGroups()
|
||||||
|
- getUserGroups(userId)
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class UserService {
|
||||||
|
|
||||||
|
protected static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);
|
||||||
|
protected final PlatformIdentityService platformIdentityService;
|
||||||
|
|
||||||
|
public UserService(PlatformIdentityService platformIdentityService) {
|
||||||
|
this.platformIdentityService = platformIdentityService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public ArrayNode getCurrentUserGroups() {
|
||||||
|
LOGGER.debug("Getting current user groups");
|
||||||
|
SecurityScope currentUserSecurityScope = SecurityUtils.getCurrentUserSecurityScopeSafe();
|
||||||
|
List<String> stringList = new ArrayList<>(currentUserSecurityScope.getGroupKeys());
|
||||||
|
return createSortedArrayNode(stringList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
protected static ArrayNode createSortedArrayNode(List<String> stringList) {
|
||||||
|
Collections.sort(stringList);
|
||||||
|
ArrayNode roles = JsonNodeFactory.instance.arrayNode();
|
||||||
|
for(String role : stringList) {
|
||||||
|
// Apply a role filter here if necessary
|
||||||
|
roles.add(role);
|
||||||
|
}
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public ArrayNode getUserGroups(String userId) {
|
||||||
|
LOGGER.debug("Getting groups of user {}", userId);
|
||||||
|
SecurityScope currentUserSecurityScope = SecurityUtils.getCurrentUserSecurityScopeSafe();
|
||||||
|
// Get groups via User Query
|
||||||
|
List<String> groupStringList = getLdapEntitlements(userId, currentUserSecurityScope.getTenantId());
|
||||||
|
if (groupStringList.isEmpty()) {
|
||||||
|
// Get groups via Group Query
|
||||||
|
groupStringList = getPlatformGroups(userId, currentUserSecurityScope);
|
||||||
|
}
|
||||||
|
return createSortedArrayNode(groupStringList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
protected List<String> getPlatformGroups(String userId, SecurityScope currentUserSecurityScope) {
|
||||||
|
List<PlatformGroup> platformGroups = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
platformGroups = platformIdentityService.createPlatformGroupQuery()
|
||||||
|
.groupTenantId(currentUserSecurityScope.getTenantId())
|
||||||
|
.groupMember(userId)
|
||||||
|
.list();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
List<String> stringList = new ArrayList<>();
|
||||||
|
for(PlatformGroup platformGroup : platformGroups) {
|
||||||
|
stringList.add(platformGroup.getKey());
|
||||||
|
}
|
||||||
|
return stringList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
protected List<String> getLdapEntitlements(String userId, String tenantId) {
|
||||||
|
PlatformUser platformUser = platformIdentityService.createPlatformUserQuery()
|
||||||
|
.tenantId(tenantId)
|
||||||
|
.userId(userId)
|
||||||
|
.includeIdentityInfo()
|
||||||
|
.singleResult();
|
||||||
|
return Optional.ofNullable(platformUser)
|
||||||
|
.map(PlatformUser::getIdentityInfo)
|
||||||
|
.flatMap(identityInfos -> identityInfos.stream()
|
||||||
|
.filter(ifInfo -> "ldap_entitlement".equals(ifInfo.getName()))
|
||||||
|
.findFirst())
|
||||||
|
.map(PlatformIdentityInfo::getValue)
|
||||||
|
.filter(value -> value instanceof String) // Ensure that value is String
|
||||||
|
.map(value -> (String) value)
|
||||||
|
.map(value -> Arrays.stream(value.split(";"))
|
||||||
|
.filter(val -> !val.trim().isEmpty())
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.orElseGet(List::of);
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package com.flowable.local.work.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import org.flowable.cmmn.api.CmmnRuntimeService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class VariableService {
|
||||||
|
protected final ObjectMapper objectMapper;
|
||||||
|
protected final CmmnRuntimeService cmmnRuntimeService;
|
||||||
|
|
||||||
|
public VariableService(ObjectMapper objectMapper, CmmnRuntimeService cmmnRuntimeService) {
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.objectMapper.registerModule(new JavaTimeModule());
|
||||||
|
this.objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||||
|
|
||||||
|
this.cmmnRuntimeService = cmmnRuntimeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonNode getVariable(@PathVariable String caseInstanceId, String variableName) {
|
||||||
|
// Check parameters
|
||||||
|
|
||||||
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
|
map.put("caseInstanceId", caseInstanceId);
|
||||||
|
map.put("variableName", variableName);
|
||||||
|
map.put("value", this.cmmnRuntimeService.getVariables(caseInstanceId));
|
||||||
|
return this.objectMapper.convertValue(map, JsonNode.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# == Embedded DB ==
|
||||||
|
spring.datasource.driver-class-name=org.h2.Driver
|
||||||
|
spring.datasource.url=jdbc:h2:file:./h2-database/flowable-work;DB_CLOSE_ON_EXIT=FALSE
|
||||||
|
|
||||||
|
# == Elasticless ==
|
||||||
|
flowable.indexing.enabled=false
|
||||||
|
management.health.elasticsearch.enabled=false
|
||||||
|
management.metrics.export.elastic.enabled=false
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
flowable.license.db-store-enabled=true
|
||||||
|
flowable.inspect.enabled=false
|
||||||
|
# Pretty-print JSON responses
|
||||||
|
spring.jackson.serialization.indent_output=false
|
||||||
|
# Forms will update even if an old process/case/task definition will be used
|
||||||
|
flowable.platform.enable-latest-form-definition-lookup=false
|
||||||
|
# Environment identifier
|
||||||
|
info.env.name=
|
||||||
|
# In order for hot swapping to work for the custom.js and custom.css
|
||||||
|
spring.thymeleaf.cache=true
|
||||||
|
spring.web.resources.chain.cache=true
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
server.port=8090
|
||||||
|
|
||||||
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||||
|
spring.datasource.url=jdbc:postgresql://localhost:5432/flowable
|
||||||
|
spring.datasource.username=flowable
|
||||||
|
spring.datasource.password=flowable
|
||||||
|
|
||||||
|
# Enable all endpoints over HTTP
|
||||||
|
management.endpoints.web.exposure.include=*
|
||||||
|
management.endpoint.health.show-details=ALWAYS
|
||||||
|
|
||||||
|
# Set this false, to disable metrics export to elasticsearch
|
||||||
|
# management.metrics.export.elastic.enabled=false
|
||||||
|
|
||||||
|
flowable.indexing.index-name-prefix=local-
|
||||||
|
flowable.security.impersonate.allowed=true
|
||||||
|
|
||||||
|
# Set this to true to get a clean IDM setup with only an admin user
|
||||||
|
flowable.platform.idm.minimal-setup=true
|
||||||
|
flowable.platform.idm.default-password=test
|
||||||
|
|
||||||
|
# == Development Properties ==
|
||||||
|
flowable.license.db-store-enabled=false
|
||||||
|
flowable.inspect.enabled=true
|
||||||
|
# Pretty-print JSON responses
|
||||||
|
spring.jackson.serialization.indent_output=true
|
||||||
|
# Forms will update even if an old process/case/task definition will be used
|
||||||
|
flowable.platform.enable-latest-form-definition-lookup=true
|
||||||
|
# Environment identifier
|
||||||
|
info.env.name=Development
|
||||||
|
# In order for hot swapping to work for the custom.js and custom.css
|
||||||
|
spring.thymeleaf.cache=false
|
||||||
|
spring.web.resources.chain.cache=false
|
||||||
|
|
||||||
|
# Path to auto-deploy-apps
|
||||||
|
flowable.app.resource-location=classpath*:/auto-deploy-apps/
|
||||||
|
|
||||||
|
# Email
|
||||||
|
flowable.mail.server.host=localhost
|
||||||
|
flowable.mail.server.port=2525
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"key": "md-country",
|
||||||
|
"name": "Country",
|
||||||
|
"description": "The master data definition for countries.",
|
||||||
|
|
||||||
|
"dataObjectType": "masterData",
|
||||||
|
"type": "internal",
|
||||||
|
"subType": "country",
|
||||||
|
|
||||||
|
"sourceId": "JSON",
|
||||||
|
"supportsNameFiltering": true,
|
||||||
|
|
||||||
|
"keyField": "alpha3Code",
|
||||||
|
"idField": "alpha3Code",
|
||||||
|
"nameField": "name",
|
||||||
|
|
||||||
|
"variables": {
|
||||||
|
"alpha2Code": "alpha2Code",
|
||||||
|
"numericCode": "numeric"
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"dataObjectDefinitionKey": "md-country",
|
||||||
|
"masterData": [
|
||||||
|
{
|
||||||
|
"name": "Australia default",
|
||||||
|
"alpha2Code": "AU",
|
||||||
|
"alpha3Code": "AUS",
|
||||||
|
"numeric": 36,
|
||||||
|
"translations": {
|
||||||
|
"de": {
|
||||||
|
"name": "Australien de"
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"name": "Australia en"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bermuda default",
|
||||||
|
"alpha2Code": "BM",
|
||||||
|
"alpha3Code": "BMU",
|
||||||
|
"numeric": 60,
|
||||||
|
"translations": {
|
||||||
|
"de": {
|
||||||
|
"name": "Bermuda de"
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"name": "Bermuda en"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Haiti default",
|
||||||
|
"alpha2Code": "HT",
|
||||||
|
"alpha3Code": "HTI",
|
||||||
|
"numeric": 332,
|
||||||
|
"translations": {
|
||||||
|
"de": {
|
||||||
|
"name": "Haiti de"
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"name": "Haiti en"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package com.flowable.local.work;
|
||||||
|
|
||||||
|
import org.flowable.spring.impl.test.FlowableSpringExtension;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because executing a Spring Boot Test will actually start Flowable and therefore additional
|
||||||
|
* infrastructure like elasticsearch is needed to run this test it is not a Unit Test but an
|
||||||
|
* Integration Test and should therefore not executed by maven surefire. Therefore the ending IT
|
||||||
|
* (for IntegrationTest) instead of Test.
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
@ExtendWith(FlowableSpringExtension.class)
|
||||||
|
//@Deployment(resources = {"my-process.bmpn20.xml"})
|
||||||
|
class FlowableLocalWorkApplicationIT {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void canStartApplication() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package com.flowable.local.work;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
class FlowableLocalWorkApplicationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void canStartContext() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package com.flowable.local.work.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class TestConfiguration {
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.flowable.local.work.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class EmailDto {
|
||||||
|
private String subject;
|
||||||
|
private List<String> receiverList;
|
||||||
|
private String content;
|
||||||
|
private Object contentRaw;
|
||||||
|
|
||||||
|
public EmailDto(String subject, List<String> receiverList, String content, Object contentRaw) {
|
||||||
|
this.subject = subject;
|
||||||
|
this.receiverList = receiverList;
|
||||||
|
this.content = content;
|
||||||
|
this.contentRaw = contentRaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubject() {
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubject(String subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getReceiverList() {
|
||||||
|
return receiverList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReceiverList(List<String> receiverList) {
|
||||||
|
this.receiverList = receiverList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getContentRaw() {
|
||||||
|
return contentRaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentRaw(Object contentRaw) {
|
||||||
|
this.contentRaw = contentRaw;
|
||||||
|
}
|
||||||
|
}
|
||||||
+335
@@ -0,0 +1,335 @@
|
|||||||
|
package com.flowable.local.work.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.*;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class FlowableExcelMapper {
|
||||||
|
|
||||||
|
protected static ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
protected static JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||||
|
protected static FlowableExcelParser flowableExcelParser = new FlowableExcelParser();
|
||||||
|
|
||||||
|
public FlowableExcelMapper() {
|
||||||
|
// Enable ObjectMapper for handling Instant as string
|
||||||
|
objectMapper.registerModule(javaTimeModule);
|
||||||
|
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// excelPath defines the location of the Excel file
|
||||||
|
// FlowableExcelParser is used to create an ArrayList from the Excel file
|
||||||
|
// The 1st row of every sheet contains the path of the variables to be created in the JsonNode
|
||||||
|
// Delimiter is "." for an ObjectNode and "*" for an ArrayNode
|
||||||
|
// The 2nd row contains tht type of the variable. Supported types are:
|
||||||
|
// - String
|
||||||
|
// - Boolean
|
||||||
|
// - Integer
|
||||||
|
// - Double
|
||||||
|
// - Date
|
||||||
|
// All sheets are added to an ArrayNode
|
||||||
|
// All rows are added to an ArrayNode in the sheets ArrayNode
|
||||||
|
// All cell values are added to an ObjectNode in the rows ArrayNode
|
||||||
|
public ArrayNode excelBookResourceToJsonNode(String resourcePath) {
|
||||||
|
ArrayList<ArrayList<ArrayList<String>>> book = flowableExcelParser.parseExcelBookFromResource(resourcePath);
|
||||||
|
return bookToJsonNode(book);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ArrayNode bookToJsonNode(ArrayList<ArrayList<ArrayList<String>>> book) {
|
||||||
|
ArrayNode bookNode = objectMapper.createArrayNode();
|
||||||
|
for (ArrayList<ArrayList<String>> sheet : book) {
|
||||||
|
bookNode.add(sheetToJsonNode(sheet));
|
||||||
|
}
|
||||||
|
return bookNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ArrayNode sheetToJsonNode(ArrayList<ArrayList<String>> rows) {
|
||||||
|
ArrayList<String> paths = new ArrayList<>();
|
||||||
|
ArrayList<String> types = new ArrayList<>();
|
||||||
|
ArrayNode arrayNode = objectMapper.createArrayNode();
|
||||||
|
for (int rowNum = 0; rowNum < rows.size(); rowNum++) {
|
||||||
|
if (rowNum == 0) {
|
||||||
|
// The 1st row contains the paths
|
||||||
|
paths.addAll(rows.get(rowNum));
|
||||||
|
} else if (rowNum == 1) {
|
||||||
|
// The 2nd row contains the types of the variables
|
||||||
|
types.addAll(rows.get(rowNum));
|
||||||
|
} else {
|
||||||
|
// All other rows contain the values of the variables
|
||||||
|
arrayNode.add(rowToJsonNode(types, paths, rows.get(rowNum), null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arrayNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JsonNode rowToJsonNode(ArrayList<String> types, ArrayList<String> paths, ArrayList<String> row, JsonNode jsonNode) {
|
||||||
|
for (int cellNum = 0; cellNum < row.size(); cellNum++) {
|
||||||
|
String cellValue = row.get(cellNum);
|
||||||
|
// Empty cells are not added to node
|
||||||
|
if (cellValue != null && !cellValue.isEmpty()) {
|
||||||
|
String type = types.get(cellNum);
|
||||||
|
Object content;
|
||||||
|
switch (cellValue) {
|
||||||
|
case "__NULL": content = null; break;
|
||||||
|
case "__N_A": content = "__N_A"; break;
|
||||||
|
default:
|
||||||
|
switch (type) {
|
||||||
|
case "String":
|
||||||
|
switch (cellValue) {
|
||||||
|
case "__EMPTY": content = ""; break;
|
||||||
|
case "__BLANK": content = " "; break;
|
||||||
|
default: content = cellValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Boolean": content = Boolean.parseBoolean(cellValue); break;
|
||||||
|
case "Integer": content = Integer.parseInt(cellValue); break;
|
||||||
|
case "Double": content = Double.parseDouble(cellValue); break;
|
||||||
|
case "Long": content = Long.parseLong(cellValue); break;
|
||||||
|
case "Date":
|
||||||
|
// Excel considers dates as local dates, but we want them as UTC
|
||||||
|
Instant instant;
|
||||||
|
try {
|
||||||
|
LocalDateTime localDateTime = LocalDateTime.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
LocalDate localDate = LocalDate.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||||
|
instant = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
|
||||||
|
}
|
||||||
|
content = instant.plusSeconds(ZonedDateTime.now().getOffset().getTotalSeconds());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Variable type not supported: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonNode = addNode(jsonNode, paths.get(cellNum), content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsonNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JsonNode addNode(JsonNode parent, String path, Object value) {
|
||||||
|
if (parent == null) {
|
||||||
|
parent = objectMapper.createObjectNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
int delimiterPosition = indexOfFirstDelimiter(path, ".*");
|
||||||
|
if (delimiterPosition <= 0) {
|
||||||
|
if (parent instanceof ObjectNode) {
|
||||||
|
// Key of a value in an ObjectNode
|
||||||
|
((ObjectNode) parent).set(path, objectMapper.valueToTree(value));
|
||||||
|
} else {
|
||||||
|
// Index of a value in an ArrayNode
|
||||||
|
int index = Integer.parseInt(path);
|
||||||
|
while (parent.size() <= index) {
|
||||||
|
// Create empty entries to parent ArrayNode
|
||||||
|
((ArrayNode) parent).add(objectMapper.createObjectNode());
|
||||||
|
}
|
||||||
|
((ArrayNode) parent).set(index, objectMapper.valueToTree(value));
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = path.substring(0, delimiterPosition);
|
||||||
|
String newPath = path.substring(delimiterPosition + 1);
|
||||||
|
String delimiter = path.substring(delimiterPosition, delimiterPosition + 1);
|
||||||
|
JsonNode child = null;
|
||||||
|
if (delimiter.equals(".")) {
|
||||||
|
child = objectMapper.createObjectNode();
|
||||||
|
} else if (delimiter.equals("*")) {
|
||||||
|
child = objectMapper.createArrayNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode node;
|
||||||
|
if (parent instanceof ObjectNode) {
|
||||||
|
// Key of a JsonNode in an ObjectNode
|
||||||
|
node = parent.get(key);
|
||||||
|
if (node != null) {
|
||||||
|
// Take the existing ObjectNode as child
|
||||||
|
child = node;
|
||||||
|
}
|
||||||
|
((ObjectNode) parent).set(key, addNode(child, newPath, value));
|
||||||
|
} else {
|
||||||
|
// Index of a JsonNode in an ArrayNode
|
||||||
|
int index = Integer.parseInt(key);
|
||||||
|
node = parent.get(index);
|
||||||
|
if (node != null) {
|
||||||
|
// Take the existing ArrayNode as child
|
||||||
|
child = node;
|
||||||
|
}
|
||||||
|
while (parent.size() <= index) {
|
||||||
|
// Create empty entries to parent ArrayNode
|
||||||
|
((ArrayNode) parent).add(objectMapper.createObjectNode());
|
||||||
|
}
|
||||||
|
((ArrayNode) parent).set(index, addNode(child, newPath, value));
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int indexOfFirstDelimiter(String str, String delimiters) {
|
||||||
|
for (int i = 0; i < str.length(); i++) {
|
||||||
|
for (char c : delimiters.toCharArray()) {
|
||||||
|
if (str.charAt(i) == c) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<ArrayList<Object>> excelBookResourceToObj(String resourcePath) {
|
||||||
|
ArrayList<ArrayList<ArrayList<String>>> book = flowableExcelParser.parseExcelBookFromResource(resourcePath);
|
||||||
|
return bookToObj(book);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ArrayList<ArrayList<Object>> bookToObj(ArrayList<ArrayList<ArrayList<String>>> book) {
|
||||||
|
ArrayList<ArrayList<Object>> bookNode = new ArrayList<>();
|
||||||
|
for (ArrayList<ArrayList<String>> sheet : book) {
|
||||||
|
bookNode.add(sheetToObj(sheet));
|
||||||
|
}
|
||||||
|
return bookNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ArrayList<Object> sheetToObj(ArrayList<ArrayList<String>> rows) {
|
||||||
|
ArrayList<String> paths = new ArrayList<>();
|
||||||
|
ArrayList<String> types = new ArrayList<>();
|
||||||
|
ArrayList<Object> arrayNode = new ArrayList<>();
|
||||||
|
for (int rowNum = 0; rowNum < rows.size(); rowNum++) {
|
||||||
|
if (rowNum == 0) {
|
||||||
|
// The 1st row contains the paths
|
||||||
|
paths.addAll(rows.get(rowNum));
|
||||||
|
} else if (rowNum == 1) {
|
||||||
|
// The 2nd row contains the types of the variables
|
||||||
|
types.addAll(rows.get(rowNum));
|
||||||
|
} else {
|
||||||
|
// All other rows contain the values of the variables
|
||||||
|
arrayNode.add(rowToObj(types, paths, rows.get(rowNum), null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arrayNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object rowToObj(ArrayList<String> types, ArrayList<String> paths, ArrayList<String> row, Object node) {
|
||||||
|
for (int cellNum = 0; cellNum < row.size(); cellNum++) {
|
||||||
|
String cellValue = row.get(cellNum);
|
||||||
|
// Empty cells are not added to node
|
||||||
|
if (cellValue != null && !cellValue.isEmpty()) {
|
||||||
|
String type = types.get(cellNum);
|
||||||
|
Object content;
|
||||||
|
switch (cellValue) {
|
||||||
|
case "__NULL": content = null; break;
|
||||||
|
case "__N_A": content = "__N_A"; break;
|
||||||
|
default:
|
||||||
|
switch (type) {
|
||||||
|
case "String":
|
||||||
|
switch (cellValue) {
|
||||||
|
case "__EMPTY": content = ""; break;
|
||||||
|
case "__BLANK": content = " "; break;
|
||||||
|
default: content = cellValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Boolean": content = Boolean.parseBoolean(cellValue); break;
|
||||||
|
case "Integer": content = Integer.parseInt(cellValue); break;
|
||||||
|
case "Double": content = Double.parseDouble(cellValue); break;
|
||||||
|
case "Long": content = Long.parseLong(cellValue); break;
|
||||||
|
case "Date":
|
||||||
|
// Excel considers dates as local dates, but we want them as UTC
|
||||||
|
Instant instant;
|
||||||
|
try {
|
||||||
|
LocalDateTime localDateTime = LocalDateTime.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
LocalDate localDate = LocalDate.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||||
|
instant = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
|
||||||
|
}
|
||||||
|
content = instant.plusSeconds(ZonedDateTime.now().getOffset().getTotalSeconds());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Variable type not supported: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node = addObj(node, paths.get(cellNum), content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected Object addObj(Object parent, String path, Object value) {
|
||||||
|
if (parent == null) {
|
||||||
|
parent = new LinkedHashMap<String, Object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
int delimiterPosition = indexOfFirstDelimiter(path, ".*");
|
||||||
|
if (delimiterPosition <= 0) {
|
||||||
|
if (parent instanceof LinkedHashMap) {
|
||||||
|
// Key of a value in an ObjectNode
|
||||||
|
((Map<String, Object>) parent).put(path, value);
|
||||||
|
} else if (parent instanceof ArrayList) {
|
||||||
|
// Index of a value in an ArrayNode
|
||||||
|
int index = Integer.parseInt(path);
|
||||||
|
while (((ArrayList<Object>) parent).size() <= index) {
|
||||||
|
// Create empty entries to parent ArrayNode
|
||||||
|
((ArrayList<Object>) parent).add(new LinkedHashMap<String, Object> ());
|
||||||
|
}
|
||||||
|
((ArrayList<Object>) parent).set(index, value);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Parent type not supported: " + parent.getClass().getName());
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = path.substring(0, delimiterPosition);
|
||||||
|
String newPath = path.substring(delimiterPosition + 1);
|
||||||
|
String delimiter = path.substring(delimiterPosition, delimiterPosition + 1);
|
||||||
|
Object child = null;
|
||||||
|
if (delimiter.equals(".")) {
|
||||||
|
child = new LinkedHashMap<String, Object>();
|
||||||
|
} else if (delimiter.equals("*")) {
|
||||||
|
child = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object node;
|
||||||
|
if (parent instanceof LinkedHashMap) {
|
||||||
|
// Key of a JsonNode in an ObjectNode
|
||||||
|
node = ((Map<String, Object>) parent).get(key);
|
||||||
|
if (node != null) {
|
||||||
|
// Take the existing ObjectNode as child
|
||||||
|
child = node;
|
||||||
|
}
|
||||||
|
((Map<String, Object>) parent).put(key, addObj(child, newPath, value));
|
||||||
|
} else if (parent instanceof ArrayList) {
|
||||||
|
// Index of a JsonNode in an ArrayNode
|
||||||
|
int index = Integer.parseInt(key);
|
||||||
|
if (((ArrayList<Object>) parent).size() > index) {
|
||||||
|
node = ((ArrayList<Object>) parent).get(index);
|
||||||
|
} else {
|
||||||
|
node = null;
|
||||||
|
}
|
||||||
|
if (node != null) {
|
||||||
|
// Take the existing ArrayNode as child
|
||||||
|
child = node;
|
||||||
|
}
|
||||||
|
while (((ArrayList<Object>) parent).size() <= index) {
|
||||||
|
// Create empty entries to parent ArrayNode
|
||||||
|
((ArrayList<Object>) parent).add(new LinkedHashMap<String, Object> ());
|
||||||
|
}
|
||||||
|
((ArrayList<Object>) parent).set(index, addObj(child, newPath, value));
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Parent type not supported: " + parent.getClass().getName());
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
package com.flowable.local.work.model;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.usermodel.*;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class FlowableExcelParser {
|
||||||
|
|
||||||
|
// excelPath defines the location of the Excel file
|
||||||
|
// Return all cells in all sheets with the following restrictions:
|
||||||
|
// - Max column count per sheet is defined in the first row
|
||||||
|
// - Max column count starts with the 1st cell and ends with the 1st empty cell
|
||||||
|
// - Parsing the sheet stops after the 1st empty row
|
||||||
|
// All sheets are added to an ArrayList
|
||||||
|
// All rows are added to an ArrayList in the sheets ArrayList
|
||||||
|
// All cell values are added to an ArrayList in the rows ArrayList
|
||||||
|
// Formulas in cells are evaluated
|
||||||
|
// All values are returned as String
|
||||||
|
public ArrayList<ArrayList<ArrayList<String>>> parseExcelBookFromResource(String excelResourcePath) {
|
||||||
|
try (FileInputStream fileInputStream = new FileInputStream(getResourcePath(excelResourcePath))) {
|
||||||
|
Workbook workBook = new XSSFWorkbook(fileInputStream);
|
||||||
|
return parseBook(workBook);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getResourcePath(String resourcePath) {
|
||||||
|
ClassLoader classLoader = FlowableExcelParser.class.getClassLoader();
|
||||||
|
URL resourceUrl = classLoader.getResource(resourcePath);
|
||||||
|
if (resourceUrl == null) {
|
||||||
|
throw new RuntimeException("Resource not found: " + resourcePath);
|
||||||
|
}
|
||||||
|
return resourceUrl.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<ArrayList<ArrayList<String>>> parseBook(Workbook workBook ) {
|
||||||
|
ArrayList<ArrayList<ArrayList<String>>> book = new ArrayList<>();
|
||||||
|
FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator();
|
||||||
|
for (Sheet workSheet : workBook) {
|
||||||
|
book.add(parseSheet(workSheet, formulaEvaluator));
|
||||||
|
}
|
||||||
|
return book;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<ArrayList<String>> parseSheet(Sheet workSheet, FormulaEvaluator formulaEvaluator) {
|
||||||
|
ArrayList<ArrayList<String>> sheet = new ArrayList<>();
|
||||||
|
// Count columns of first row
|
||||||
|
int maxCols = getMaxCols(workSheet.getRow(0));
|
||||||
|
DataFormatter dataFormatter = new DataFormatter();
|
||||||
|
int firstRow = workSheet.getFirstRowNum();
|
||||||
|
int lastRow = workSheet.getLastRowNum();
|
||||||
|
// For (Row workRow : workSheet) sometimes ignores empty rows, then the first empty row as an exit criterion wouldn't work
|
||||||
|
for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {
|
||||||
|
ArrayList<String> row = parseRow(workSheet.getRow(rowIndex), maxCols, formulaEvaluator, dataFormatter);
|
||||||
|
if (row == null) {
|
||||||
|
// Take all rows until the first empty row
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
sheet.add(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<String> parseRow(Row workRow, int maxCols, FormulaEvaluator formulaEvaluator, DataFormatter dataFormatter) {
|
||||||
|
if (workRow == null) {
|
||||||
|
// Row is empty
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ArrayList<String> row = new ArrayList<>();
|
||||||
|
boolean allCellsNull = true;
|
||||||
|
for (int colIndex = 0; colIndex < maxCols; colIndex++) {
|
||||||
|
Cell workCell = workRow.getCell(colIndex);
|
||||||
|
formulaEvaluator.evaluate(workCell);
|
||||||
|
String content = dataFormatter.formatCellValue(workCell, formulaEvaluator);
|
||||||
|
if (content != null && !content.isEmpty()) {
|
||||||
|
allCellsNull = false;
|
||||||
|
}
|
||||||
|
row.add(content);
|
||||||
|
}
|
||||||
|
if (allCellsNull) {
|
||||||
|
// Row is empty
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMaxCols(Row workRow) {
|
||||||
|
int maxCol = workRow.getLastCellNum();
|
||||||
|
for (int colIndex = 0; colIndex < maxCol; colIndex++) {
|
||||||
|
Cell workCell = workRow.getCell(colIndex);
|
||||||
|
if (workCell == null || workCell.getCellType() == CellType.BLANK || workCell.getCellType() == CellType._NONE) {
|
||||||
|
// Count cells until the first empty cell
|
||||||
|
return colIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public ArrayList<LinkedHashMap<String, Object>> parseExcelFromResource(String resourcePath) {
|
||||||
|
try (FileInputStream fileInputStream = new FileInputStream(getResourcePath(resourcePath))) {
|
||||||
|
Workbook workBook = new XSSFWorkbook(fileInputStream);
|
||||||
|
FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator();
|
||||||
|
ArrayList<ArrayList<String>> sheet = parseSheet(workBook.getSheetAt(0), formulaEvaluator);
|
||||||
|
ArrayList<String> colHeaders = new ArrayList<>();
|
||||||
|
ArrayList<LinkedHashMap<String, Object>> rowList = new ArrayList<>();
|
||||||
|
for (int rowIndex = 0; rowIndex < sheet.size(); rowIndex++) {
|
||||||
|
ArrayList<String> row = sheet.get(rowIndex);
|
||||||
|
if (rowIndex == 0) {
|
||||||
|
// Header row
|
||||||
|
for (Object cell : row) {
|
||||||
|
colHeaders.add(String.valueOf(cell));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LinkedHashMap<String, Object> rowMap = new LinkedHashMap<>();
|
||||||
|
for (int colIndex = 0; colIndex < row.size(); colIndex++) {
|
||||||
|
rowMap.put(colHeaders.get(colIndex), row.get(colIndex));
|
||||||
|
}
|
||||||
|
rowList.add(rowMap);
|
||||||
|
}
|
||||||
|
return rowList;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
package com.flowable.local.work.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class FlowableJsonParser {
|
||||||
|
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private static final JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||||
|
|
||||||
|
public static final String BOOLEAN = "Boolean";
|
||||||
|
public static final String STRING = "String";
|
||||||
|
public static final String INTEGER = "Integer";
|
||||||
|
public static final String DOUBLE = "Double";
|
||||||
|
public static final String LONG = "Long";
|
||||||
|
public static final String INSTANT = "Instant";
|
||||||
|
public static final String JSON_OBJECT_NODE = "JsonObjectNode";
|
||||||
|
public static final String JSON_ARRAY_NODE = "JsonArrayNode";
|
||||||
|
public static final String MAP = "Map";
|
||||||
|
public static final String ARRAY_LIST = "ArrayList";
|
||||||
|
|
||||||
|
|
||||||
|
public FlowableJsonParser() {
|
||||||
|
objectMapper.registerModule(javaTimeModule);
|
||||||
|
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> parseMap(Object map) throws JsonProcessingException, ClassCastException {
|
||||||
|
return (Map<String, Object>) parseObject(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object parseObject(Object object) throws JsonProcessingException, ClassCastException {
|
||||||
|
if (object instanceof Map) {
|
||||||
|
Map<String, Object> resultMap = new LinkedHashMap<>(Map.of());
|
||||||
|
for (Map.Entry<String, Object> mapEntry : ((Map<String, Object>) object).entrySet()) {
|
||||||
|
String key = mapEntry.getKey();
|
||||||
|
resultMap.put(key, parseMapEntry(key, mapEntry.getValue()));
|
||||||
|
}
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
if (object instanceof ArrayList) {
|
||||||
|
ArrayList<Object> resultList = new ArrayList<>();
|
||||||
|
for (Object arrayEntry : (ArrayList<Object>) object) {
|
||||||
|
resultList.add(parseArrayEntry(arrayEntry));
|
||||||
|
}
|
||||||
|
return resultList;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object parseMapEntry(String key, Object value) throws JsonProcessingException {
|
||||||
|
if (value instanceof Map) {
|
||||||
|
Map<String, Object> valueMap = (Map<String, Object>) value;
|
||||||
|
if (valueMap.containsKey("__TYPE") && valueMap.containsKey("__VALUE") && valueMap.size() == 2) {
|
||||||
|
// if Map in Map, check if inner Map has explicit type (__TYPE and __VALUE)
|
||||||
|
return convertObjectExplicit(valueMap.get("__VALUE"), (String) valueMap.get("__TYPE"));
|
||||||
|
}
|
||||||
|
if (Arrays.asList("__IN", "__OUT").contains(key)) {
|
||||||
|
// convert __IN and __OUT maps as Map
|
||||||
|
return parseObject(value);
|
||||||
|
}
|
||||||
|
// convert Map to JsonObjectNode (default in models)
|
||||||
|
return convertObjectExplicit(value, JSON_OBJECT_NODE);
|
||||||
|
}
|
||||||
|
if (value instanceof ArrayList) {
|
||||||
|
// convert ArrayList to JsonArrayNode (default in models)
|
||||||
|
return convertObjectExplicit(value, JSON_ARRAY_NODE);
|
||||||
|
}
|
||||||
|
// take json supported type (Boolean, String, Integer, Double)
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object parseArrayEntry(Object value) throws JsonProcessingException {
|
||||||
|
if (value instanceof Map) {
|
||||||
|
// convert Map to JsonObjectNode (default in models)
|
||||||
|
return convertObjectExplicit(value, JSON_OBJECT_NODE);
|
||||||
|
}
|
||||||
|
if (value instanceof ArrayList) {
|
||||||
|
// convert ArrayList to JsonArrayNode (default in models)
|
||||||
|
return convertObjectExplicit(value, JSON_ARRAY_NODE);
|
||||||
|
}
|
||||||
|
// take json supported type (Boolean, String, Integer, Double)
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object convertObjectExplicit(Object value, String explicitType) throws JsonProcessingException {
|
||||||
|
switch (explicitType) {
|
||||||
|
case BOOLEAN: return Boolean.parseBoolean(value.toString());
|
||||||
|
case STRING: return value.toString();
|
||||||
|
case INTEGER: return Integer.parseInt(value.toString());
|
||||||
|
case DOUBLE: return Double.parseDouble(value.toString());
|
||||||
|
case LONG: return Long.parseLong(value.toString());
|
||||||
|
case INSTANT: return Instant.parse(value.toString());
|
||||||
|
case MAP: case ARRAY_LIST: return parseObject(value);
|
||||||
|
case JSON_OBJECT_NODE: case JSON_ARRAY_NODE: return objectMapper.convertValue(parseObject(value), JsonNode.class);
|
||||||
|
default: return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
package com.flowable.local.work.model;
|
||||||
|
|
||||||
|
import com.flowable.action.engine.test.ActionExtension;
|
||||||
|
import com.flowable.app.engine.test.FlowableAppExtension;
|
||||||
|
import com.flowable.dataobject.engine.test.DataObjectExtension;
|
||||||
|
import com.flowable.form.spring.impl.test.FlowableFormSpringExtension;
|
||||||
|
import com.flowable.idm.engine.test.PlatformIdmExtension;
|
||||||
|
import com.flowable.platform.tenant.test.TenantSetupExtension;
|
||||||
|
import com.flowable.policy.engine.test.PolicyExtension;
|
||||||
|
import com.flowable.serviceregistry.engine.test.ServiceRegistryExtension;
|
||||||
|
import com.flowable.template.engine.test.TemplateExtension;
|
||||||
|
import org.flowable.cmmn.spring.impl.test.FlowableCmmnSpringExtension;
|
||||||
|
import org.flowable.dmn.spring.impl.test.FlowableDmnSpringExtension;
|
||||||
|
import org.flowable.spring.impl.test.FlowableSpringExtension;
|
||||||
|
import org.junit.jupiter.api.TestInstance;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ExtendWith(ActionExtension.class)
|
||||||
|
@ExtendWith(DataObjectExtension.class)
|
||||||
|
//@ExtendWith(EngageExtension.class)
|
||||||
|
@ExtendWith(FlowableAppExtension.class)
|
||||||
|
@ExtendWith(FlowableSpringExtension.class)
|
||||||
|
@ExtendWith(FlowableCmmnSpringExtension.class)
|
||||||
|
@ExtendWith(FlowableFormSpringExtension.class)
|
||||||
|
@ExtendWith(FlowableDmnSpringExtension.class)
|
||||||
|
@ExtendWith(PlatformIdmExtension.class)
|
||||||
|
@ExtendWith(PolicyExtension.class)
|
||||||
|
@ExtendWith(ServiceRegistryExtension.class)
|
||||||
|
@ExtendWith(TemplateExtension.class)
|
||||||
|
@ExtendWith(TenantSetupExtension.class)
|
||||||
|
@ExtendWith(TestMailServerExtension.class)
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
@Transactional
|
||||||
|
@SpringBootTest
|
||||||
|
public @interface FlowableModelTest {
|
||||||
|
/*
|
||||||
|
@AliasFor(annotation = SpringBootTest.class, attribute = "webEnvironment")
|
||||||
|
SpringBootTest.WebEnvironment webEnvironment() default SpringBootTest.WebEnvironment.MOCK;
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
+655
@@ -0,0 +1,655 @@
|
|||||||
|
package com.flowable.local.work.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.*;
|
||||||
|
import com.flowable.audit.api.AuditService;
|
||||||
|
import com.flowable.audit.api.runtime.AuditInstance;
|
||||||
|
import com.flowable.core.spring.security.SecurityUtils;
|
||||||
|
import com.flowable.local.work.service.JsonUtils;
|
||||||
|
import com.flowable.platform.service.task.CompleteFormRepresentation;
|
||||||
|
import com.flowable.platform.service.task.PlatformTaskService;
|
||||||
|
import jakarta.mail.Address;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.flowable.bpmn.model.*;
|
||||||
|
import org.flowable.bpmn.model.Process;
|
||||||
|
import org.flowable.cmmn.api.runtime.CaseInstance;
|
||||||
|
import org.flowable.cmmn.engine.CmmnEngine;
|
||||||
|
import org.flowable.common.engine.api.identity.AuthenticationContext;
|
||||||
|
import org.flowable.common.engine.impl.identity.Authentication;
|
||||||
|
import org.flowable.engine.ManagementService;
|
||||||
|
import org.flowable.engine.ProcessEngine;
|
||||||
|
import org.flowable.engine.TaskService;
|
||||||
|
import org.flowable.engine.history.HistoricProcessInstance;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.flowable.job.service.impl.persistence.entity.TimerJobEntity;
|
||||||
|
import org.flowable.spring.security.SpringSecurityAuthenticationContext;
|
||||||
|
import org.flowable.task.api.Task;
|
||||||
|
import org.flowable.variable.api.history.HistoricVariableInstance;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.platform.commons.util.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StreamUtils;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class FlowableModelTestUtils {
|
||||||
|
|
||||||
|
protected final ProcessEngine processEngine;
|
||||||
|
protected final CmmnEngine cmmnEngine;
|
||||||
|
protected final TaskService taskService;
|
||||||
|
protected final PlatformTaskService platformTaskService;
|
||||||
|
protected final ManagementService managementService;
|
||||||
|
protected final JsonUtils jsonUtils;
|
||||||
|
protected final FlowableExcelMapper flowableExcelMapper;
|
||||||
|
protected static final Logger logger = LoggerFactory.getLogger(FlowableModelTestUtils.class);
|
||||||
|
protected final TestMailServer testMailServer;
|
||||||
|
protected final AuditService auditService;
|
||||||
|
|
||||||
|
|
||||||
|
public static String TENANT_ID = null;
|
||||||
|
public static String ROOT_PROCESS_ID = "ROOT_PROCESS_ID";
|
||||||
|
public static String TEST_PROCESS_ID = "TEST_PROCESS_ID";
|
||||||
|
|
||||||
|
|
||||||
|
public FlowableModelTestUtils(ProcessEngine processEngine,
|
||||||
|
CmmnEngine cmmnEngine,
|
||||||
|
TaskService taskService,
|
||||||
|
PlatformTaskService platformTaskService,
|
||||||
|
ManagementService managementService,
|
||||||
|
JsonUtils jsonUtils,
|
||||||
|
FlowableExcelMapper flowableExcelMapper,
|
||||||
|
TestMailServer testMailServer,
|
||||||
|
AuditService auditService) {
|
||||||
|
this.processEngine = processEngine;
|
||||||
|
this.cmmnEngine = cmmnEngine;
|
||||||
|
this.taskService = taskService;
|
||||||
|
this.platformTaskService = platformTaskService;
|
||||||
|
this.managementService = managementService;
|
||||||
|
this.jsonUtils = jsonUtils;
|
||||||
|
this.flowableExcelMapper = flowableExcelMapper;
|
||||||
|
this.testMailServer = testMailServer;
|
||||||
|
this.auditService = auditService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkAndAndAssertAuditRecord(Map<String, Object> map, int auditNumber, String hint, String message, String category, String type) {
|
||||||
|
if (map != null) {
|
||||||
|
Object check = map.get("audit");
|
||||||
|
if (check instanceof Integer && (Integer) check > 0) {
|
||||||
|
assertAuditRecord(auditNumber, hint, message, category, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertAuditRecord(int auditNumber, String hint, String message, String category, String type) {
|
||||||
|
List<AuditInstance> auditTrail = getAuditTrail();
|
||||||
|
int auditTrailSize = auditTrail.size();
|
||||||
|
Assertions.assertThat(auditTrailSize).as(hint + ": invalid auditNumber " + auditTrailSize).isGreaterThanOrEqualTo(auditNumber);
|
||||||
|
if (auditTrailSize == 0) return;
|
||||||
|
|
||||||
|
AuditInstance auditInstance = auditTrail.get(auditNumber - 1);
|
||||||
|
Assertions.assertThat(auditInstance.getPayload().get("message")).as(hint).isEqualTo(message);
|
||||||
|
Assertions.assertThat(auditInstance.getPayload().get("category")).as(hint).isEqualTo(category);
|
||||||
|
Assertions.assertThat(auditInstance.getType()).as(hint).isEqualTo(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkAndAssertEmail(Map<String, Object> map, int emailNumber, String hint, String subject, String receivers) {
|
||||||
|
if (map != null) {
|
||||||
|
Object check = map.get("email");
|
||||||
|
if (check instanceof Integer && (Integer) check > 0) {
|
||||||
|
assertEmail(emailNumber, hint, subject, receivers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertEmail(int emailNumber, String hint, String subject, String receivers) {
|
||||||
|
List<EmailDto> emails = getMailList();
|
||||||
|
int emailListSize = emails.size();
|
||||||
|
Assertions.assertThat(emailListSize).as(hint + ": invalid email number " + emailListSize).isGreaterThanOrEqualTo(emailNumber);
|
||||||
|
if (emailNumber == 0) return;
|
||||||
|
|
||||||
|
EmailDto email = emails.get(emailNumber - 1);
|
||||||
|
String emailSubject = email.getSubject();
|
||||||
|
Assertions.assertThat(emailSubject).as(hint + ": invalid subject " + emailSubject).endsWith(subject);
|
||||||
|
|
||||||
|
List<Pair<String, Boolean>> receiverCheckList = new ArrayList<>();
|
||||||
|
for (String receiver : receivers.split("[,\\s]+")) {
|
||||||
|
receiverCheckList.add(Pair.of(receiver, false));
|
||||||
|
}
|
||||||
|
List<String> emailReceiverList = email.getReceiverList();
|
||||||
|
Assertions.assertThat(emailReceiverList.size()).as(hint + ": invalid number of receivers " + emailReceiverList).isEqualTo(receiverCheckList.size());
|
||||||
|
|
||||||
|
for (String emailReceiver : emailReceiverList) {
|
||||||
|
for (int i = 0; i < receiverCheckList.size(); i++) {
|
||||||
|
Pair<String, Boolean> checkReceiver = receiverCheckList.get(i);
|
||||||
|
if (checkReceiver.getLeft().equals(emailReceiver) && !checkReceiver.getRight()) {
|
||||||
|
receiverCheckList.set(i, Pair.of(emailReceiver, true));
|
||||||
|
break;
|
||||||
|
} else if (i == receiverCheckList.size() - 1) {
|
||||||
|
Assertions.fail(hint + ": invalid email receiver " + emailReceiverList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getHistoryCasePayload(String caseInstanceId) {
|
||||||
|
List<HistoricVariableInstance> historicVariableInstanceList = cmmnEngine.getCmmnHistoryService()
|
||||||
|
.createHistoricVariableInstanceQuery()
|
||||||
|
.caseInstanceId(caseInstanceId)
|
||||||
|
.list();
|
||||||
|
return convertHistVariableListToMap(historicVariableInstanceList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getRuntimeCasePayload(String caseId) {
|
||||||
|
return cmmnEngine.getCmmnRuntimeService().getVariables(caseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CaseInstance startCaseInstance(String key, Map<String, Object> variables) {
|
||||||
|
return cmmnEngine.getCmmnRuntimeService()
|
||||||
|
.createCaseInstanceBuilder()
|
||||||
|
.caseDefinitionKey(key)
|
||||||
|
.tenantId(TENANT_ID) // Must not be "default"
|
||||||
|
.variables(variables)
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProcessInstance startProcessInstance(String key, Map<String, Object> variables) {
|
||||||
|
return processEngine.getRuntimeService()
|
||||||
|
.createProcessInstanceBuilder()
|
||||||
|
.processDefinitionKey(key)
|
||||||
|
.tenantId(TENANT_ID) // Must not be "default"
|
||||||
|
.variables(variables)
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task getOpenTask(String taskKey) {
|
||||||
|
Task task = taskService.createTaskQuery().taskDefinitionKey(taskKey).singleResult();
|
||||||
|
Assertions.assertThat(task).as("No open task with key {} found", taskKey).isNotNull();
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task getOpenTaskByName(String taskName) {
|
||||||
|
Task task = taskService.createTaskQuery().taskName(taskName).singleResult();
|
||||||
|
Assertions.assertThat(task).as("No open task with name {} found", taskName).isNotNull();
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void claimOpenTask(String taskKey, String userId) {
|
||||||
|
setTestAuthenticatedUser(userId, TENANT_ID);
|
||||||
|
taskService.claim(getOpenTask(taskKey).getId(), userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AuditInstance> getAuditTrail() {
|
||||||
|
return auditService.createAuditInstanceQuery()
|
||||||
|
//.tenantId(TENANT_ID) TODO: Must not be null, should work with default tenant
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EmailDto> getMailList() {
|
||||||
|
return Arrays.stream(testMailServer.getMessages()).map(mimeMessage -> {
|
||||||
|
try {
|
||||||
|
return new EmailDto(mimeMessage.getSubject(), Arrays.stream(mimeMessage.getAllRecipients())
|
||||||
|
.map(Address::toString).collect(Collectors.toList()), mimeMessage.getContent().toString(), mimeMessage.getContent());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSubset(JsonNode root, JsonNode test) {
|
||||||
|
// If test is null, it is always a subset of root
|
||||||
|
if (test == null || test.isNull()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If test is a value node, compare values
|
||||||
|
if (test.isValueNode()) {
|
||||||
|
if (test.isTextual() && "__N_A".equals(test.asText())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (root == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return root.isValueNode() && root.asText().equals(test.asText());
|
||||||
|
}
|
||||||
|
// If test is an array node, check if all elements of test exist in root
|
||||||
|
if(test.isArray()){
|
||||||
|
for (int i = 0; i < test.size(); i++) {
|
||||||
|
JsonNode testElement = test.get(i);
|
||||||
|
JsonNode rootElement = null;
|
||||||
|
if (root != null) {
|
||||||
|
rootElement = root.get(i);
|
||||||
|
}
|
||||||
|
if (testElement.isTextual() && "__N_A".equals(testElement.asText())) {
|
||||||
|
if (rootElement != null && !rootElement.isNull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isSubset(rootElement, testElement)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If test is an object node, check if all fields in test exist in root
|
||||||
|
if (test.isObject()){
|
||||||
|
for (Iterator<String> iterator = test.fieldNames(); iterator.hasNext(); ) {
|
||||||
|
String fieldName = iterator.next();
|
||||||
|
JsonNode testValue = test.get(fieldName);
|
||||||
|
JsonNode rootValue = null;
|
||||||
|
if (root != null) {
|
||||||
|
rootValue = root.get(fieldName);
|
||||||
|
}
|
||||||
|
if (testValue.isTextual() && "__N_A".equals(testValue.asText())) {
|
||||||
|
if (root != null && root.has(fieldName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isSubset(rootValue, testValue)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If none of the above matches, return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<Arguments> getJsonArgumentsFromExcel(String path) {
|
||||||
|
ArrayList<Arguments> argumentList = new ArrayList<>();
|
||||||
|
JsonNode book = flowableExcelMapper.excelBookResourceToJsonNode(path);
|
||||||
|
for (JsonNode sheet : book) {
|
||||||
|
for (JsonNode row : sheet) {
|
||||||
|
argumentList.add(Arguments.of(path, row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return argumentList.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<Arguments> getObjArgumentsFromExcel(String path) {
|
||||||
|
ArrayList<Arguments> argumentList = new ArrayList<>();
|
||||||
|
ArrayList<ArrayList<Object>> book = flowableExcelMapper.excelBookResourceToObj(path);
|
||||||
|
for (ArrayList<Object> sheet : book) {
|
||||||
|
for (Object row : sheet) {
|
||||||
|
argumentList.add(Arguments.of(path, row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return argumentList.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectNode testJsonExcelRow(String path, JsonNode row) {
|
||||||
|
ObjectNode vars = jsonUtils.getEmptyObjectNode();
|
||||||
|
JsonNode rootParam = row.get("root");
|
||||||
|
if (rootParam != null) {
|
||||||
|
Iterator<Map.Entry<String, JsonNode>> rootVars = rootParam.fields();
|
||||||
|
while (rootVars.hasNext()) {
|
||||||
|
Map.Entry<String, JsonNode> rootVar = rootVars.next();
|
||||||
|
String key = rootVar.getKey();
|
||||||
|
JsonNode value = rootVar.getValue();
|
||||||
|
vars.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JsonNode inParam = row.get("in");
|
||||||
|
if (inParam != null) {
|
||||||
|
vars.set("__IN", inParam);
|
||||||
|
}
|
||||||
|
JsonNode outParam = row.get("out");
|
||||||
|
if (outParam != null) {
|
||||||
|
vars.set("__OUT", outParam);
|
||||||
|
}
|
||||||
|
JsonNode idParam = row.get("id");
|
||||||
|
JsonNode test = row.get("test");
|
||||||
|
int timerCount = 0;
|
||||||
|
JsonNode timerNode = row.get("timer");
|
||||||
|
if (timerNode != null) timerCount = timerNode.asInt();
|
||||||
|
Integer auditRecordCount = null;
|
||||||
|
JsonNode auditNode = row.get("audit");
|
||||||
|
if (auditNode != null) auditRecordCount = auditNode.asInt();
|
||||||
|
Integer emailCount = null;
|
||||||
|
JsonNode emailNode = row.get("email");
|
||||||
|
if (emailNode != null) emailCount = emailNode.asInt();
|
||||||
|
|
||||||
|
logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} ",
|
||||||
|
path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount);
|
||||||
|
ObjectNode processIds = createRootTestProcessInstance(idParam.asText(), jsonUtils.convertJsonNodeToMap(vars));
|
||||||
|
for (int i = 0; i < timerCount; i++) {
|
||||||
|
executeTimer(processIds.get(TEST_PROCESS_ID).asText());
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectNode result = jsonUtils.getEmptyObjectNode();
|
||||||
|
Map<String, Object> rootProcessPayload = getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
|
||||||
|
JsonNode root = jsonUtils.convertMapToJsonNode(rootProcessPayload);
|
||||||
|
if (root != null) {
|
||||||
|
Assertions.assertThat(isSubset(root, test))
|
||||||
|
.withFailMessage(System.lineSeparator() + "test :" + test + System.lineSeparator() + "root :" + root).isTrue();
|
||||||
|
result.set("root", root);
|
||||||
|
}
|
||||||
|
if (auditRecordCount != null) {
|
||||||
|
// List<AuditInstance> auditTrail = getAuditTrail(processIds.get(ROOT_PROCESS_ID).asText());
|
||||||
|
List<AuditInstance> auditTrail = getAuditTrail();
|
||||||
|
int auditTrailSize = auditTrail.size();
|
||||||
|
Assertions.assertThat(auditTrailSize).as("Invalid audit trail size: {}", auditTrailSize).isEqualTo(auditRecordCount);
|
||||||
|
result.set("auditTrail", jsonUtils.convertListToJsonNode(Collections.singletonList(auditTrail)));
|
||||||
|
}
|
||||||
|
if (emailCount != null) {
|
||||||
|
List<EmailDto> emailList = getMailList();
|
||||||
|
int emailListSize = emailList.size();
|
||||||
|
Assertions.assertThat(emailListSize).as("Invalid number of emails: {}", emailListSize).isEqualTo(emailCount);
|
||||||
|
result.set("emails", jsonUtils.convertListToJsonNode(Collections.singletonList(emailList)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> testObjExcelRow(String path, Map<String, Object> row) {
|
||||||
|
Map<String, Object> vars = new LinkedHashMap<>();
|
||||||
|
Map<String, Object> rootParam = (Map<String, Object>) row.get("root");
|
||||||
|
if (rootParam != null) {
|
||||||
|
for (Map.Entry<String, Object> entry : rootParam.entrySet()) {
|
||||||
|
vars.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, Object> inParam = (Map<String, Object>) row.get("in");
|
||||||
|
if (inParam != null) {
|
||||||
|
vars.put("__IN", inParam);
|
||||||
|
}
|
||||||
|
Map<String, Object> outParam = (Map<String, Object>) row.get("out");
|
||||||
|
if (outParam != null) {
|
||||||
|
vars.put("__OUT", outParam);
|
||||||
|
}
|
||||||
|
String idParam = (String) row.get("id");
|
||||||
|
Map<String, Object> test =(Map<String, Object>) row.get("test");
|
||||||
|
int timerCount = 0;
|
||||||
|
Object timerNode = row.get("timer");
|
||||||
|
if (timerNode != null) timerCount = (Integer) timerNode;
|
||||||
|
Integer auditRecordCount = null;
|
||||||
|
Object auditNode = row.get("audit");
|
||||||
|
if (auditNode != null) auditRecordCount = (Integer) auditNode;
|
||||||
|
Integer emailCount = null;
|
||||||
|
Object emailNode = row.get("email");
|
||||||
|
if (emailNode != null) emailCount = (Integer) emailNode;
|
||||||
|
|
||||||
|
logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} ",
|
||||||
|
path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount);
|
||||||
|
ObjectNode processIds = createRootTestProcessInstance(idParam, vars);
|
||||||
|
for (int i = 0; i < timerCount; i++) {
|
||||||
|
executeTimer(processIds.get(TEST_PROCESS_ID).asText());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
Map<String, Object> rootProcessPayload = getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
|
||||||
|
JsonNode root = jsonUtils.convertMapToJsonNode(rootProcessPayload);
|
||||||
|
if (root != null) {
|
||||||
|
Assertions.assertThat(isSubset(root, jsonUtils.convertMapToJsonNode(test)))
|
||||||
|
.withFailMessage(System.lineSeparator() + "test :" + test + System.lineSeparator() + "root :" + root).isTrue();
|
||||||
|
result.put("root", rootProcessPayload);
|
||||||
|
}
|
||||||
|
if (auditRecordCount != null) {
|
||||||
|
// List<AuditInstance> auditTrail = getAuditTrail(processIds.get(ROOT_PROCESS_ID).asText());
|
||||||
|
List<AuditInstance> auditTrail = getAuditTrail();
|
||||||
|
int auditTrailSize = auditTrail.size();
|
||||||
|
Assertions.assertThat(auditTrailSize).as("Invalid audit trail size: {}", auditTrailSize).isEqualTo(auditRecordCount);
|
||||||
|
result.put("audit", auditRecordCount);
|
||||||
|
}
|
||||||
|
if (emailCount != null) {
|
||||||
|
List<EmailDto> emailList = getMailList();
|
||||||
|
int emailListSize = emailList.size();
|
||||||
|
Assertions.assertThat(emailListSize).as("Invalid number of emails: {}", emailListSize).isEqualTo(emailCount);
|
||||||
|
result.put("email", emailCount);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportApp(String rootUrl, String appModelKey, String username, String password) {
|
||||||
|
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
|
||||||
|
|
||||||
|
RestTemplate restTemplate = restTemplateBuilder
|
||||||
|
.basicAuthentication(username, password)
|
||||||
|
.rootUri(rootUrl)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ResponseEntity<JsonNode> responseEntity = restTemplate.postForEntity(URI.create(rootUrl + "/app/authentication?j_username=" + username + "&j_password=" + password + "&spring_security_remember_me=true&submit=Login"), null, JsonNode.class);
|
||||||
|
|
||||||
|
String designCookieRaw = responseEntity.getHeaders().get("Set-Cookie").stream().collect(Collectors.joining(";"));
|
||||||
|
String flowableDesignRememberMeTokenValue = Arrays.stream(designCookieRaw.split(";")).filter(part -> part.contains("FLOWABLE_DESIGN_REMEMBER_ME")).findAny().get();
|
||||||
|
String csrfToken = Arrays.stream(designCookieRaw.split(";")).filter(part -> part.contains("FLOWABLE_DESIGN_CSRF_TOKEN")).findAny().get();
|
||||||
|
|
||||||
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
httpHeaders.set("Cookie", flowableDesignRememberMeTokenValue + ";" + csrfToken);
|
||||||
|
|
||||||
|
ArrayNode apps = (ArrayNode) restTemplate.exchange(URI.create(rootUrl + "/app/models?filter=apps&modelType=3&sort=modifiedDesc"), HttpMethod.GET, new HttpEntity<>(httpHeaders), JsonNode.class).getBody().path("data");
|
||||||
|
String appModelId = StreamSupport.stream(apps.spliterator(), false).filter(app -> app.path("key").asText().equals(appModelKey)).map( app -> app.path("id").asText()).findAny().get();
|
||||||
|
|
||||||
|
File file = new File(Paths.get("src/test/resources/test-auto-deploy-apps", appModelKey + ".zip").toString());
|
||||||
|
restTemplate.execute(
|
||||||
|
URI.create(rootUrl + "/app/app-definitions/" + appModelId + "/export-bar?includeChildReferences=true"),
|
||||||
|
HttpMethod.GET,
|
||||||
|
clientHttpRequest -> clientHttpRequest.getHeaders().set(
|
||||||
|
"Cookie", flowableDesignRememberMeTokenValue + ";" + csrfToken),
|
||||||
|
clientHttpResponse -> {
|
||||||
|
StreamUtils.copy(clientHttpResponse.getBody(), new FileOutputStream(file, false));
|
||||||
|
return file;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectNode emptyNode() {
|
||||||
|
return jsonUtils.getEmptyObjectNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> emptyMap() {
|
||||||
|
return new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectNode loadObjectNodeFromResources(String path) {
|
||||||
|
if (path.isEmpty()) return jsonUtils.getEmptyObjectNode();
|
||||||
|
else return jsonUtils.loadObjectNodeFromFile(Paths.get("src","test", "resources", path).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> loadMapFromResources(String path) {
|
||||||
|
return jsonUtils.convertJsonNodeToMap(loadObjectNodeFromResources(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeTimer(String processId) {
|
||||||
|
TimerJobEntity timerJobEntity = (TimerJobEntity) managementService.createTimerJobQuery().processInstanceId(processId).singleResult();
|
||||||
|
Assertions.assertThat(timerJobEntity).withFailMessage("No active timer").isNotNull();
|
||||||
|
managementService.moveTimerToExecutableJob(timerJobEntity.getId());
|
||||||
|
managementService.executeJob(timerJobEntity.getId());
|
||||||
|
}
|
||||||
|
public void setTestAuthenticatedUser(String userId, String tenantId) {
|
||||||
|
if (userId == null) {
|
||||||
|
this.resetTestAuthenticatedUser();
|
||||||
|
} else {
|
||||||
|
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
|
||||||
|
if (StringUtils.isNotBlank(tenantId)) {
|
||||||
|
grantedAuthorities.add(SecurityUtils.createTenantAuthority(tenantId));
|
||||||
|
}
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken(userId, "", grantedAuthorities));
|
||||||
|
AuthenticationContext authenticationContext = Authentication.getAuthenticationContext();
|
||||||
|
if (!(authenticationContext instanceof SpringSecurityAuthenticationContext)) {
|
||||||
|
Authentication.setAuthenticatedUserId(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTestAuthenticatedUser(String userId, String tenantId, String... groupKeys) {
|
||||||
|
if (userId == null) {
|
||||||
|
this.resetTestAuthenticatedUser();
|
||||||
|
} else {
|
||||||
|
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
|
||||||
|
if (StringUtils.isNotBlank(tenantId)) {
|
||||||
|
grantedAuthorities.add(SecurityUtils.createTenantAuthority(tenantId));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String groupKey : groupKeys) {
|
||||||
|
grantedAuthorities.add(SecurityUtils.createGroupAuthority(groupKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken(userId, "", grantedAuthorities));
|
||||||
|
AuthenticationContext authenticationContext = Authentication.getAuthenticationContext();
|
||||||
|
if (!(authenticationContext instanceof SpringSecurityAuthenticationContext)) {
|
||||||
|
Authentication.setAuthenticatedUserId(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetTestAuthenticatedUser() {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(null);
|
||||||
|
AuthenticationContext authenticationContext = Authentication.getAuthenticationContext();
|
||||||
|
if (!(authenticationContext instanceof SpringSecurityAuthenticationContext)) {
|
||||||
|
Authentication.setAuthenticatedUserId(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void completeTaskWithFlatVars(String taskId, Map<String, Object> variables, String outcome) {
|
||||||
|
Map<String, Object> taskVariables = platformTaskService.getTaskVariables(taskId);
|
||||||
|
Map<String, Object> taskVariablesFlat = jsonUtils.flatten(taskVariables);
|
||||||
|
taskVariablesFlat.putAll(variables);
|
||||||
|
Map<String, Object> completionVars = jsonUtils.unflatten(taskVariablesFlat);
|
||||||
|
|
||||||
|
completeTask(taskId, completionVars, outcome);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void completeTask(String taskId, Map<String, Object> completionVariables, String outcome) {
|
||||||
|
CompleteFormRepresentation form = new CompleteFormRepresentation();
|
||||||
|
for (String key : completionVariables.keySet()) {
|
||||||
|
form.setValues(key, completionVariables.get(key));
|
||||||
|
}
|
||||||
|
form.setOutcome(outcome);
|
||||||
|
platformTaskService.completeTaskForm(taskId, form);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void completeOpenTask(String taskKey, ObjectNode vars, String outcome) {
|
||||||
|
Task task = getOpenTask(taskKey);
|
||||||
|
Map<String, Object> flatVars = jsonUtils.convertJsonNodeToMap(vars);
|
||||||
|
completeTaskWithFlatVars(task.getId(), flatVars, outcome);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getHistProcessPayload(String processInstanceId) {
|
||||||
|
List<HistoricVariableInstance> historicVariableInstanceList = processEngine.getHistoryService()
|
||||||
|
.createHistoricVariableInstanceQuery()
|
||||||
|
.processInstanceId(processInstanceId)
|
||||||
|
.list();
|
||||||
|
return convertHistVariableListToMap(historicVariableInstanceList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<String, Object> convertHistVariableListToMap(List<HistoricVariableInstance> historicVariableInstanceList) {
|
||||||
|
HashMap<String, Object> variableMap = new HashMap<>();
|
||||||
|
for (HistoricVariableInstance historicVariableInstance : historicVariableInstanceList) {
|
||||||
|
variableMap.put(historicVariableInstance.getVariableName(), historicVariableInstance.getValue());
|
||||||
|
}
|
||||||
|
return variableMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectNode createRootTestProcessInstance(String testKey, Map<String, Object> variables) {
|
||||||
|
// Create root process with test process as call activity
|
||||||
|
String wrapperProcessKey = testKey + "_T";
|
||||||
|
BpmnModel bpmnModel = createWrapperTestProcessModel(testKey, (ObjectNode) jsonUtils.convertMapToJsonNode(variables));
|
||||||
|
processEngine.getProcessEngineConfiguration()
|
||||||
|
.getRepositoryService()
|
||||||
|
.createDeployment()
|
||||||
|
.addBpmnModel(wrapperProcessKey + ".bpmn20.xml", bpmnModel)
|
||||||
|
.tenantId(TENANT_ID) // Must not be "default"
|
||||||
|
.deploy();
|
||||||
|
|
||||||
|
// Start root process
|
||||||
|
ProcessInstance rootProcessInstance = startProcessInstance(wrapperProcessKey, variables);
|
||||||
|
|
||||||
|
// Store the process IDs for using them in assertions
|
||||||
|
ProcessInstance runtimeTestProcessInstance = processEngine.getRuntimeService()
|
||||||
|
.createProcessInstanceQuery()
|
||||||
|
.superProcessInstanceId(rootProcessInstance.getId())
|
||||||
|
.singleResult();
|
||||||
|
String testProcessId;
|
||||||
|
if (runtimeTestProcessInstance != null) {
|
||||||
|
testProcessId = runtimeTestProcessInstance.getId();
|
||||||
|
} else {
|
||||||
|
HistoricProcessInstance historicTestProcessInstance = processEngine.getHistoryService()
|
||||||
|
.createHistoricProcessInstanceQuery()
|
||||||
|
.superProcessInstanceId(rootProcessInstance.getId())
|
||||||
|
.singleResult();
|
||||||
|
// if history level is none, testProcessId is not available
|
||||||
|
testProcessId = historicTestProcessInstance != null ? historicTestProcessInstance.getId() : null;
|
||||||
|
}
|
||||||
|
ObjectNode rootNode = jsonUtils.getEmptyObjectNode();
|
||||||
|
rootNode.put(ROOT_PROCESS_ID, rootProcessInstance.getProcessInstanceId());
|
||||||
|
rootNode.put(TEST_PROCESS_ID, testProcessId);
|
||||||
|
return rootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BpmnModel createWrapperTestProcessModel(String testKey, ObjectNode variables) {
|
||||||
|
BpmnModel bpmnModel = new BpmnModel();
|
||||||
|
Process process = new Process();
|
||||||
|
process.setId(testKey + "_T");
|
||||||
|
process.setName(testKey + "_T");
|
||||||
|
|
||||||
|
StartEvent startEvent = new StartEvent();
|
||||||
|
startEvent.setId(testKey + "_Start");
|
||||||
|
startEvent.setName(testKey + "_Start");
|
||||||
|
process.addFlowElement(startEvent);
|
||||||
|
|
||||||
|
CallActivity callActivity = new CallActivity();
|
||||||
|
callActivity.setId(testKey);
|
||||||
|
callActivity.setCalledElement(testKey);
|
||||||
|
JsonNode inNode = variables.get("__IN");
|
||||||
|
if (inNode instanceof ObjectNode) {
|
||||||
|
Map<String, Object> inMap = jsonUtils.convertObjectNodeToMap((ObjectNode) inNode);
|
||||||
|
ArrayList<IOParameter> inParameters = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, Object> entry : inMap.entrySet()) {
|
||||||
|
IOParameter ioParameter = new IOParameter();
|
||||||
|
ioParameter.setSourceExpression("${__IN." + entry.getKey() + "}");
|
||||||
|
ioParameter.setTarget(entry.getKey());
|
||||||
|
inParameters.add(ioParameter);
|
||||||
|
}
|
||||||
|
callActivity.setInParameters(inParameters);
|
||||||
|
}
|
||||||
|
JsonNode outNode = variables.get("__OUT");
|
||||||
|
if (outNode instanceof ObjectNode) {
|
||||||
|
Map<String, String> outMap = (Map) jsonUtils.convertJsonNodeToMap(variables).get("__OUT");
|
||||||
|
ArrayList<IOParameter> outParameters = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, String> entry : outMap.entrySet()) {
|
||||||
|
IOParameter ioParameter = new IOParameter();
|
||||||
|
ioParameter.setSource(entry.getKey());
|
||||||
|
ioParameter.setTarget(entry.getValue());
|
||||||
|
outParameters.add(ioParameter);
|
||||||
|
}
|
||||||
|
callActivity.setOutParameters(outParameters);
|
||||||
|
}
|
||||||
|
process.addFlowElement(callActivity);
|
||||||
|
|
||||||
|
EndEvent endEvent = new EndEvent();
|
||||||
|
endEvent.setId(testKey + "_End");
|
||||||
|
endEvent.setName(testKey + "_End");
|
||||||
|
process.addFlowElement(endEvent);
|
||||||
|
|
||||||
|
SequenceFlow start = new SequenceFlow(testKey + "_Start", testKey);
|
||||||
|
start.setId("Start_Sequence_Flow");
|
||||||
|
process.addFlowElement(start);
|
||||||
|
|
||||||
|
SequenceFlow end = new SequenceFlow(testKey, testKey + "_End");
|
||||||
|
end.setId("End_Sequence_Flow");
|
||||||
|
process.addFlowElement(end);
|
||||||
|
|
||||||
|
bpmnModel.addProcess(process);
|
||||||
|
return bpmnModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.flowable.local.work.model;
|
||||||
|
|
||||||
|
import com.flowable.spring.boot.properties.FlowableMailProperties;
|
||||||
|
import com.icegreen.greenmail.util.GreenMail;
|
||||||
|
import com.icegreen.greenmail.util.ServerSetup;
|
||||||
|
|
||||||
|
import jakarta.mail.internet.MimeMessage;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import static com.icegreen.greenmail.util.ServerSetup.PROTOCOL_SMTP;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TestMailServer {
|
||||||
|
private GreenMail greenMail;
|
||||||
|
|
||||||
|
private final FlowableMailProperties flowableMailProperties;
|
||||||
|
public TestMailServer(FlowableMailProperties flowableMailProperties) {
|
||||||
|
this.flowableMailProperties = flowableMailProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setup() {
|
||||||
|
greenMail = new GreenMail(new ServerSetup(flowableMailProperties.getPort(), flowableMailProperties.getHost(), PROTOCOL_SMTP));
|
||||||
|
greenMail.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
greenMail.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MimeMessage[] getMessages() {
|
||||||
|
return greenMail.getReceivedMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package com.flowable.local.work.model;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
public class TestMailServerExtension implements BeforeEachCallback, AfterEachCallback {
|
||||||
|
@Override
|
||||||
|
public void beforeEach(ExtensionContext context) {
|
||||||
|
getTestMailServer(context).setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterEach(ExtensionContext context) {
|
||||||
|
getTestMailServer(context).stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TestMailServer getTestMailServer(ExtensionContext context) {
|
||||||
|
return SpringExtension.getApplicationContext(context).getBean(TestMailServer.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package com.flowable.local.work.model.test;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.*;
|
||||||
|
|
||||||
|
import com.flowable.local.work.model.EmailDto;
|
||||||
|
import com.flowable.local.work.model.FlowableModelTest;
|
||||||
|
import com.flowable.local.work.model.FlowableModelTestUtils;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.flowable.cmmn.api.runtime.CaseInstance;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@FlowableModelTest
|
||||||
|
public class ModelTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected FlowableModelTestUtils flowableModelTest;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled
|
||||||
|
public void exportApp() {
|
||||||
|
flowableModelTest.exportApp("http://localhost:8093", "TST_APP", "admin", "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void c001Test() {
|
||||||
|
flowableModelTest.setTestAuthenticatedUser("admin", null);
|
||||||
|
CaseInstance caseInstance = flowableModelTest.startCaseInstance("TST_C001", Map.of());
|
||||||
|
String caseInstanceId = caseInstance.getId();
|
||||||
|
flowableModelTest.completeOpenTask("TST_P001_T001", flowableModelTest.loadObjectNodeFromResources("model/test/C001/T001.json"), "COMPLETE");
|
||||||
|
Assertions.assertThat(flowableModelTest.getRuntimeCasePayload(caseInstanceId).get("testText")).isEqualTo("my test text");
|
||||||
|
|
||||||
|
flowableModelTest.completeOpenTask("TST_C001_T002", flowableModelTest.emptyNode(), "COMPLETE");
|
||||||
|
Assertions.assertThat(flowableModelTest.getHistoryCasePayload(caseInstanceId).get("testText")).isEqualTo("my test text");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void p005HappyPathTest() {
|
||||||
|
ObjectNode processIds = flowableModelTest.createRootTestProcessInstance("TST_P005", flowableModelTest.emptyMap());
|
||||||
|
flowableModelTest.claimOpenTask("TST_P005_T001", "admin");
|
||||||
|
flowableModelTest.completeOpenTask("TST_P005_T001", flowableModelTest.loadObjectNodeFromResources("model/test/P005/T001.json"), null);
|
||||||
|
Map<String, Object> rootProcessPayload = flowableModelTest.getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
|
||||||
|
Assertions.assertThat(rootProcessPayload.get("dataEntry")).isEqualTo("my root text");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Stream<Arguments> p001TestData() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("admin", "model/test/P001/initiator.json")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("p001TestData")
|
||||||
|
public void p001Test(String initiator, String path) {
|
||||||
|
flowableModelTest.setTestAuthenticatedUser(initiator, null);
|
||||||
|
ObjectNode processIds = flowableModelTest.createRootTestProcessInstance("TST_P001",
|
||||||
|
flowableModelTest.loadMapFromResources(path));
|
||||||
|
flowableModelTest.completeOpenTask("TST_P001_T001", flowableModelTest.emptyNode(), "complete");
|
||||||
|
Map<String, Object> rootProcessPayload = flowableModelTest.getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
|
||||||
|
|
||||||
|
Assertions.assertThat(rootProcessPayload.get("rootResult")).isEqualTo(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Stream<Arguments> p002TestData() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("model/test/P002/boolean1.json", true, false),
|
||||||
|
Arguments.of("model/test/P002/boolean2.json", false, true),
|
||||||
|
Arguments.of("model/test/P002/string1.json", "hello root", "hello"),
|
||||||
|
Arguments.of("model/test/P002/string2.json", "123", "456"),
|
||||||
|
Arguments.of("model/test/P002/string3.json", "123.456,", "456.789"),
|
||||||
|
Arguments.of("model/test/P002/int.json", 123, 456),
|
||||||
|
Arguments.of("model/test/P002/double.json", 123.456, 456.789),
|
||||||
|
Arguments.of("model/test/P002/date.json", "2025-11-12", "2025-11-11")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("p002TestData")
|
||||||
|
public void p002Test(String path, Object result, Object out) {
|
||||||
|
ObjectNode processIds = flowableModelTest.createRootTestProcessInstance("TST_P002",
|
||||||
|
flowableModelTest.loadMapFromResources(path));
|
||||||
|
Map<String, Object> rootProcessPayload = flowableModelTest.getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
|
||||||
|
|
||||||
|
List<EmailDto> mail = flowableModelTest.getMailList();
|
||||||
|
Assertions.assertThat(mail.size()).isEqualTo(0);
|
||||||
|
|
||||||
|
Assertions.assertThat(rootProcessPayload.get("result")).isEqualTo(result);
|
||||||
|
Assertions.assertThat(rootProcessPayload.get("out")).isEqualTo(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<Arguments> p002ExcelTestData() {
|
||||||
|
return flowableModelTest.getJsonArgumentsFromExcel("model/test/P002/p002Test.xlsx");
|
||||||
|
}
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("p002ExcelTestData")
|
||||||
|
public void p002ExcelTest(String path, JsonNode argument) {
|
||||||
|
ObjectNode result = flowableModelTest.testJsonExcelRow(path, argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<Arguments> p002ExcelTestData2() {
|
||||||
|
return flowableModelTest.getObjArgumentsFromExcel("model/test/P002/p002Test.xlsx");
|
||||||
|
}
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("p002ExcelTestData2")
|
||||||
|
public void p002ExcelTest2(String path, Map<String, Object> argument) {
|
||||||
|
Map<String, Object> result = flowableModelTest.testObjExcelRow(path, argument);
|
||||||
|
flowableModelTest.checkAndAssertEmail(result, 1,
|
||||||
|
"Email", "Test", "test@flowable.com");
|
||||||
|
flowableModelTest.checkAndAndAssertAuditRecord(result, 1,
|
||||||
|
"Audit record", "TST_P002 Audit trail entry", "system", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
spring.datasource.url=jdbc:h2:mem:flowable-work-db;DB_CLOSE_DELAY=1000
|
||||||
|
spring.datasource.username=admin
|
||||||
|
spring.datasource.password=test
|
||||||
|
|
||||||
|
# To make our life easier in tests we are disabling the async executor and elasticsearch
|
||||||
|
flowable.async-executor-activate=false
|
||||||
|
flowable.async-history-executor-activate=false
|
||||||
|
flowable.indexing.enabled=false
|
||||||
|
management.health.elasticsearch.enabled=false
|
||||||
|
management.metrics.export.elastic.enabled=false
|
||||||
|
|
||||||
|
# Set debug level in tests
|
||||||
|
logging.level.com.flowable=INFO
|
||||||
|
logging.level.com.flowable.local.work.model.FlowableModelTestUtils=INFO
|
||||||
|
|
||||||
|
# Disable the timeout process in the tests
|
||||||
|
flowable.external-system.wechat.timeout.process-definition-key=
|
||||||
|
|
||||||
|
# Path to test-auto-deploy-apps
|
||||||
|
flowable.app.resource-location=classpath*:/test-auto-deploy-apps/
|
||||||
|
|
||||||
|
# Email
|
||||||
|
flowable.mail.server.host=localhost
|
||||||
|
flowable.mail.server.port=3025
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"root": {
|
||||||
|
"testText": "my test text"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"__IN": {
|
||||||
|
"initiator": "admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"param": true,
|
||||||
|
"__IN": {
|
||||||
|
"param": false
|
||||||
|
},
|
||||||
|
"__OUT": {
|
||||||
|
"result": "out"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"param": false,
|
||||||
|
"__IN": {
|
||||||
|
"param": true
|
||||||
|
},
|
||||||
|
"__OUT": {
|
||||||
|
"result": "out"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"param": "2025-11-12",
|
||||||
|
"__IN": {
|
||||||
|
"param": "2025-11-11"
|
||||||
|
},
|
||||||
|
"__OUT": {
|
||||||
|
"result": "out"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"param": 123.456,
|
||||||
|
"__IN": {
|
||||||
|
"param": 456.789
|
||||||
|
},
|
||||||
|
"__OUT": {
|
||||||
|
"result": "out"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"param": 123,
|
||||||
|
"__IN": {
|
||||||
|
"param": 456
|
||||||
|
},
|
||||||
|
"__OUT": {
|
||||||
|
"result": "out"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"param": "hello root",
|
||||||
|
"__IN": {
|
||||||
|
"param": "hello"
|
||||||
|
},
|
||||||
|
"__OUT": {
|
||||||
|
"result": "out"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"param": "123",
|
||||||
|
"__IN": {
|
||||||
|
"param": "456"
|
||||||
|
},
|
||||||
|
"__OUT": {
|
||||||
|
"result": "out"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"param": "123.456,",
|
||||||
|
"__IN": {
|
||||||
|
"param": "456.789"
|
||||||
|
},
|
||||||
|
"__OUT": {
|
||||||
|
"result": "out"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"root": {
|
||||||
|
"dataEntry": "my root text"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,295 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Apache Maven Wrapper startup batch script, version 3.3.4
|
||||||
|
#
|
||||||
|
# Optional ENV vars
|
||||||
|
# -----------------
|
||||||
|
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||||
|
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
|
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||||
|
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
set -euf
|
||||||
|
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||||
|
|
||||||
|
# OS specific support.
|
||||||
|
native_path() { printf %s\\n "$1"; }
|
||||||
|
case "$(uname)" in
|
||||||
|
CYGWIN* | MINGW*)
|
||||||
|
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||||
|
native_path() { cygpath --path --windows "$1"; }
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# set JAVACMD and JAVACCMD
|
||||||
|
set_java_home() {
|
||||||
|
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||||
|
if [ -n "${JAVA_HOME-}" ]; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||||
|
|
||||||
|
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||||
|
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||||
|
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="$(
|
||||||
|
'set' +e
|
||||||
|
'unset' -f command 2>/dev/null
|
||||||
|
'command' -v java
|
||||||
|
)" || :
|
||||||
|
JAVACCMD="$(
|
||||||
|
'set' +e
|
||||||
|
'unset' -f command 2>/dev/null
|
||||||
|
'command' -v javac
|
||||||
|
)" || :
|
||||||
|
|
||||||
|
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||||
|
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# hash string like Java String::hashCode
|
||||||
|
hash_string() {
|
||||||
|
str="${1:-}" h=0
|
||||||
|
while [ -n "$str" ]; do
|
||||||
|
char="${str%"${str#?}"}"
|
||||||
|
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||||
|
str="${str#?}"
|
||||||
|
done
|
||||||
|
printf %x\\n $h
|
||||||
|
}
|
||||||
|
|
||||||
|
verbose() { :; }
|
||||||
|
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf %s\\n "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trim() {
|
||||||
|
# MWRAPPER-139:
|
||||||
|
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||||
|
# Needed for removing poorly interpreted newline sequences when running in more
|
||||||
|
# exotic environments such as mingw bash on Windows.
|
||||||
|
printf "%s" "${1}" | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptDir="$(dirname "$0")"
|
||||||
|
scriptName="$(basename "$0")"
|
||||||
|
|
||||||
|
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
|
while IFS="=" read -r key value; do
|
||||||
|
case "${key-}" in
|
||||||
|
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||||
|
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||||
|
esac
|
||||||
|
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
|
||||||
|
case "${distributionUrl##*/}" in
|
||||||
|
maven-mvnd-*bin.*)
|
||||||
|
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||||
|
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||||
|
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||||
|
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||||
|
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||||
|
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||||
|
*)
|
||||||
|
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||||
|
distributionPlatform=linux-amd64
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||||
|
;;
|
||||||
|
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||||
|
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
|
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||||
|
distributionUrlName="${distributionUrl##*/}"
|
||||||
|
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||||
|
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||||
|
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||||
|
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||||
|
|
||||||
|
exec_maven() {
|
||||||
|
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||||
|
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -d "$MAVEN_HOME" ]; then
|
||||||
|
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||||
|
exec_maven "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${distributionUrl-}" in
|
||||||
|
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||||
|
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# prepare tmp dir
|
||||||
|
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||||
|
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||||
|
trap clean HUP INT TERM EXIT
|
||||||
|
else
|
||||||
|
die "cannot create temp dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||||
|
|
||||||
|
# Download and Install Apache Maven
|
||||||
|
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||||
|
verbose "Downloading from: $distributionUrl"
|
||||||
|
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
|
||||||
|
# select .zip or .tar.gz
|
||||||
|
if ! command -v unzip >/dev/null; then
|
||||||
|
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||||
|
distributionUrlName="${distributionUrl##*/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# verbose opt
|
||||||
|
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||||
|
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||||
|
|
||||||
|
# normalize http auth
|
||||||
|
case "${MVNW_PASSWORD:+has-password}" in
|
||||||
|
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||||
|
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||||
|
verbose "Found wget ... using wget"
|
||||||
|
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||||
|
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||||
|
verbose "Found curl ... using curl"
|
||||||
|
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||||
|
elif set_java_home; then
|
||||||
|
verbose "Falling back to use Java to download"
|
||||||
|
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||||
|
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
cat >"$javaSource" <<-END
|
||||||
|
public class Downloader extends java.net.Authenticator
|
||||||
|
{
|
||||||
|
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||||
|
{
|
||||||
|
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||||
|
}
|
||||||
|
public static void main( String[] args ) throws Exception
|
||||||
|
{
|
||||||
|
setDefault( new Downloader() );
|
||||||
|
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END
|
||||||
|
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||||
|
verbose " - Compiling Downloader.java ..."
|
||||||
|
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||||
|
verbose " - Running Downloader.java ..."
|
||||||
|
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||||
|
if [ -n "${distributionSha256Sum-}" ]; then
|
||||||
|
distributionSha256Result=false
|
||||||
|
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||||
|
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||||
|
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
|
exit 1
|
||||||
|
elif command -v sha256sum >/dev/null; then
|
||||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
|
||||||
|
distributionSha256Result=true
|
||||||
|
fi
|
||||||
|
elif command -v shasum >/dev/null; then
|
||||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||||
|
distributionSha256Result=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||||
|
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ $distributionSha256Result = false ]; then
|
||||||
|
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||||
|
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# unzip and move
|
||||||
|
if command -v unzip >/dev/null; then
|
||||||
|
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||||
|
else
|
||||||
|
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||||
|
actualDistributionDir=""
|
||||||
|
|
||||||
|
# First try the expected directory name (for regular distributions)
|
||||||
|
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
|
||||||
|
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
|
||||||
|
actualDistributionDir="$distributionUrlNameMain"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||||
|
if [ -z "$actualDistributionDir" ]; then
|
||||||
|
# enable globbing to iterate over items
|
||||||
|
set +f
|
||||||
|
for dir in "$TMP_DOWNLOAD_DIR"/*; do
|
||||||
|
if [ -d "$dir" ]; then
|
||||||
|
if [ -f "$dir/bin/$MVN_CMD" ]; then
|
||||||
|
actualDistributionDir="$(basename "$dir")"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
set -f
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$actualDistributionDir" ]; then
|
||||||
|
verbose "Contents of $TMP_DOWNLOAD_DIR:"
|
||||||
|
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
|
||||||
|
die "Could not find Maven distribution directory in extracted archive"
|
||||||
|
fi
|
||||||
|
|
||||||
|
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||||
|
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
|
||||||
|
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||||
|
|
||||||
|
clean || :
|
||||||
|
exec_maven "$@"
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
<# : batch portion
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
@REM or more contributor license agreements. See the NOTICE file
|
||||||
|
@REM distributed with this work for additional information
|
||||||
|
@REM regarding copyright ownership. The ASF licenses this file
|
||||||
|
@REM to you under the Apache License, Version 2.0 (the
|
||||||
|
@REM "License"); you may not use this file except in compliance
|
||||||
|
@REM with the License. You may obtain a copy of the License at
|
||||||
|
@REM
|
||||||
|
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@REM
|
||||||
|
@REM Unless required by applicable law or agreed to in writing,
|
||||||
|
@REM software distributed under the License is distributed on an
|
||||||
|
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
@REM KIND, either express or implied. See the License for the
|
||||||
|
@REM specific language governing permissions and limitations
|
||||||
|
@REM under the License.
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Apache Maven Wrapper startup batch script, version 3.3.4
|
||||||
|
@REM
|
||||||
|
@REM Optional ENV vars
|
||||||
|
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
|
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||||
|
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||||
|
@SET __MVNW_CMD__=
|
||||||
|
@SET __MVNW_ERROR__=
|
||||||
|
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||||
|
@SET PSModulePath=
|
||||||
|
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||||
|
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||||
|
)
|
||||||
|
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||||
|
@SET __MVNW_PSMODULEP_SAVE=
|
||||||
|
@SET __MVNW_ARG0_NAME__=
|
||||||
|
@SET MVNW_USERNAME=
|
||||||
|
@SET MVNW_PASSWORD=
|
||||||
|
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
|
||||||
|
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||||
|
@GOTO :EOF
|
||||||
|
: end batch / begin powershell #>
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
if ($env:MVNW_VERBOSE -eq "true") {
|
||||||
|
$VerbosePreference = "Continue"
|
||||||
|
}
|
||||||
|
|
||||||
|
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
|
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||||
|
if (!$distributionUrl) {
|
||||||
|
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||||
|
"maven-mvnd-*" {
|
||||||
|
$USE_MVND = $true
|
||||||
|
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||||
|
$MVN_CMD = "mvnd.cmd"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
$USE_MVND = $false
|
||||||
|
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
|
if ($env:MVNW_REPOURL) {
|
||||||
|
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||||
|
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
|
||||||
|
}
|
||||||
|
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||||
|
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||||
|
|
||||||
|
$MAVEN_M2_PATH = "$HOME/.m2"
|
||||||
|
if ($env:MAVEN_USER_HOME) {
|
||||||
|
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
|
||||||
|
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$MAVEN_WRAPPER_DISTS = $null
|
||||||
|
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
|
||||||
|
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
|
||||||
|
} else {
|
||||||
|
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
|
||||||
|
}
|
||||||
|
|
||||||
|
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
|
||||||
|
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||||
|
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||||
|
|
||||||
|
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||||
|
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||||
|
exit $?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||||
|
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
# prepare tmp dir
|
||||||
|
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||||
|
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||||
|
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||||
|
trap {
|
||||||
|
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||||
|
|
||||||
|
# Download and Install Apache Maven
|
||||||
|
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||||
|
Write-Verbose "Downloading from: $distributionUrl"
|
||||||
|
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
|
||||||
|
$webclient = New-Object System.Net.WebClient
|
||||||
|
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||||
|
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||||
|
}
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||||
|
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||||
|
if ($distributionSha256Sum) {
|
||||||
|
if ($USE_MVND) {
|
||||||
|
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||||
|
}
|
||||||
|
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||||
|
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||||
|
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# unzip and move
|
||||||
|
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||||
|
|
||||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||||
|
$actualDistributionDir = ""
|
||||||
|
|
||||||
|
# First try the expected directory name (for regular distributions)
|
||||||
|
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
|
||||||
|
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
|
||||||
|
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
|
||||||
|
$actualDistributionDir = $distributionUrlNameMain
|
||||||
|
}
|
||||||
|
|
||||||
|
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||||
|
if (!$actualDistributionDir) {
|
||||||
|
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
|
||||||
|
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
|
||||||
|
if (Test-Path -Path $testPath -PathType Leaf) {
|
||||||
|
$actualDistributionDir = $_.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$actualDistributionDir) {
|
||||||
|
Write-Error "Could not find Maven distribution directory in extracted archive"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||||
|
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||||
|
try {
|
||||||
|
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||||
|
} catch {
|
||||||
|
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||||
|
Write-Error "fail to move MAVEN_HOME"
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.flowable.local</groupId>
|
||||||
|
<artifactId>14</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.7.18</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>flowable-local-work</module>
|
||||||
|
<module>flowable-local-design</module>
|
||||||
|
<module>flowable-local-design-angular</module>
|
||||||
|
<module>flowable-local-control</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<java.version>11</java.version>
|
||||||
|
<com.flowable.platform.version>3.14.10</com.flowable.platform.version>
|
||||||
|
<org.postgresql.version>42.7.8</org.postgresql.version>
|
||||||
|
<com.h2database.version>2.2.224</com.h2database.version>
|
||||||
|
<liquibase.skip>false</liquibase.skip>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<!-- Imports the bill-of-materials POM. -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.flowable</groupId>
|
||||||
|
<artifactId>flowable-platform-bom</artifactId>
|
||||||
|
<version>${com.flowable.platform.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||||
|
<servers>
|
||||||
|
<server>
|
||||||
|
<id>flowable-repo</id>
|
||||||
|
<username>USER</username> <!-- Replace USER -->
|
||||||
|
<password>PASSWORD</password> <!-- Replace PASSWORD -->
|
||||||
|
</server>
|
||||||
|
</servers>
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>flowable-artifacts</id>
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>flowable-repo</id>
|
||||||
|
<url>https://artifacts.flowable.com/artifactory/flowable-maven-all</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
<activeProfiles>
|
||||||
|
<activeProfile>flowable-artifacts</activeProfile>
|
||||||
|
</activeProfiles>
|
||||||
|
</settings>
|
||||||
Binary file not shown.
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
java --add-exports java.desktop/com.apple.eawt=ALL-UNNAMED -jar ./fakeSMTP-2.0.jar -o ./received-emails -s -p 2525
|
||||||
Reference in New Issue
Block a user