Chapter 16. Integration
16.1. Overview
Planner’s input and output data (the planning problem and the best solution) are plain old JavaBeans (POJOs), so integration with other Java technologies is straightforward. For example:
- To read a planning problem from the database (and store the best solution in it), annotate the domain POJOs with JPA annotations.
- To read a planning problem from an XML file (and store the best solution in it), annotate the domain POJOs with XStream or JAXB annotations.
-
To expose the Solver as a REST Service that reads the planning problem and responds with the best solution, annotate the domain POJOs with XStream or JAXB annotations and hook the
Solver
in Camel or RESTEasy.
16.2. Persistent Storage
16.2.1. Database: JPA and Hibernate
Enrich the domain POJOs (solution, entities and problem facts) with JPA annotations to store them in a database.
Do not confuse JPA’s @Entity
annotation with Planner’s @PlanningEntity
annotation. They can appear both on the same class:
@PlanningEntity // OptaPlanner annotation @Entity // JPA annotation public class Talk {...}
Add a dependency to the optaplanner-persistence-jpa
JAR to take advantage of these extra integration features:
16.2.1.1. JPA and Hibernate: Persisting a Score
When a Score
is persisted into a relational database, JPA and Hibernate will default to Java serializing it to a BLOB
column. This has several disadvantages:
-
The Java serialization format of
Score
classes is currently not backwards compatible. Upgrading to a newer Planner version can break reading an existing database. - The score is not easily readable for a query executed in the database console. This is annoying during development.
- The score cannot be used in a SQL or JPA-QL query to filter the results: for example to query all infeasible schedules.
To avoid these issues, configure it to use 2 INTEGER
columns instead by using the appropriate *ScoreHibernateType
for your Score
type, for example for a HardSoftScore
:
@PlanningSolution @Entity @TypeDef(defaultForType = HardSoftScore.class, typeClass = HardSoftScoreHibernateType.class) public class CloudBalance implements Solution<HardSoftScore> { @Columns(columns = {@Column(name = "hardScore"), @Column(name = "softScore")}) protected HardSoftScore score; ... }
Configure the same number of @Column
annotations as the number of score levels in the score, otherwise Hibernate will fail fast because a property mapping has the wrong number of columns.
In this case, the DDL will look like this:
CREATE TABLE CloudBalance( ... hardScore INTEGER, softScore INTEGER );
When using a BigDecimal
based Score
, specify the precision and scale of the columns to avoid silent rounding:
@PlanningSolution @Entity @TypeDef(defaultForType = HardSoftBigDecimalScore.class, typeClass = HardSoftBigDecimalScoreHibernateType.class) public class CloudBalance implements Solution<HardSoftBigDecimalScore> { @Columns(columns = { @Column(name = "hardScore", precision = 10, scale = 5), @Column(name = "softScore", precision = 10, scale = 5)}) protected HardSoftBigDecimalScore score; ... }
When using any type of bendable Score
, specify the hard and soft level sizes as parameters:
@PlanningSolution @Entity @TypeDef(defaultForType = BendableScore.class, typeClass = BendableScoreHibernateType.class, parameters = { @Parameter(name = "hardLevelsSize", value = "3"), @Parameter(name = "softLevelsSize", value = "2")}) public class Schedule implements Solution<BendableScore> { @Columns(columns = { @Column(name = "hard0Score"), @Column(name = "hard1Score"), @Column(name = "hard2Score"), @Column(name = "soft0Score"), @Column(name = "soft1Score")}) protected BendableScore score; ... }
All this support is Hibernate specific because currently JPA 2.1’s converters do not support converting to multiple columns.
16.2.1.2. JPA and Hibernate: Planning Cloning
In JPA and Hibernate, there is usually a @ManyToOne
relationship from most problem fact classes to the planning solution class. Therefore, the problem fact classes reference the planning solution class, which implies that when the solution is planning cloned, they need to be cloned too. Use an @DeepPlanningClone
on each such problem fact class to enforce that:
@PlanningSolution // OptaPlanner annotation @Entity // JPA annotation public class Conference { @OneToMany(mappedBy = "conference") private List<Room> roomList; ... }
@DeepPlanningClone // OptaPlanner annotation: Force the default planning cloner to planning clone this class too @Entity // JPA annotation public class Room { @ManyToOne private Conference conference; // Because of this reference, this problem fact needs to be planning cloned too }
Neglecting to do this can lead to persisting duplicate solutions, JPA exceptions or other side effects.
16.2.2. XML or JSON: XStream
Enrich the domain POJOs (solution, entities and problem facts) with XStream annotations to serialize them to/from XML or JSON.
Add a dependency to the optaplanner-persistence-xstream
JAR to take advantage of these extra integration features:
16.2.2.1. XStream: Marshalling a Score
When a Score
is marshalled to XML or JSON by the default XStream configuration, it’s verbose and ugly. To fix that, configure the XStreamScoreConverter
and provide the ScoreDefinition
as a parameter:
@PlanningSolution @XStreamAlias("CloudBalance") public class CloudBalance implements Solution<HardSoftScore> { @XStreamConverter(value = XStreamScoreConverter.class, types = {HardSoftScoreDefinition.class}) private HardSoftScore score; ... }
For example, this will generate a pretty XML:
<CloudBalance> ... <score>0hard/-200soft</score> </CloudBalance>
To use this for any type of bendable score, also provide 2 int
parameters to define hardLevelsSize
and softLevelsSize
:
@PlanningSolution @XStreamAlias("Schedule") public class Schedule implements Solution<BendableScore> { @XStreamConverter(value = XStreamScoreConverter.class, types = {BendableScoreDefinition.class}, ints = {2, 3}) private BendableScore score; ... }
For example, this will generate:
<Schedule> ... <score>0/0/-100/-20/-3</score> </Schedule>
16.2.3. XML or JSON: JAXB
Enrich the domain POJOs (solution, entities and problem facts) with JAXB annotations to serialize them to/from XML or JSON.
16.3. SOA and ESB
16.3.1. Camel and Karaf
Camel is an enterprise integration framework which includes support for Planner (starting from Camel 2.13). It can expose a use case as a REST service, a SOAP service, a JMS service, …
Read the documentation for the camel-optaplanner component. That component works in Karaf too.
16.4. Other Environments
16.4.1. JBoss Modules, WildFly and JBoss EAP
To deploy a Planner web application on WildFly, simply include the optaplanner dependency JARs in the WAR file’s WEB-INF/lib
directory (just like any other dependency) as shown in the optaplanner-webexamples-*.war
. However, in this approach the WAR file can easily grow to several MB in size, which is fine for a one-time deployment, but too heavyweight for frequent redeployments (especially over a slow network connection).
The remedy is to deliver the optaplanner JARs in a JBoss module to WildFly and create a skinny WAR. Let’s create a module called org.optaplanner:
Navigate to the directory
${WILDFLY_HOME}/modules/system/layers/base/
. This directory contains the JBoss modules of WildFly. Create directory structureorg/optaplanner/main
for our new module.-
Copy
optaplanner-core-${version}.jar
and all its direct and transitive dependency JARs into that new directory. Use themvn dependency:tree
command on each optaplanner artifact to discover all dependencies. Create the file
module.xml
in that new directory. Give it this content:<?xml version="1.0" encoding="UTF-8"?> <module xmlns="urn:jboss:module:1.3" name="org.optaplanner"> <resources> ... <resource-root path="kie-api-${version}.jar"/> ... <resource-root path="optaplanner-core-${version}.jar"/> ... <resource-root path="."/> </resources> <dependencies> <module name="javaee.api"/> </dependencies> </module>
-
Copy
Navigate to the deployed WAR file.
-
Remove
optaplanner-core-${version}.jar
and all its direct and transitive dependency JARs from theWEB-INF/lib
directory in the WAR file. Create the file
jboss-deployment-structure.xml
in theWEB-INF/lib
directory. Give it this content:<?xml version="1.0" encoding="UTF-8" ?> <jboss-deployment-structure> <deployment> <dependencies> <module name="org.optaplanner" export="true"/> </dependencies> </deployment> </jboss-deployment-structure>
-
Remove
Because of JBoss Modules' ClassLoader
magic, you’ll likely need to provide the ClassLoader
of your classes during the SolverFactory creation, so it can find the classpath resources (such as the solver config, score DRL’s and domain classes) in your JARs.
16.4.2. OSGi
The optaplanner-core
JAR includes OSGi metadata in its MANIFEST.MF
file to function properly in an OSGi environment too. Furthermore, the Maven artifact drools-karaf-features
(which will be renamed to kie-karaf-features
) contains a features.xml
file that supports the OSGi-feature optaplanner-engine
.
Because of the OSGi’s ClassLoader
magic, you’ll likely need to provide the ClassLoader
of your classes during the SolverFactory creation, so it can find the classpath resources (such as the solver config, score DRLs and domain classes) in your JARs.
Planner does not require OSGi. It works perfectly fine in a normal Java environment too.
16.4.3. Android
Android is not a complete JVM (because some JDK libraries are missing), but Planner works on Android with easy Java or incremental Java score calculation. The Drools rule engine does not work on Android yet, so Drools score calculation doesn’t work on Android and its dependencies need to be excluded.
Workaround to use Planner on Android:
Add a dependency to the
build.gradle
file in your Android project to excludeorg.drools
andxmlpull
dependencies:dependencies { ... compile('org.optaplanner:optaplanner-core:...') { exclude group: 'xmlpull' exclude group: 'org.drools' } ... }
16.5. Integration with Human Planners (Politics)
A good Planner implementation beats any good human planner for non-trivial datasets. Many human planners fail to accept this, often because they feel threatened by an automated system.
But despite that, both can benefit if the human planner acts as supervisor to Planner:
The human planner defines and validates the score function.
-
Some examples expose a
Parametrization
object, which defines the weight for each score constraint. The human planner can then tweak those weights at runtime. - When the business changes, the score function often needs to change too. The human planner can notify the developers to add, change or remove score constraints.
-
Some examples expose a
The human planner is always in control of Planner.
- As shown in the course scheduling example, the human planner can lock 1 or more planning variables to a specific planning value and make those immovable. Because they are immovable, Planner does not change them: it optimizes the planning around the enforcements made by the human. If the human planner locks all planning variables, he/she sidelines Planner completely.
- In a prototype implementation, the human planner might use this occasionally. But as the implementation matures, it must become obsolete. But do keep the feature alive: as a reassurance for the humans. Or in case that one day the business changes dramatically before the score constraints can be adjusted.
Therefore, it’s often a good idea to involve the human planner in your project.