1. Developing on-premise applications
This document describes best practices for developing a customized on-premise application based on edoras one. It is mainly intended for software developers, although some topics address system installation and configuration and may therefore also be of interest to system administrators.
Various topics are presented, grouped by general theme, with each topic describing a specific development task. The topics are generally independent of each other and can be read on their own without having to read the entire document. A working knowledge of the concepts underlying edoras one is required, and some topics will also require knowledge of edoras gear or some additional external technologies.
2. Obtaining the example project
Many of the examples described in this document are demonstrated in an example on-premise project. To obtain a copy of this project please contact edorasware support.
3. Overview of the example project
The example project demonstrates the following aspects of developing an on-premise application with edoras one:
-
the basic configuration needed to start edoras one based on the core dependencies. The files involved here are generally identified with bootstrap, either in the file path or filename.
-
additional customizations for a specific project (in our examples we use the fictitious domain com.acme). The files involved here are generally identified with acme, either in the file path or filename.
4. Creating a clean skeleton project
The example project is pre-packaged with example services, listeners etc. already in the project. If you need a bare project for your own development you can strip out all of this additional example code:
-
obtain the example project
-
go to the project root folder
-
execute the following Gradle command on the command line:
./gradlew createSkeletonProject
gradlew.bat createSkeletonProject
The project will then be a clean skeleton project.
5. Setting up a development environment
Developing on-premise applications with edoras one is fairly straightforward if you are familiar with standard Java development workflows:
-
create a new project using a build tool (e.g. Maven or Gradle)
-
include the edoras one artifacts in your project as normal dependencies
-
provide the basic configuration needed to start edoras one
-
extend edoras one by adding your own configurations and / or classes as required
This section describes setting up your development environment and including edoras one into your project. The following sections describe the edoras one extension points and how they can be used to customize edoras one and integrate it into your IT infrastructure.
The minimum Java version to develop with edoras one is 7. If you use Java 8 then you will need to add the following system property such that it starts up without any
errors: javax.xml.accessExternalSchema=all .
|
5.1. Configuring the build system
5.1.1. Basic project build information
The example project provides build configurations for both the Maven and Gradle build systems. You can use whichever build system you prefer. For the Maven build Maven 3.0.0 or greater is required. The Gradle build is based on the Gradle wrapper and downloads the appropriate version automatically.
You can download the build tools here:
Basic Maven configuration
The project information and compiler version (Java 7 is required) should be set in the Maven pom.xml
:
pom.xml
<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.edorasware.one</groupId>
<artifactId>edoras-one-bootstrap</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>
pom.xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
You should also configure the resource encoding to avoid warnings during the build:
pom.xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
5.1.2. Artifact repository configuration
To build the project you will need access to a Maven repository containing the edoras one artifacts so that the edoras one dependencies can be resolved. The artifacts can either be uploaded to a repository manually, or if you have access to the edorasware public repository then the connection details may be configured into your local repository and artifacts will be downloaded from edorasware automatically when they are needed.
After adding the edoras one artifacts to your repository you will also need to provide a suitable repository configuration and access credentials.
Maven repository configuration
The Maven repository configuration will typically be located in the pom.xml
file.
The following snippet shows the configuration for the edoras repository.
The configuration for your local repository will look similar.
pom.xml
for Maven builds <repositories>
<repository>
<id>repo.edorasware.com</id>
<url>https://repo.edorasware.com/edoras-repo</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.edorasware.com</id>
<url>https://repo.edorasware.com/edoras-repo</url>
</pluginRepository>
</pluginRepositories>
Add the repository credentials to the servers
section in your local Maven configuration file.
By default you can find the Maven configuration file at <USER_HOME>/.m2/settings.xml
.
<servers>
<server>
<id>repo.edorasware.com</id>
<username>customer-username</username>
<password>customer-password</password>
</server>
</servers>
Additional details of how to configure a Maven repository can be found on the Maven project page.
Gradle repository configuration
The Gradle repository configuration will typically be located in the build.gradle
file.
The following snippet shows the configuration for the edoras repository.
The configuration for your local repository will look similar.
build.gradle
for Gradle builds repositories {
maven {
credentials {
username DOWNLOAD_REPO_USERNAME
password DOWNLOAD_REPO_PASSWORD
}
url "https://repo.edorasware.com/edoras-repo"
}
mavenCentral()
}
Add the credentials that you received from edorasware to your local gradle.properties
file.
By default you can find the Gradle configuration file at <USER_HOME>/.gradle/gradle.properties
.
DOWNLOAD_REPO_USERNAME=customer-username
DOWNLOAD_REPO_PASSWORD=customer-password
5.1.3. edoras one dependency configuration
The edoras one artifacts can now be added to the project as a dependency.
Maven dependency configuration
By adding a property to the <properties>
tag of the Maven pom.xml
file the edoras one version can be
set in a single place:
pom.xml
<properties>
<com.edorasware.one.version>@projectVersion@</com.edorasware.one.version>
</properties>
This property can then be used in the dependency configuration to import the required dependencies:
pom.xml
<!-- Compile dependencies -->
<dependency>
<groupId>com.edorasware.one</groupId>
<artifactId>edoras-one-client</artifactId>
<version>${com.edorasware.one.version}</version>
</dependency>
<dependency>
<groupId>com.edorasware.one</groupId>
<artifactId>edoras-one-server-rest</artifactId>
<version>${com.edorasware.one.version}</version>
<exclusions>
<!-- TODO fix for https://issues.apache.org/jira/browse/BATIK-1038 -->
<exclusion>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-extensions</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.edorasware.one</groupId>
<artifactId>edoras-one-server-test</artifactId>
<version>${com.edorasware.one.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.edorasware.one</groupId>
<artifactId>edoras-one-server-core</artifactId>
<classifier>test</classifier>
<version>${com.edorasware.one.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.edorasware.license</groupId>
<artifactId>edoras-license-one</artifactId>
<classifier>development</classifier>
<version>1.0.5</version>
</dependency>
Gradle dependency configuration
By adding a property to the <properties>
tag of the Gradle build.gradle
file the edoras one version can be
set in a single place:
build.gradle
def edorasOneVersion = '@projectVersion@'
This property can then be used in the dependency configuration to import the required dependencies:
build.gradle
compile "com.edorasware.one:edoras-one-client:$edorasOneVersion"
compile("com.edorasware.one:edoras-one-server-rest:$edorasOneVersion") {
//TODO fix for https://issues.apache.org/jira/browse/BATIK-1038
exclude group: 'org.apache.xmlgraphics', module: 'batik-extensions'
}
compile "com.edorasware.one:edoras-gear-search:$edorasOneVersion"
testCompile "com.edorasware.one:edoras-one-server-test:$edorasOneVersion"
testCompile "com.edorasware.one:edoras-one-server-core:$edorasOneVersion:test"
testCompile "com.edorasware.one:edoras-gear-search:$edorasOneVersion:test"
testRuntime "com.edorasware.license:edoras-license-one:1.0.5:development"
5.1.4. Database dependency configuration
In addition to the edoras one dependencies, at least one database dependency is also required. For convenience the build configurations from the example project already contain dependencies for some commonly-used databases:
pom.xml
for Maven builds <dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.168</version>
</dependency>
build.gradle
for Gradle builds compile "com.h2database:h2:1.3.168"
5.1.5. Logging dependency configuration
edoras one uses the Simple Logging Facade for Java (SLF4J) for logging. SLF4J is a framework that allows the end user to plug in the desired logging framework at deployment time. You can learn more about slf4j on the {slf4j-page}.
Maven dependency configuration
By adding a property to the <properties>
tag of the Maven pom.xml
file the slf4j version can be
set in a single place:
pom.xml
<org.slf4j.version>1.7.7</org.slf4j.version>
This property can then be used in the dependency configuration to import the required dependencies. The following dependencies instruct SLF4J to send all logging output from components used in edoras one to slf4j:
pom.xml
for Maven builds <dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
<version>1.1.3</version>
<scope>provided</scope> <!-- globally replace commons-logging with jcl-over-slf4j -->
</dependency>
A suitable adapter can then be configured to send the slf4j output to a particular logging system. In the bootstrap project we use log4j, so we include the slf4j-to-log4j adapter:
pom.xml
for Maven builds <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
Gradle dependency configuration
By adding a property to the <properties>
tag of the Gradle build.gradle
file the slf4j version can be
set in a single place:
build.gradle
def slf4jVersion = '1.7.12'
This property can then be used in the dependency configuration to import the required dependencies. The following dependencies instruct SLF4J to send all logging output from components used in edoras one to slf4j:
build.gradle
runtime "org.slf4j:jul-to-slf4j:$slf4jVersion"
runtime "org.slf4j:jcl-over-slf4j:$slf4jVersion"
// globally replace commons-logging with jcl-over-slf4j
providedRuntime "commons-logging:commons-logging:1.1.3"
A suitable adapter can then be configured to send the slf4j output to a particular logging system. In the bootstrap project we use log4j, so we include the slf4j-to-log4j adapter:
build.gradle
runtime "org.slf4j:slf4j-log4j12:$slf4jVersion"
5.2. Configuring edoras one
edoras one is configured using Spring. For details of the Spring project, please refer to the Spring project page. In the default configuration, environment-specific components are selected using Spring profiles, and the component settings are generally either fixed in the configuration or made configurable using properties.
The starting point for the Spring configuration is defined in the web.xml
file that is deployed with
the application. This will typically contain the location of the root Spring configuration file and the default
profiles that will be used:
webapp/WEB-INF/web.xml
<!-- Spring context configuration -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/com/edorasware/acme/config/acme-context.xml</param-value>
</context-param>
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>database-h2,integration-development,security-basic</param-value>
</context-param>
To override the default Spring profiles, the active profiles may be set from the command line when the
application server is started: -Dspring.profiles.active="…"
.
The majority of the necessary Spring configuration is provided by the edoras one artifacts and can be reused by an on-premise project, but a small amount of configuration is required for each specific installation.
To configure the on-premise project you will need to provide a Spring application context configuration, for example:
com/edorasware/bootstrap/config/one-application-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="propertyPlaceholderConfigurer" class="com.edorasware.one.spring.OnePropertyPlaceholderConfigurer" >
<property name="blacklist" value="password"/>
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
<property name="ignoreResourceNotFound" value="true"/>
<property name="locations">
<list>
<value>classpath:/com/edorasware/bootstrap/config/one.properties</value>
<value>file:${edoras-one.home:${user.home}/.edoras-one}/one.properties</value>
</list>
</property>
</bean>
<!-- configure JUL-SLF4J rerouting as Activiti logs to Java Util Logging API -->
<bean id="julReroute"
class="com.edorasware.commons.core.util.logging.JulToSlf4jBridgeHandlerInstaller"
init-method="init"/>
<!-- import the edoras one, edoras vis and edoras cmmn context -->
<import resource="classpath*:/config/one-core-application-context.xml"/>
<import resource="classpath:/com/edorasware/vis/config/vis-application-context.xml"/>
<!-- import the database configuration -->
<import resource="classpath:/com/edorasware/bootstrap/config/database-config.xml"/>
<!-- import the security configuration -->
<import resource="classpath:/com/edorasware/bootstrap/config/security/*-config.xml"/>
<!-- import the integration configuration -->
<import resource="classpath:/com/edorasware/bootstrap/config/integration-config.xml"/>
<!-- import the content configuration -->
<import resource="classpath:/com/edorasware/bootstrap/config/content-config.xml"/>
<!-- import the action configuration -->
<import resource="classpath:/com/edorasware/bootstrap/config/action-config.xml"/>
</beans>
In this configuration, the properties are read in using application defaults (provided by the on-premise WAR file) and can be overridden by system properties.
The files one-core-application-context.xml
and vis-application-context.xml
are provided by edoras one
and contain the default edoras one Spring configuration.
The remaining imports are provided by the example on-premise project and contain the configuration for the database connection, security context, basic integration services and the content provider. These configurations are described separately.
The application context can naturally contain any additional Spring configuration needed by the
on-premise implementation. The one-application-context.xml
can also be included in a project-specific configuration
file to improve readability and maintainability (see com/edorasware/acme/config/acme-context.xml
in the example project).
5.2.1. Database configuration
In the example project, the database is configured in the com/edorasware/bootstrap/config/database-config.xml
file, which is loaded from the classpath:
com/edorasware/bootstrap/config/database-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="transactionManager" name="schemaTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<beans profile="database-h2">
<bean id="h2HikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="poolName" value="edorasOnePool"/>
<property name="connectionTestQuery" value="SELECT 1"/>
<property name="driverClassName" value="org.h2.Driver"/>
<property name="jdbcUrl" value="${h2Url}"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
<property name="connectionTimeout" value="30000"/>
<property name="idleTimeout" value="30000"/>
<property name="maxLifetime" value="1800000"/>
<property name="maximumPoolSize" value="4"/>
</bean>
<bean id="dataSource" name="databaseSchemaManagerDataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<property name="targetDataSource">
<bean class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<constructor-arg name="configuration" ref="h2HikariConfig"/>
</bean>
</property>
</bean>
</beans>
<beans profile="database-jndi">
<bean id="dataSource" name="databaseSchemaManagerDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="${databaseJndiName}"/>
</bean>
</beans>
<beans profile="database-jndi-xa">
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="schemaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="dataSource" ref="databaseSchemaManagerDataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="${databaseJndiName}"/>
</bean>
<bean id="databaseSchemaManagerDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="${schemaDatabaseJndiName}"/>
</bean>
</beans>
</beans>
The active database is selected using a Spring profile which should be set when the application is started. In the bootstrap project the following database profiles are supported by default:
Profile |
Description |
database-h2 |
Use a local h2 database. By default the database content will be stored in the |
database-jndi |
Use a JNDI database provided by the application server. The default database JNDI name is |
If you want to externalize the database properties then they can be added to the com/edorasware/bootstrap/config/one.properties
properties file, and can then be overwritten either in the installation-specific one.properties file or from the command line.
The persistence management configuration is able to distinguish data and schema manipulation datasource. The example of persistence management
configuration with 2 datasources:
<gear:persistence-management
id="myPersistenceManagement"
data-source="dataManipulationDataSource"
database-type="h2"
database-schema-creation-strategy="CREATE_DROP"/>
<!-- configure edoras database schema service/manager -->
<bean id="databaseSchemaService" class="com.edorasware.commons.core.persistence.schema.internal.DefaultDatabaseSchemaService">
<constructor-arg name="dataSource" ref="databaseSchemaManagerDataSource"/>
<constructor-arg name="migrationsLocation" value="com/edorasware/commons/core/persistence/schema"/>
<constructor-arg name="transactionManager" ref="transactionManager"/>
</bean>
<bean id="databaseSchemaManager" class="com.edorasware.commons.core.persistence.schema.internal.StrategyBasedDatabaseSchemaManager">
<constructor-arg name="databaseSchemaService" ref="databaseSchemaService"/>
<constructor-arg name="strategy" value="CREATE_DROP"/>
</bean>
<bean id="databaseSchemaManagerLifecycleBean" class="com.edorasware.gear.core.persistence.schema.DatabaseSchemaManagerLifecycleBean">
<constructor-arg name="databaseSchemaManager" ref="databaseSchemaManager"/>
</bean>
<!-- configured data sources -->
<bean id="databaseSchemaManagerDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver"/>
<!-- The script creates dataManipulationUser which is used in the datamanipulation datasource -->
<property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MVCC=TRUE;INIT=
RUNSCRIPT FROM 'classpath:com/edorasware/gear/documentation/userguide/PersistenceManagementTest-databaseSchemaManager2DataSources.sql'"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="dataManipulationDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" depends-on="databaseSchemaManagerDataSource">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MVCC=TRUE"/>
<property name="username" value="dataManipulationUser"/>
<property name="password" value="passwd"/>
</bean>
5.2.2. Security configuration
The application security is configured by files in the folder com/edorasware/bootstrap/config/security
.
Standard configurations are provided for basic authentication (security-basic-config.xml
):
com/edorasware/bootstrap/config/security/security-basic-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<import resource="security-config-base.xml"/>
<security:http pattern="/login.jsp" auto-config="true">
<security:custom-filter ref="csrfFilter" position="CSRF_FILTER"/>
<security:custom-filter ref="csrfHeaderFilter" after="CSRF_FILTER"/>
<security:form-login login-page="/login.jsp"
default-target-url="/"
always-use-default-target="true"
authentication-failure-url="/login-error.jsp"/>
</security:http>
<security:http pattern="/login-error.jsp" auto-config="true">
<security:custom-filter ref="csrfFilter" position="CSRF_FILTER"/>
<security:custom-filter ref="csrfHeaderFilter" after="CSRF_FILTER"/>
<security:form-login login-page="/login.jsp"
default-target-url="/"
always-use-default-target="true"
authentication-failure-url="/login-error.jsp"/>
</security:http>
<security:http pattern="/rest/**">
<security:intercept-url pattern="/rest/**" access="ROLE_USER"/>
<security:session-management session-fixation-protection="none"/>
<security:custom-filter ref="sessionManagementFilter" position="SESSION_MANAGEMENT_FILTER"/>
<security:custom-filter ref="csrfFilter" position="CSRF_FILTER"/>
<security:custom-filter ref="csrfHeaderFilter" after="CSRF_FILTER"/>
<security:http-basic/>
<security:logout/>
</security:http>
<security:http>
<security:intercept-url pattern="/**" access="ROLE_USER"/>
<security:custom-filter ref="browserRedirectionFilter" position="FIRST"/>
<security:custom-filter position="SWITCH_USER_FILTER" ref="switchUserProcessingFilter"/>
<security:intercept-url pattern="/j_spring_security_switch_user" access="ROLE_USER"/>
<security:session-management session-fixation-protection="none"/>
<security:custom-filter ref="sessionManagementFilter" position="SESSION_MANAGEMENT_FILTER"/>
<security:custom-filter ref="csrfFilter" position="CSRF_FILTER"/>
<security:custom-filter ref="csrfHeaderFilter" after="CSRF_FILTER"/>
<security:http-basic/>
<security:logout/>
<security:form-login login-page="/login.jsp"
default-target-url="/"
always-use-default-target="true"
authentication-failure-url="/login-error.jsp"/>
</security:http>
<bean id="sessionManagementFilter"
class="org.springframework.security.web.session.SessionManagementFilter">
<constructor-arg name="securityContextRepository"
ref="httpSessionSecurityContextRepository"/>
<property name="invalidSessionStrategy">
<bean class="com.edorasware.cloud.security.CloudInvalidSessionStrategy">
<constructor-arg name="invalidSessionUrl" value="${application.endpoint}/login.jsp"/>
</bean>
</property>
</bean>
<bean id="httpSessionSecurityContextRepository" class="org.springframework.security.web.context.HttpSessionSecurityContextRepository"/>
<bean id="browserRedirectionFilter" class="com.edorasware.one.servlet.BrowserRedirectionFilter">
<property name="loginPageUrlPattern" value="(.*)/login.jsp"/>
</bean>
<bean id="csrfHeaderFilter" class="com.edorasware.cloud.security.CsrfHeaderFilter">
<constructor-arg name="xsrfTokenName" value="${xsrf.cookie.name}"/>
</bean>
<bean id="csrfFilter" class="org.springframework.security.web.csrf.CsrfFilter">
<constructor-arg ref="xsrfTokenRepository"/>
<property name="accessDeniedHandler" ref="accessDeniedHandler"/>
</bean>
<bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/login-error.jsp"/>
</bean>
<bean id="xsrfTokenRepository" class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository">
<property name="headerName" value="X-XSRF-TOKEN"/>
</bean>
<!-- default authentication manager which uses the default one user details service to get
the user work objects. The passwords are also encoded with the default password encoder -->
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsService">
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
</beans>
In the example root configuration file (com/edorasware/bootstrap/config/one-application-context.xml
), the security configuration is
imported with the help of the security.type
system property which defaults to basic
. In the case when CSRF
token are not needed
for your solution you can use security-low
profile. For the security profiles security-basic
and security-embedded
CSRF
cookie
name can be set by xsrf.cookie.name
property and header name by xsrf.header.name
property.
If you want to create your own security configuration you need to set the security.type
property (e.g. to extended
) and then create
a corresponding security configuration file inside the
com/edorasware/bootstrap/config/security
folder (e.g. security-extended-config.xml
).
5.2.3. Integration configuration
The integration of edoras one with external systems is configured in the file com/edorasware/bootstrap/config/integration-context.xml
:
com/edorasware/bootstrap/config/integration-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<beans profile="integration-development">
<bean id="mailSender" class="com.edorasware.cloud.core.mail.LoggingMailSender"/>
</beans>
<beans profile="integration-production">
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="${mail.smtp.host}"/>
<property name="port" value="${mail.smtp.port}"/>
<property name="username" value="${mail.smtp.username}"/>
<property name="password" value="${mail.smtp.password}"/>
<property name="defaultEncoding" value="${mail.smtp.encoding}"/>
<property name="javaMailProperties">
<props>
<prop key="mail.debug">${mail.smtp.debug}</prop>
<prop key="mail.transport.protocol">${mail.smtp.transport.protocol}</prop>
<prop key="mail.smtp.auth">${mail.smtp.auth}</prop>
<prop key="mail.smtp.starttls.enable">${mail.smtp.starttls.enable}</prop>
<prop key="mail.smtp.socketFactory.port">${mail.smtp.port}</prop>
<prop key="mail.smtp.socketFactory.fallback">false</prop>
<prop key="mail.smtp.quitwait">false</prop>
</props>
</property>
</bean>
</beans>
</beans>
This example uses two Spring profiles to define the mail sender bean which is used to send emails.
Details of specific integration options and how they can be configured are provided in the relevant sections of this document.
5.2.4. Content configuration
The storage of file content in edoras one is configured in the file com/edorasware/bootstrap/config/content-config.xml
:
com/edorasware/acme/config/content-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- configure the content provider -->
<bean id="contentManager" class="com.edorasware.commons.core.content.internal.DefaultConfigurableContentManager">
<constructor-arg>
<list>
<ref bean="databaseContentProvider"/>
</list>
</constructor-arg>
<constructor-arg ref="currentTenantService"/>
<constructor-arg ref="currentUserService"/>
<constructor-arg ref="transactionManager"/>
</bean>
<bean id="deprecatedContentProvider" class="com.edorasware.cloud.core.content.internal.FileBasedContentProvider">
<constructor-arg value="${fileBasedContentProvider.contentDir}"/>
<constructor-arg value="${contentProvider.tempDir}"/>
<constructor-arg value="2"/>
</bean>
</beans>
This example stores content using the default database content provider where the content is stored in the database. For single node
setups we also provide the FilesystemContentProvider
which stores the content on the file system. Please refer to its JavaDoc for furhter
information how to configure and use it.
5.3. Tenant initialization
To use edoras one, you also need to create a tenant. An edoras one installation may support multiple tenants using a single database, where each tenant is completely isolated from the others (i.e. it has it’s own set of users, work objects, Apps etc.).
On startup, edoras one looks for tenant JSON configuration files in the locations configured by the tenant.data.location
property.
If a configuration file is found for a tenant not already present in the database then that tenant will be initialised according
to the tenant configuration file:
acme.json
{
"name": "acme",
"accounts": [
{
"name": "acme",
"domain": "acme.com",
"groups": [ "Manager" ],
"users": [
{
"firstName": "John",
"lastName": "Smith",
"login": "john",
"email": "john.smith@acme.com",
"memberGroups": [ "Manager" ],
"language": "en"
}
]
}
]
}
Once a tenant is initialised it will not be updated, even if the tenant configuration file is changed at a later date.
For a full description of the tenant JSON format, please refer to the edoras one administration guide.
5.4. Logging configuration
In the bootstrap project, we are using log4j as the logging framework. The default logging configuration for log4j is provided
by the log4j.properties
file in the root package.
log4j.properties
# Comment this line and uncomment the following to allow log writing to a local file
log4j.rootLogger=INFO, A
# log4j.rootLogger=INFO, A, local.file
log4j.appender.A=org.apache.log4j.ConsoleAppender
log4j.appender.A.layout=org.apache.log4j.PatternLayout
log4j.appender.A.layout.ConversionPattern=%d{ISO8601} %-5p %-85.85c - %m%n
log4j.appender.local.file=org.apache.log4j.FileAppender
log4j.appender.local.file.append=false
log4j.appender.local.file.file=/tmp/edoras.log
log4j.appender.local.file.threshold=INFO
log4j.appender.local.file.layout=org.apache.log4j.PatternLayout
log4j.appender.local.file.layout.ConversionPattern=%-5p %c: %m%n
## Quieten distracting noise
log4j.logger.org.springframework=WARN
log4j.logger.com.edorasware.gear.core.maintenance.internal.jdbc.JdbcMaintenancePersistence=WARN
## Spring integration
#log4j.logger.org.springframework.integration=DEBUG
## Spring web
#log4j.logger.org.springframework.web=DEBUG
## Project
#log4j.logger.com.edorasware.customer.acme=DEBUG
If an alternative logging configuration is needed in a given environment, this can be specified without rebuilding the
WAR file by setting the appropriate system property when the application server is started,
for example by adding -Dlog4j.configuration=file:C:/tmp/log4j.properties
to the application server command line.
5.5. Property settings
The default property settings are defined in the one.properties
file located in the com/edorasware/bootstrap/config
folder.
Installation-specific property values can be set using system properties, for example by adding them to the application server command line.
For a full description of the standard edoras one properties, please refer to the edoras one administration guide.
5.6. edoras vis configuration
The edoras vis configuration is optional as the defaults should be sufficient for most project. If you still need to change the palette or adapt the edoras vis editor then please read on.
5.6.1. Palette configuration
Palette configuration should be added into the project’s Spring configuration as shown below.
<bean id="paletteConfiguration" class="com.edorasware.bpm.modeler.config.PaletteConfiguration">
<property name="paths">
<list>
<value>classpath:com/edorasware/vis/palette</value>
</list>
</property>
<property name="baseProcessPalette" value="classpath:com/edorasware/vis/palette/base.process.palette.xml"/>
<property name="baseFormPalette" value="classpath:com/edorasware/vis/palette/base.form.palette.xml"/>
<property name="baseCasePalette" value="classpath:com/edorasware/vis/palette/base.case.palette.xml"/>
<property name="defaultProcessPaletteName" value="default.process.palette.xml"/>
<property name="defaultFormPaletteName" value="default.form.palette.xml"/>
<property name="defaultCasePaletteName" value="default.case.palette.xml"/>
</bean>
Name | Description |
---|---|
paths |
List of palette folder paths |
baseProcessPalette |
Fully qualified base process palette file name |
baseFormPalette |
Fully qualified base form palette file name |
baseCasePalette |
Fully qualified base case palette file name |
defaultProcessPaletteName |
Fully qualified default process palette file name |
defaultFormPaletteName |
Fully qualified default form palette file name |
defaultCasePaletteName |
Fully qualified default case palette file name |
5.6.2. Editor configuration
Editor configuration should be added into the project’s Spring configuration as shown below.
<bean id="editorConfiguration" class="com.edorasware.bpm.modeler.config.EditorConfiguration">
<property name="disableSaveDialog" value="true"/>
<property name="showSystemFormPalette" value="false"/>
<property name="showSystemProcessPalette" value="false"/>
<property name="showSystemCasePalette" value="false"/>
<property name="saveNotificationUrl" value="../rest/modeler"/>
</bean>
Name | Description |
---|---|
disableSaveDialog |
Flag to enable or disable showing the save dialog for each save operation |
showSystemFormPalette |
Flag to show/hide system form palette in the form designer |
showSystemProcessPalette |
Flag to show/hide system process palette in the process designer |
showSystemCasePalette |
Flag to show/hide system case palette in the case designer |
saveNotificationUrl |
URL to which the save notification will be posted |
5.7. License file
To start edoras one you will need a valid edoras one license file.
The license location is configured in the one.properties
file located in the com/edorasware/bootstrap/config
folder.
com/edorasware/bootstrap/config/one.properties
edorasware.license = file:${user.home}/.edorasware/edorasware.license
5.8. Build and deploy the on-premise WAR file
The project WAR file can now be build using the Maven package
target or the Gradle war
task. The generated war
can then be deployed in a suitable application server.
5.9. Developing with Eclipse
This section guides you through the basic setup needed to develop edoras one on-premise projects using the Eclipse IDE. It assumes that you have a copy of the edoras one bootstrap project and have access to the edorasware repository.
If you plan to use the project for real development then you should place the project under some form of source control (e.g. Git or Subversion) before importing it into the IDE and making your own modifications. This will not be covered here.
5.9.1. Add the edoras repository credentials to your Maven configuration
All artifacts required to build and run your edoras one are available in the edoras repository.
This repository is usually configured in the POM of your edoras one bootstrap project.
The only missing information is the repository access credentials.
Add the credentials that you received from edorasware to the servers
section in your local Maven configuration file.
By default you can find the Maven configuration file at <USER_HOME>/.m2/settings.xml
.
... <servers> ... <server> <id>repo.edorasware.com</id> <username>customer-username</username> <password>customer-password</password> </server> ... </servers> ...
5.9.2. Install Eclipse
Download an appropriate package for your system from the Eclipse project page and install it in a suitable location.
5.9.4. Configure the Java runtime
edoras one runs inside an application server and requires larger memory settings than the ones that are available by default in a Java VM. In the following steps we change the memory settings to a suitable value.
Select the Window
⇒ Preferences
menu item to open the Preferences
dialog.
Choose the Installed JREs
preferences in the Java
section.
Select the default JRE (that is the one highlighted in bold) and press the Edit…
button to show the Edit JRE
dialog.
Add the option -XX:MaxPermSize=128M
to the Default VM arguments
text field.
This increases the maximum permanent generation size to a value that is sufficient for edoras one.
5.9.5. Import the bootstrap project into your workspace
Select the File
⇒ Import…
menu item to open the project import dialog. Then select Existing Maven Projects
:
Then select the folder where you have extracted the bootstrap project:
5.9.6. Configure an application server
edoras one is deployed to standard Servlet 3.0 web application servers. The following shows how to integrate the Tomcat 7.0 web application server into Eclipse. Please refer to the Tomcat installation guide to learn how to install Tomcat in your local environment.
Integration of other application servers is done in a similar way.
When you have installed Tomcat to a local directory, select the File
⇒ New
⇒ Other…
menu
item to open the Select a Wizard
dialog. Choose the Server
option in the Server
section and press the Next
button.
In the Define a New Server
page of the wizard, choose the Tomcat 7.0 Server
type and press the Next
button.
In the Tomcat Server
page of the wizard, press the Browse…
button
and choose the home directory of your installed Tomcat web application server.
Press the Finish
button to create the application server inside Eclipse.
Select the Window
⇒ Show View
⇒ Servers
menu item.
This brings you to the Servers
view.
Double click the Tomcat 7.0 Server
option to open the editor for that server.
Change the timeouts in the Timeouts
section: change Start
to 450 seconds and Stop
to 150 seconds.
This is required as it takes the application server some time to start up and stop edoras one and the
default settings are too small.
Switch to the Modules
tab by clicking the Modules
label at the bottom of the editor.
In the Modules
tab press the Add Web Module…
button to open the Add Web Module
dialog.
Choose your project module and press the OK
button to deploy your project into the application server.
When you have added the web module, you can change the deployment path by editing the table entry (setting
the path when creating the web module does not currently work). Change your deployment path to '/acme' and
select the File
⇒ Save All
menu item to save your changes.
5.9.7. Start the application server
Select the application server in the Servers
view
and press the Start the server
toolbar button.
Congratulations, you now have started your edoras one application!
Direct your browser to http://localhost:8080/acme
and enjoy!
:idea-page: IntelliJ IDEA project page
:tomcat-install-page: Tomcat installation guide
5.10. Developing with IntelliJ IDEA
This section guides you through the basic setup needed to develop edoras one on-premise projects using the IntelliJ IDEA IDE. It assumes that you have a copy of the edoras one bootstrap project and have access to the edorasware repository.
If you plan to use the project for real development then you should place the project under some form of source control (e.g. Git or Subversion) before importing it into the IDE and making your own modifications. This will not be covered here.
5.10.1. Add the edoras repository credentials to your Maven configuration
All artifacts required to build and run your edoras one are available in the edoras repository.
This repository is usually configured in the POM of your edoras one bootstrap project.
The only missing information is the repository access credentials.
Add the credentials that you received from edorasware to the servers
section in your local Maven configuration file.
By default you can find the Maven configuration file at <USER_HOME>/.m2/settings.xml
.
... <servers> ... <server> <id>repo.edorasware.com</id> <username>customer-username</username> <password>customer-password</password> </server> ... </servers> ...
5.10.2. Install IntelliJ IDEA
Download an appropriate package for your system from the {idea-page} and install it in a suitable location.
5.10.4. Import the bootstrap project into your workspace
Either select Import project
from the IDEA start screen, or use the File
⇒ Open…
menu item from an existing project.
Select the Maven pom.xml
(or Gradle build.gradle
) from the folder where you extracted the bootstrap project:
You may see some information dialogs that alternative build files have been detected. These dialogs may be safely ignored.
5.10.5. Check the project language level
Open the project settings dialog with File
⇒ Project Structure…
and check that the Project language level
is set to 7
:
5.10.6. Configure an application server
edoras one is deployed to standard Servlet 3.0 web application servers. The following shows how to integrate the Tomcat 7.0 web application server into IDEA. Please refer to the Tomcat installation guide to learn how to install Tomcat in your local environment.
Integration of other application servers is done in a similar way.
When you have installed Tomcat to a local directory, select the Run
⇒ Edit Configurations…
menu
item to open the run configuration dialog:
Select the +
icon to add a new run configuration, and then select Tomcat Server
⇒ Local
.
Give the run configuration a suitable name (e.g. bootstrap
), and configure the installed Tomcat application
server using the Configure…
button.
Depending on your system, it may be necessary to assign more memory to the server process by adding the
following settings to the VM options
:
-Xms512m -Xmx1024m -XX:MaxPermSize=256m
Add the bootstrap deployment artifact by selecting the Deployment
tab and adding the
deployment artifact edoras-one-bootstrap:war exploded
:
When it has been added, you can change the Application context
setting to /acme
by selecting the artifact
in the deployment list.
5.10.7. Start the application server
Select the bootstrap run configuration from the selection widget in the IDEA toolbar, and then press the green arrow to the right to start the server.
Congratulations, you now have started your edoras one application!
Direct your browser to http://localhost:8080/acme
(if it has not been opened automatically) and enjoy!
6. Customizing edoras one
This section describes the extension points for common on-premise use cases. If no suitable extension point is documented for a feature that you require then contact edorasware support to find a solution.
6.1. Overriding edoras one bean definitions
The default edoras one Spring bean configurations can be overridden if a new Spring bean is defined with the same ID after the base configuration has been imported (if two bean definitions have the same ID then the last definition wins).
6.2. Action event listeners
Custom work object action event listeners can be added to the edoras one configuration by creating the action listener implementation and then registering it as a bean with a bean ID that matches the following pattern:
customer{First|Last}{Task|Case|Process|WorkObject|Document}{|Definition}ActionListener
customerFirst
listeners will be invoked before all other listeners, customerLast
listeners will be
invoked after all other listeners. This rule applies for both the work objects and definitions.
If more than one listener is required, then the listeners can be combined using a composite action listener
(Composite{Task|Case|Process|WorkObject}ActionListener ) and the composite listener registered
using the above naming convention.
|
As an example, a listener to log the creation of new task objects can be defined:
package com.edorasware.acme.listeners;
import com.edorasware.gear.core.task.support.TaskActionEvent;
import com.edorasware.gear.core.task.support.TaskActionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingTaskActionListener implements TaskActionListener {
private static final Logger LOG =
LoggerFactory.getLogger(LoggingTaskActionListener.class);
@Override
public void actionWillBePerformed(TaskActionEvent event) {
if (event.isCreationEvent()) {
LOG.info("About to create task {}", event.getNewTask().getId());
}
}
@Override
public void actionPerformed(TaskActionEvent event) {
if (event.isCreationEvent()) {
LOG.info("Created task {}", event.getNewTask().getId());
}
}
}
and then the bean can be defined with a suitable ID to register it with the task service:
<bean id="customerLastTaskActionListener"
class="com.edorasware.acme.listeners.LoggingTaskActionListener"/>
For details on action listener implementation, please refer to the edoras gear documentation.
6.3. Automatic App updates
When a new tenant is initialized an App called the System App is automatically installed. This App contains the models required for correct operation of the edoras one software, and is typically loaded from the edoras one artifacts as it is strongly tied to a specific edoras one release.
- NOTE
-
It is strongly recommended that the System App be updated automatically to make sure that the latest version is active after the edoras one dependency has been updated to a newer version (which may also include a new System App).
Section Defining App development workflows describes how to configure workflows to support transfer of Apps between systems with automatic updates, and the section Incoming adapter lifecycle describes how the pre-defined system app adapter can be configured to perform automatic updates of the System App.
6.4. Expressions and service beans
The powerful expression resolver used in the edoras one server supports access to arbitrary Spring bean property values and methods. This capability can be used in on-premise projects to integrate custom Java code with process models via expressions: service beans can be written in Java and registered with the expression resolver, and the appropriate methods can then be invoked from a process model at the appropriate time. There are many potential ways to use this capability, but some typical use cases might be:
-
data initialization
-
data conversion
-
encapsulation of complex business logic
-
integration with external systems
6.4.1. Defining service beans
Service beans are simply plain Java objects registered as a Spring bean with a particular ID.
As an example, if we want to create a service to generate new task names, we can create the Java class:
package com.edorasware.acme.expression;
import com.edorasware.api.expression.ExpressionBean;
import org.activiti.engine.delegate.DelegateExecution;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
/**
* An example service bean that allows UUID strings to be created from a process model.
*/
@ExpressionBean
public class TaskNameService {
private static AtomicInteger taskCount = new AtomicInteger();
/**
* Returns a new task name based on the given base name.
*
* @param baseName the base name
* @return the task name
*/
public String nextTaskName(String baseName) {
return baseName + "-" + taskCount.incrementAndGet();
}
}
and use this to create a corresponding Spring bean with a suitable ID, either explicitly:
<bean id="taskNameService"
class="com.edorasware.acme.expression.TaskNameService"/>
or by adding an appropriate Spring annotation (e.g. @Service
) to the class definition
and enabling the Spring component scan for the relevant package:
<!-- Scan the project-specific classes to locate REST controllers etc. -->
<context:component-scan base-package="com.edorasware.acme"/>
To allow open access to all available Spring beans from a process expression would be a big security
issue, so edoras one only allows the expression resolver to access only a limited set of beans.
A specific bean can be enabled for expressions by adding the @ExpressionBean
annotation to the bean
class.
6.4.2. Accessing service beans
Once the bean has been defined and made accessible, it can be used by expressions within process definitions.
As an example we can use the taskNameService
bean to generate task names for user tasks:
6.4.3. Passing values using method parameters
The simplest way to pass values to a service bean method is to simply use the expression to provide the relevant values to the method call. We used this approach in the previous example when we passed in the name of the parent case:
#{taskNameService.nextTaskName(case.name)}
This approach has a number of advantages:
-
the input values are modeled, and it is therefore relatively easy for the modeler to understand what is happening
-
the same method may be used in different contexts where the source values come from different places
-
changes to variable names can be made without affecting running processes (old processes will continue to use the old variable name)
It works well for simple utility function when only a few parameters are required, but as the number of parameters increases, the method invocations become complex and therefore awkward to write and maintain.
6.4.4. Passing values using the execution context
For more complex use cases, an alternative is to provide the current Activiti execution context as a parameter to the
service task method and allow the method itself to extract the information it needs and make any changes that may be
required. When doing this, it is useful to extend the AbstractActivityService
class, as this provides a number of
useful utility methods for working with execution context, such as locating the current work object. The following example
service uses the execution context to add a message to a variable in the root work object:
/**
* An example service bean that uses an execution context.
*/
@Service
@ExpressionBean
public class AcmeService extends AbstractActivityService {
// type-safe variable definition for the service message
public static final VariableName<String, String> SERVICE_MSG =
VariableName.create("serviceMsg", String.class);
@Autowired
public AcmeService(GearExpressionResolver gearExpressionResolver, GenericWorkObjectService genericWorkObjectService) {
super(gearExpressionResolver, genericWorkObjectService, false);
}
/**
* Adds a service invocation message to the root work object.
*
* @param execution the execution context
*/
public void invoke(DelegateExecution execution) {
WorkObject<?, ?, ?> workObjectInScope = getWorkObjectInScope(execution);
WorkObject<?, ?, ?> rootObject = getRootObject(workObjectInScope);
WorkObjectUpdate.Builder<?, ?, ?, ?, ?> updateBuilder =
this.genericWorkObjectService.createUpdateBuilder(rootObject.getGlobalId());
String message = "AcmeService was invoked at " + getTimestamp()
+ " with tag " + getTag(execution);
updateBuilder.putVariable(SERVICE_MSG, message);
this.genericWorkObjectService.apply(updateBuilder.build(), "ACME service invocation");
}
/**
* Returns the value of the tag property in the task definition.
*
* @param execution the execution context
* @return the value of the tag property
*/
protected String getTag(DelegateExecution execution) {
Map<String, String> properties = getEdorasPropertyMapForCurrentActivity(execution);
String value = properties.get("edoras:tag");
return hasText(value) ? value : "UNDEFINED";
}
/**
* Returns a string with the current timestamp.
*
* @return the current timestamp
*/
protected String getTimestamp() {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
return dateFormat.format(new Date());
}
}
The execution context is passed into the method call by using the execution
keyword within the
expression:
#{acmeService.invoke(execution)}
This approach delegates full control to the service implementation, allowing it to navigate the work object hierarchy to read or modify whatever it needs to. Obviously, this is very powerful, but it also means that the values used by the method are not visible to the modeler, making it hard to see what is really going on. It is therefore important to try to make the behaviour as intuitive as possible and to provide good documentation for the available services to the modelers.
As the data used by the implementation is coded in Java rather then being part of the model, it is also more complicated to change the way that values are stored in the work object hierarchy. Any change to the values used by the service will take effect immediately for all processes that use that service in the future, including processes that are already running.
6.4.5. Managing database transactions in service beans
Service bean method invocations are typically executed from within a process. The process engine already manages the transactions used during process execution, and so no additional transaction handling needs to be provided by service bean implementations.
6.4.6. Security
As service beans have access to the full functionality of Java and edoras one, it is also important that security is considered when creating a new service bean implementation. Users should not be able to use a service bean to gain access to confidential information to which they would not otherwise have access, to corrupt or delete information, or to interfere with the normal functioning of edoras one.
6.4.7. Creating custom service tasks in the process palette
We have already seen how service beans can be invoked using expressions. When specific service beans are used on a regular basis, it may be convenient to extend the process palette in the process modeler to include customized service tasks for common use cases.
Details of how to customize the process palette can be found in the section Customizing palettes.
As an example, we start by creating a patch to the default edoras one process palette (thereby reusing all of the existing palette configuration):
<palette id="acme-process-palette"
resource-bundle="translation"
apply-patch-to-palette="edoras-one-process-palette"
xmlns="http://www.edorasware.com/schema/vis/palette"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.edorasware.com/schema/vis/palette http://www.edorasware.com/schema/vis/edoras-vis-process-palette-2.0.57.xsd">
<!-- palette configuration... -->
</palette>
To make our custom tasks easy to find, we will create a separate group to contain them:
<group id="acme-tasks">
<component id="acmeservicetask" extends="ServiceTask"
attribute-groups="acmeServiceTaskAttributes"/>
</group>
In the process modeler, this group is now added to the existing ones:
We can then define the attributes that should appear when the service task is edited. For our example, we will provide a default name and background color, set the expression to invoke the service (not visible in the modeler), define a new tag attribute, and suppress some of the additional service task attributes that are not needed:
<attribute-groups>
<attribute-group id="acmeServiceTaskAttributes">
<!-- Provide suitable default values for base attributes -->
<attribute category="common" id="name" type="SimpleText"
value="ACME service task"
readonly="false" optional="true"
ref-to-view="text_name"/>
<attribute id="bgcolor" category="common" type="Color"
value="#ccffcc" index="190"
readonly="false" optional="false"
ref-to-view="fill_el" fill="true" stroke="false"/>
<!-- use a fixed expression to invoke the service -->
<attribute id="expression" category="edoras" type="SimpleText"
value="#{acmeService.invoke(execution)}"
readonly="true" optional="false" visible="false"/>
<!-- add a custom attribute that we can access in the service -->
<attribute id="tag" category="edoras" type="SimpleText"
value="default" export="true"
readonly="false" optional="false"/>
<!-- suppress attributes that aren't needed -->
<attribute id="delegateExpression" category="edoras" type="SimpleText"
value=""
readonly="false" optional="true" visible="false"/>
<attribute id="resultVariable" category="edoras" type="SimpleText"
value=""
readonly="false" optional="true" visible="false"/>
<attribute id="class" category="edoras" type="SimpleText"
value=""
readonly="false" optional="true" visible="false"/>
</attribute-group>
</attribute-groups>
Note that the tag attribute is marked as 'exported', meaning that it will be added to the final task definition as
a property, where it can be accessed by the service bean. The resulting definition property key has
the edoras:
prefix to avoid possible collisions with other properties used in the process engine.
The display strings used in the modeler are supplied by the corresponding resource bundle, allowing them to be translated:
acme-tasks.title = ACME tasks
acmeservicetask.title = ACME service task
acmeServiceTaskAttributes.tag.title = Tag
acmeServiceTaskAttributes.tag.description = Tag
When we add the custom task to a process in the modeler, we can see that the default attribute values are applied, and that some of the normal service task attributes are no longer available:
6.5. REST services
REST controller classes in edoras one are defined as plain Java classes annotated with the standard Spring Web annotations:
/**
* Controller that provides basic application reference data.
*/
@Controller
@RequestMapping(value = "/referencedata")
public final class ReferenceDataController {
// ...
}
The individual REST endpoints are defined within a REST controller using annotations on the endpoint
implementation methods. For example, the following method defines a REST endpoint for the GET HTTP request method,
with the URL /referencedata/domains
(the concatenation of the class and method request mappings). It also
supports the optional typedText
request parameter and returns a list of domains which will be encoded in JSON format:
/**
* Look up domains that match the given typed text.
*
* @param typedText the text typed (usually from an auto-complete field)
* @return a list of matching domains with an OK response code
*/
@RequestMapping(value = "domains", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<List<Domain>> getDomains(
@RequestParam(required = false) String typedText)
{
LOG.debug("Requesting domains for '{}'", typedText);
List<Domain> domains = this.referenceDataManager.getDomains(typedText);
return new ResponseEntity<>(domains, RestUtils.JSON_HTTP_HEADERS, HttpStatus.OK);
}
To activate the REST service controller, you can either declare it explicitly as a bean in the Spring configuration, or use a Spring component scan to automatically locate annotated beans in a given package:
<!-- Scan the project-specific classes to locate REST controllers etc. -->
<context:component-scan base-package="com.edorasware.acme"/>
In both cases, the REST endpoints defined by the REST controller will be published under the path '/rest' relative to the
edoras one base URL. So if the main edoras one application is accessible under the URL http://localhost:8080/acme
, the
domains
endpoint shown above will be accessible under http://localhost:8080/acme/rest/referencedata/domains
.
6.5.1. Managing database transactions in REST services
edoras one uses Spring Transaction Management to manage transactions.
If your REST controller methods access a database then you should add the appropriate Spring transaction annotations so that Spring can correctly manage the transactions.
You can either add the annotations to the REST controller methods directly, or to classes invoked by the REST controller.
The recommended pattern is to delegate the business logic for the REST endpoint to a separate class and annotate that class with the appropriate transaction annotations. Using this pattern, exceptions from the business logic will result in the transaction being rolled back before control returns to the REST controller, which is then free to reply to the client in any way that it likes. If transaction annotations are added directly to the REST controller then the REST controller must itself throw an exception to force a rollback of the transaction. If this is not done then partial changes may be committed inadvertently.
Wherever the annotations are added, it is important that the scope of the annotated methods is sufficient for all related operations to be grouped correctly within a single transaction.
As an example, the REST controller for the reference data service delegates calls to a ReferenceDataManager
implementation:
/**
* Manages reference data using persistent database storage.
*/
public class DatabaseReferenceDataManager implements ReferenceDataManager {
@Override
@Transactional(readOnly = true)
public List<Domain> getDomains(String typedText) {
// ...
}
// ...
Note that only the database-driven implementation needs to be annotated in this way. The alternative
StaticReferenceDataManager
implementation used for testing is driven by static data and therefore does not
need any transaction annotations.
6.5.2. Supporting edoras form REST-based widgets
The REST APIs needed to support specific form widgets in edoras one are described in detail in the edoras one Modeler Guide.
6.5.3. Supporting the edoras form Dynamic Link Button widget
The Dynamic Link Button widget requires a REST endpoint with a specific response (see the edoras one Modeler Guide for details). To simplify
the implementation of REST endpoints to support this widget, a Java class is provided that directly supports this response (NavigationResponse
):
import com.edorasware.bootstrap.rest.util.NavigationResponse;
import com.edorasware.one.permission.view.ViewName;
// ...
/**
* Create a navigation response for the next (oldest) task.
*
* @return the navigation response or NOT_FOUND if no task is available
*/
@RequestMapping(value = "nextTask", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<NavigationResponse> getNextTask() {
LOG.debug("Requesting next task navigation");
TaskQuery query = TaskQuery.builder()
.predicate(Task.STATE.isActive())
.sorting(Task.CREATION_TIME.orderDesc())
.limit(1)
.build();
Task task = this.taskService.findTask(query);
if (task == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
NavigationResponse response = NavigationResponse.get(task, ViewName.BROWSE);
return new ResponseEntity<>(response, RestUtils.JSON_HTTP_HEADERS, HttpStatus.OK);
}
6.6. Other project-specific Spring beans
Any project-specific Spring beans can also be configured directly in the application context (or in a file that
is included by the application context). For example we can configure a ReferenceDataManager
instance:
<bean class="com.edorasware.acme.services.referencedata.internal.StaticReferenceDataManager">
<constructor-arg>
<bean class="com.edorasware.cloud.core.config.DerivedStringListFactoryBean">
<property name="sourceString" value="Switzerland,Swaziland,Spain"/>
</bean>
</constructor-arg>
</bean>
This can be injected automatically where it is needed, for example into a reference data REST controller.
6.7. Custom database schema and data upgrades
The current database schema and data upgrade mechanism is based on Flyway. In edoras one we use so called upgrades to manage database schema and data changes. There are two kind of upgrades: SQL and Java data upgrades.
In the following sections we discuss each of these upgrades in detail.
6.7.1. SQL database schema upgrade
If you need to create a customized SQL upgrade, then create your upgrade file (with the .sql
file type) with all the needed SQL statements and save it inside the
com/edorasware/commons/core/persistence/schema/${databaseType}
package such that edoras one is able to retrieve and execute it. The ${databaseType}
needs to be one of these values and the SQL upgrade filename must have the edoras-gear_
prefix and a proper version suffix in the following format:
edoras-gear_A.B.C.X_Small_description.sql
The filename has three main parts, the prefix, the version number and a small description of the upgrade. You need to set all these three parts such that the upgrade will be executed automatically.
The version format is A.B.C.X
where A, B, C and X are numbers. The first three places in the version A.B.C
are reserved for the edoras one upgrades so you can use
the fourth X
number for your custom database schema upgrades. So a valid SQL database schema upgrade file could look like this:
/com/edorasware/commons/core/persistence/schema/h2/edoras-gear_1.0.1.1_Small_description.sql
This upgrade will be executed after the 1.0.1
upgrade from edoras one is executed and it will have Small description
as description. To check which version number you need
to use please have a look at the /com/edorasware/commons/core/persistence/schema/h2/
folder inside the edoras-gear-commons
library and see which versions are used by edoras
one.
Migration to another database
If you want to migrate for example from Oracle 11 to Oracle 12 and you have custom database schema scripts you need to follow these steps:
-
Retrieve the database type for the new database (in the example it would be
oracle12c
) from these values. -
Next create a new package in your project for the new database at
com/edorasware/commons/core/persistence/schema/${databaseType}
. -
Then move the custom schemas from the old database package to the new one and adapt the DDL to match the new database SQL dialect.
After that you are able to run edoras one with the your custom database schema upgrades on the new database.
6.7.2. Java data upgrade
Besides the SQL schema upgrades we also need to upgrade data from time to time. We use the same mechanism here but we do the upgrades in Java. Here you can implement a subclass of
the com.edorasware.one.upgrade.OneBaseUpgrade
which has an abstract upgrade(TenantId)
method which you need to implement. This method gets called when Flyway
executes this upgrade and it is called for each tenant once.
You can define the class as Spring bean and use the comfort of autowiring the needed beans for the data upgrade. You just need to pass the version
and the description
to the
super class. This is similar to the version and description part in the filename of a SQL database schema upgrade. Such an upgrade could look like this:
@SuppressWarnings("unused")
public final class TestUpgrade extends OneBaseUpgrade {
@SuppressWarnings("SpringJavaAutowiredMembersInspection")
@Autowired
public TestUpgrade(BulkWorkObjectProcessor workObjectProcessor,
BulkWorkObjectDefinitionProcessor definitionProcessor,
CurrentTenantService currentTenantService,
CurrentUserService currentUserService,
UserManager userManager,
OnePersistence onePersistence,
PlatformTransactionManager transactionManager,
TenantLookupService tenantLookupService)
{
super("1.0.1.1", "Small description", workObjectProcessor, definitionProcessor,
currentTenantService, currentUserService, userManager, onePersistence, transactionManager, tenantLookupService);
}
@Override
protected void upgrade(TenantId tenantId) {
// do the needed data upgrade here
}
}
In the above example you see how the autowiring capabilities are used for the AnyWorkObjectService
and the other services. The version and the description are just passed to the
super constructor. The OneBaseUpgrade
class provides you with some helper methods to easily iterate over the work objects. Please have a detailed look at the class before doing
the custom upgrade such that you do not duplicate the needed logic.
:acme-root-dir: ../../../edoras-one-bootstrap/
:acme-src-dir: ../../../edoras-one-bootstrap/src/main/
6.8. Custom caching and session storage for clustering
As described in the edoras one Operator Guide, the default configuration for edoras one supports distributed caching and session storage in a clustered environment out of the box using Redis.
This section describes how an alternative distributed caching and session storage mechanism can be configured.
6.8.1. Caching non-static resources
For the non-static resources we need a distributed cache manager that can keep the caches consistent when an object is changed by a cluster node. This cache manager implementation should meet the following requirements:
-
it must implement the interface
org.springframework.cache.CacheManager
-
it must support caches as shown below in the example cache manager bean definition
As an example, a custom distributed cache manager bean could be defined as follows:
<bean id="cacheManager" class="com.edorasware.acme.cluster.DummyCacheManager">
<constructor-arg name="cacheNames">
<util:list value-type="java.lang.String" list-class="java.util.ArrayList">
<!-- edoras gear cache configurations -->
<value>edorasGear-globalIdById</value>
<value>edorasGear-idByGlobalId</value>
<value>edorasGear-taskDefinitionsById</value>
<value>edorasGear-caseDefinitionsById</value>
<value>edorasGear-anyWorkObjectDefinitionsById</value>
<value>edorasGear-timerDefinitionsById</value>
<value>edorasGear-documentDefinitionsById</value>
<value>edorasGear-processDefinitionsById</value>
<value>edorasGear-taskDefinitionsByExternalId</value>
<value>edorasGear-caseDefinitionsByExternalId</value>
<value>edorasGear-anyWorkObjectDefinitionsByExternalId</value>
<value>edorasGear-timerDefinitionsByExternalId</value>
<value>edorasGear-documentDefinitionsByExternalId</value>
<value>edorasGear-processDefinitionsByExternalId</value>
<!-- edoras one cache configurations -->
<value>edorasOne-languagesByLocale</value>
<value>edorasOne-convertedDocuments</value>
<value>edorasOne-previewedDocuments</value>
<value>edorasOne-tenantNameById</value>
<value>edorasOne-tenantIdByName</value>
<value>edorasOne-systemGroupsByTenantId</value>
<!-- edoras vis cache configurations -->
<value>edorasVis-util</value>
<value>edorasVis-paletteCache</value>
<!-- edoras gear cache configurations -->
<value>edorasGear-contentMetadataByContentReferenceId</value>
<value>edorasGear-contentVersionsByContentReferenceId</value>
<value>edorasGear-contentReferencesByContentSourceId</value>
</util:list>
</constructor-arg>
<constructor-arg name="objectMapper" ref="customCacheObjectMapper"/>
</bean>
6.8.2. Caching static resources
For static resources (e.g. definitions), distributed caching is also required. There are special occasions in which static resource can be changed. In the case of definitions there is a possibility to change definition during upgrades. That’s why static resources are added to the distributed caches too.
6.8.3. Defining the global cache manager
The application will use a cache manager instance with bean ID cacheManager
to look up caches.
This bean should have the ID cacheManager
to overwrite the default cache manager provided by the
edoras one base configuration.
6.8.4. Caches and object serialization
Objects stored in a cache must normally be serialized and deserialized, either for transfer to other systems in the cluster or for local persistent storage. A cache implementation may choose its own serialization mechanism, but the following JSON object mapper configuration may be useful in many cases. This object mapper supports all of the cached object types:
<!-- Object serialization for use in the cache -->
<bean id="customCacheObjectMapper" class="com.edorasware.cloud.core.json.config.JsonObjectMapperAdapter">
<property name="serializers">
<list>
<bean class="com.edorasware.cloud.core.json.LocaleSerializer"/>
<bean class="com.edorasware.cloud.core.json.LanguageSerializer"/>
<bean class="com.edorasware.cloud.core.json.SystemGroupsSerializer"/>
<bean class="com.edorasware.cloud.core.json.CacheContentWrapperSerializer"/>
<bean class="com.edorasware.cloud.core.transfer.json.ContentMetadataSerializer"/>
<bean class="com.edorasware.cloud.core.transfer.json.ContentReferenceSerializer"/>
<bean class="com.edorasware.cloud.core.transfer.json.OptionalContentMetadataSerializer"/>
<bean class="com.edorasware.cloud.core.json.DeputyUserSerializer"/>
<bean class="com.edorasware.cloud.core.json.TaskDefinitionSerializer"/>
<bean class="com.edorasware.cloud.core.json.CaseDefinitionSerializer"/>
<bean class="com.edorasware.cloud.core.json.AnyWorkObjectDefinitionSerializer"/>
<bean class="com.edorasware.cloud.core.json.TimerDefinitionSerializer"/>
<bean class="com.edorasware.cloud.core.json.DocumentDefinitionSerializer"/>
<bean class="com.edorasware.cloud.core.json.ProcessDefinitionSerializer"/>
<bean class="com.edorasware.cloud.core.json.WorkObjectDefinitionIdSerializer"/>
<bean class="com.edorasware.cloud.core.json.PropertySerializer"/>
<!-- non-symmetric serializers -->
<bean class="com.edorasware.cloud.core.json.IdSerializer"/>
</list>
</property>
<property name="deserializers">
<list>
<bean class="com.edorasware.cloud.core.json.LocaleDeserializer"/>
<bean class="com.edorasware.cloud.core.json.LanguageDeserializer"/>
<bean class="com.edorasware.cloud.core.json.SystemGroupsDeserializer"/>
<bean class="com.edorasware.cloud.core.json.CacheContentWrapperDeserializer"/>
<bean class="com.edorasware.cloud.core.transfer.json.ContentMetadataDeserializer"/>
<bean class="com.edorasware.cloud.core.transfer.json.ContentReferenceDeserializer"/>
<bean class="com.edorasware.cloud.core.transfer.json.OptionalContentMetadataDeserializer"/>
<bean class="com.edorasware.cloud.core.json.DeputyUserDeserializer"/>
<bean class="com.edorasware.cloud.core.json.TaskDefinitionDeserializer"/>
<bean class="com.edorasware.cloud.core.json.CaseDefinitionDeserializer"/>
<bean class="com.edorasware.cloud.core.json.AnyWorkObjectDefinitionDeserializer"/>
<bean class="com.edorasware.cloud.core.json.TimerDefinitionDeserializer"/>
<bean class="com.edorasware.cloud.core.json.DocumentDefinitionDeserializer"/>
<bean class="com.edorasware.cloud.core.json.ProcessDefinitionDeserializer"/>
<bean class="com.edorasware.cloud.core.json.WorkObjectDefinitionIdDeserializer"/>
<bean class="com.edorasware.cloud.core.json.PropertyDeserializer"/>
</list>
</property>
</bean>
6.8.5. Custom distributed session storage
In addition to distributed caching, an edoras one cluster also requires distributed session storage. If the
default session storage using Redis is not suitable then a custom session repository may be configured
by overwriting the Spring bean with ID sessionRepository
, e.g.:
<bean id="sessionRepository" class="com.edorasware.acme.cluster.DummySessionRepository">
<property name="defaultMaxInactiveInterval" value="${session.timeout}"/>
</bean>
The session repository bean must implement the interface org.springframework.session.SessionRepository
.
6.8.6. Using Spring profiles
To allow the caching and session repository mechanism to be selected at deployment time, the relevant Spring bean
configurations can be wrapped in a <beans>
element and enabled with a specific profile, e.g.:
<beans profile="cache-custom">
<!-- Add the custom bean definitions here -->
</beans>
The required implementation can then be selected at runtime by enabling the appropriate
profile on the command line (e.g. cache-custom
or session-custom
).
6.9. Elasticsearch integration
Edoras one supports flexible Elasticsearch integration. Content from the edoras one database can be mirrored into one or more elasticsearch indices and used for custom search functionality based on the elasticsearch query API. For some queries this may be significantly faster than running equivalent queries directly against the edoras one database.
- NOTE
-
The edoras one database is the definitive data store, and the elasticsearch index may lag behind the database slightly, as updates have to be passed from edoras one to elasticsearch and then indexed. If a particular use case requires definitive current data then you should retrieve this from the database not the elasticsearch index.
This section describes how the elasticsearch integration can be configured.
6.9.1. Set up an elasticsearch cluster
Although edoras one can start an embedded elasticsearch node, this option is only suitable for small development and evaluation environments. For real deployments it will be necessary to start a separate elasticsearch process.
To set up a cluster, simply follow the instructions from the Elasticsearch web page. To get started you can just download the elasticsearch distribution (version 2.3.5), unpack it and run the start script.
You should set the elasticsearch cluster name to edorasone
(see the config/elasticsearch.yml
file in the elasticsearch
distribution).
If you want to include document content from edoras one in the index then you will also need to install the
Mapper attachments plugin in the elasticsearch cluster nodes. The sample full index configuration provided
by the edoras-gear-search
module requires this plugin (see Full index configuration for more details).
For small installations the default cluster settings should be sufficient. If you need more information then please refer to one of the many good resources available on Elasticsearch cluster administration. This is a complex topic that will not be covered here.
6.9.2. Elasticsearch integration dependency
Once you have a cluster running, you can configure edoras one to mirror database content to the elasticsearch index.
The first step is to add the edoras-gear-search
module as a dependency to your project, e.g.:
<dependency>
<groupId>com.edorasware.one</groupId>
<artifactId>edoras-gear-search</artifactId>
<version>${com.edorasware.one.version}</version>
</dependency>
6.9.3. elasticsearch Spring profile
To enable the elasticsearch integration code you also need to add the elasticsearch
Spring profile to
your application environment. Without this profile the elasticsearch integration will not be active.
6.9.4. Configuration properties
The properties used to configure the elasticsearch integration (e.g. to connect to an external elasticsearch cluster) are described in the standard property documentation in the edoras one Operator Guide.
6.9.5. elasticsearch index handling
Before looking at how to configure an index to be mirrored from edoras one, we need some background information on how the edoras one elasticsearch integration manages its indices.
Working indices and aliases
Each index has a public index name (e.g. edoras-objects
) which can be used by application
code to access the index and perform queries, but in fact this index name is just an alias for
a 'real' working index. Working indices have the the same name but with a timestamp appended
(e.g. edoras-objects-20160913111617
). Thus the index structure for an active edoras one index
may look like this:
There may be multiple working index copies (some of which may be inactive) but there will only be one alias.
Initial synchronization and resynchronisation
edoras one will automatically check whether a valid index is available when it is started. If an index is not found then a synchronization process will be started, copying all of the required data from the edoras one database into a new working index. When this synchronization process is complete, the alias will be created so that the new working index may be accessed. In a large database this may mean that it takes some time before the index is available.
Once the synchronization is complete and the index alias has been created, further changes to the edoras one database will automatically be mirrored to elasticsearch.
If for some reason the elasticsearch index is out of step with the edoras one database then a resynchronization can be performed. When resynchronizing, a new working index will be created in the background and the alias will only be switched over to the new index when the synchronisation is complete. Thus the stale index can still be used up to the point where the new index is ready.
To trigger a resynchronization of all indices, the current user should have administrator permissions and access the following URL (relative to the edoras one root context):
/rest/api/v1/maintenance/index/synchronizeAll
A specific index can be resynchronized using the following URL:
/rest/api/v1/maintenance/index/{index}/synchronize
6.9.6. Full index configuration
The edoras-gear-search
module includes a predefined index configuration (FullIndexConfiguration
)
that can be used to create a full index of the edoras one content (work objects, work object attachments
and definitions).
- NOTE
-
Work objects are always indexed as
AnyWorkObject
instances by the full index, the 'real' entity type is not preserved (other than in the work object type field).
To create a full index from an edoras one installation, simply declare the index configuration bean in your project configuration:
<bean id="fullIndex" class="com.edorasware.elastic.index.FullIndexConfiguration"/>
The full index can be configured using properties. Please refer to the standard property documentation in the edoras one Operator Guide for more details.
The full index configuration uses the Mapper attachments plugin to index content attached to work objects so this plugin must be also be installed in your elasticsearch cluster nodes.
There are also two related beans that can be used in conjunction with the full index. The FullIndexSearch
bean
provides useful methods that can be used to execute searches against the full index and return the corresponding
work objects or work object definitions (as AnyWorkObject
or AnyWorkObjectDefinition
respectively). This can be
very useful for implementing your own search REST endpoints:
<bean id="fullIndexSearch" class="com.edorasware.elastic.index.FullIndexSearch"/>
The FullIndexValidator
bean can be used to perform a periodic validation of the elasticsearch index. Any
difference between the elasticsearch index and the edoras one content will be reported to the system log.
Validation of the FullIndexConfiguration
is supported. In the case when you want to make validation of the full index active,
add fullIndexValidator
bean to the spring context:
<bean id="fullIndexValidator" class="com.edorasware.elastic.index.FullIndexValidator"/>
and add a task periodically validate index to the scheduler (e.g. in com/edorasware/cloud/core/config/scheduler-config.xml
).
<task:scheduled-tasks scheduler="taskScheduler"> ... <task:scheduled ref="fullIndexValidator" method="validate" fixed-delay="3600000"/> ... </task:scheduled-tasks>
fullIndexValidator
counts and compares amount of workobjects and workObjectDefinitions in the database and in the full
elasticsearch index. In the case when amounts are not the same warning is logged to the logs.
6.9.7. Custom index configurations
To configure a new elasticsearch index, you will need the following:
-
an elasticsearch template file describing the JSON content of your index
-
a serialization implementation to convert edoras one work objects / definitions into index JSON documents
-
an optional deserializer and DTO class to simplify handling of JSON documents from search results
-
an index configuration defining which edoras one content should be indexed and how this should be done
-
optional utility classes to simplify access to the index (e.g. by providing shared implementations of common searches, returning DTO instances rather than raw JSON documents)
6.9.8. Template file
To define the template file for your custom index, please refer to the {https://www.elastic.co/products/elasticsearch[Elasticsearch]} documentation. An example is provided in the bootstrap project, defining a small number of work object fields that should be indexed:
{
"template": "#{index.name}*",
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"refresh_interval": -1,
"index.mapping.attachment.indexed_chars": -1
},
"mappings": {
"_default_": {
"dynamic": "strict",
"_all": {
"enabled": false
}
},
"indexEntry": {
"properties": {
"id": {
"type": "string",
"index": "not_analyzed"
},
"globalId": {
"type": "string",
"index": "not_analyzed"
},
"name": {
"type": "string",
"index": "analyzed"
},
"type": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
One important point is that the template
attribute contains the placeholder #{index.name}
followed by a *
. The placeholder
will be replaced by the configured index name when the template is loaded, and the *
allows this template to be used
for the working indices which have the index name as prefix and a timestamp suffix. This attribute value can therefore
be used in all index templates.
6.9.9. Index serialization / deserialization
The index configuration will need to convert the incoming work objects and definitions into corresponding JSON documents that match the configured template file. It may also be useful to provide a Java interface for working with the indexed JSON content. There are several ways that this can be achieved, but one possibility is to define a DTO class with the appropriate attributes that can be initialised from an edoras one entity and serialised to JSON using a JSON object mapper:
public class AcmeIndexEntry {
@JsonProperty
private String id;
@JsonProperty
private String globalId;
@JsonProperty
private String type;
@JsonProperty
private String name;
private AcmeIndexEntry() {
// needed for JSON mapper
}
AcmeIndexEntry(Entity<?, ?> entity) {
this.id = entity.getId().getValue();
this.globalId = entity.getGlobalId().getValue();
this.type = entity.getType().getName();
this.name = entity.getName();
}
...
In this case the DTO is simple and only contains fields that are shared between work objects and definitions. We can therefore reuse the same DTO for all index content.
The DTO can also be initialized from a JSON document (as parsed by a JSON object mapper):
AcmeIndexEntry(JsonNode jsonNode) {
this.id = jsonNode.findPath("id").asText();
this.globalId = jsonNode.findPath("globalId").asText();
this.type = jsonNode.findPath("type").asText();
this.name = jsonNode.findPath("name").asText();
}
- NOTE
-
A separate JSON object mapper with qualifier
indexObjectMapper
is provided by the elasticsearch integration code, and should be used for index serialization / de-serialization operations.
6.9.10. Custom index configuration
Custom index configurations can index all of the edoras one database or just a subset, or may index only part of
the information stored in each work object. To define a custom index you just need to create a Spring bean that
implements the IndexConfiguration
interface:
public class AcmeIndexConfiguration implements IndexConfiguration {
static final String INDEX_NAME = "acme";
static final String DOCUMENT_TYPE = "indexEntry";
@Autowired
private DocumentAdapter documentAdapter;
@Autowired
private AcmeEntitySerializer serializer;
// ...
All beans implementing the IndexConfiguration
interface will automatically be located
by the elasticsearch integration code and used to create a corresponding elasticsearch index.
Basic index setup
The IndexConfiguration
interface defines a number of methods. Firstly there are some basic index setup methods
(index name etc.):
@Override
public String getIndexName() {
return INDEX_NAME;
}
@Override
public String getIndexDescription() {
return "Acme sample index configuration";
}
@Override
public Resource getTemplateResource() {
return new ClassPathResource("acme-template.json", AcmeIndexConfiguration.class);
}
@Override
public boolean deleteOnReset() {
// if the database is reset then an existing index will also be deleted
return true;
}
The migrate()
method can be used to patch the template of an existing index if the template is changed at a later
date. This won’t usually update the existing index information (for which a full resynchronization may be required)
but it will allow the existing index to accept new JSON documents without throwing an exception. In our case we only
have one template version so no migration code is required:
@Override
public void migrate() {
// nothing to do
}
Indexing during normal edoras one operation
We now come to the 'normal' indexing methods that are used to mirror changes from edoras one into the index:
@Override
public boolean accept(WorkObject<?, ?, ?> workObject) {
// accept all changes into the index
return true;
}
@Override
public void indexWorkObject(WorkObject<?, ?, ?> workObject) {
String document = this.serializer.serialize(workObject);
this.documentAdapter.indexDocument(
INDEX_NAME, DOCUMENT_TYPE, workObject.getId().getValue(), document);
}
@Override
public void deleteWorkObject(WorkObjectId workObjectId) {
this.documentAdapter.deleteDocument(
INDEX_NAME, DOCUMENT_TYPE, workObjectId.getValue());
}
When a work object is created or modified, the persisted work object will be passed to the accept()
method.
If this method returns true
then the work object will be passed to the indexWorkObject()
method to be
added to the index. This converts the work object to a corresponding JSON document using the serialization code
described above, and adds it to the current index using the document adapter. The document adapter is just
a simple wrapper around the elasticsearch client API provided by the edoras one elasticsearch integration code.
The accept()
method can therefore be used to filter the work objects that are indexed.
Any work object deletions are passed to the deleteWorkObject()
method so that the corresponding index entry can
be removed. No accept()
filter is applied when a work object is deleted.
A corresponding set of methods is provided to support the normal indexing of work object definitions and content manager content.
Resynchronization indexing
The resynchronization indexing process is managed by a different set of methods:
@Override
public Optional<Predicate> getWorkObjectSynchronizationPredicate() {
return Optional.of(Predicates.EMPTY);
}
@Override
public void syncUpdateWorkObject(SyncWriter syncWriter,
String workingIndexName,
WorkObject<?, ?, ?> workObject) {
syncWriter.indexDocument(
workingIndexName,
DOCUMENT_TYPE,
workObject.getId().getValue(),
this.serializer.serialize(workObject));
}
@Override
public void syncDeleteWorkObject(SyncWriter syncWriter,
String workingIndexName,
WorkObjectId workObjectId) {
syncWriter.deleteDocument(work