Skip to content

Commit

Permalink
PLANNER-1041 Jackson support for non-subtyped Score field + jackson m…
Browse files Browse the repository at this point in the history
…odule
  • Loading branch information
ge0ffrey committed Mar 14, 2018
1 parent 2fbae85 commit 348069a
Show file tree
Hide file tree
Showing 38 changed files with 923 additions and 40 deletions.
Expand Up @@ -28,7 +28,7 @@ image::Integration/integrationOverview.png[align="center"]
[[integrationWithJpaAndHibernate]]
=== Database: JPA and Hibernate

Enrich the domain POJO's (solution, entities and problem facts) with JPA annotations
Enrich domain POJO's (solution, entities and problem facts) with JPA annotations
to store them in a database by calling `EntityManager.persist()`.

[NOTE]
Expand Down Expand Up @@ -189,7 +189,7 @@ Neglecting to do this can lead to persisting duplicate solutions, JPA exceptions
[[integrationWithXStream]]
=== XML or JSON: XStream

Enrich the domain POJO's (solution, entities and problem facts) with XStream annotations to serialize them to/from XML or JSON.
Enrich domain POJO's (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:

Expand Down Expand Up @@ -256,7 +256,7 @@ The `hardLevelsSize` and `softLevelsSize` implied, when reading a bendable score
[[integrationWithJaxb]]
=== XML or JSON: JAXB

Enrich the domain POJO's (solution, entities and problem facts) with JAXB annotations to serialize them to/from XML or JSON.
Enrich domain POJO's (solution, entities and problem facts) with JAXB annotations to serialize them to/from XML or JSON.

Add a dependency to the `optaplanner-persistence-jaxb` jar to take advantage of these extra integration features:

Expand Down Expand Up @@ -323,70 +323,101 @@ The `hardLevelsSize` and `softLevelsSize` implied, when reading a bendable score
[[integrationWithJackson]]
=== JSON: Jackson

Enrich the domain POJO's (solution, entities and problem facts) with Jackson annotations to serialize them to/from JSON.
Enrich domain POJO's (solution, entities and problem facts) with Jackson annotations to serialize them to/from JSON.

Add a dependency to the `optaplanner-persistence-jackson` jar to take advantage of these extra integration features:
Add a dependency to the `optaplanner-persistence-jackson` jar and register `OptaPlannerJacksonModule`:

[source,java,options="nowrap"]
----
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(OptaPlannerJacksonModule.createModule());
----


[[jacksonMarshallingAScore]]
==== JAXB: Marshalling a `Score`

When a `Score` is marshalled to JSON by the default Jackson configuration, it fails.
To fix that, configure a `ScoreJacksonJsonSerializer` and the appropriate ``ScoreJacksonJsonDeserializer``:
When a `Score` is marshalled to/from JSON by the default Jackson configuration, it fails.
The `OptaPlannerJacksonModule` fixes that, by using `HardSoftScoreJacksonJsonSerializer`,
HardSoftScoreJacksonJsonDeserializer`, etc.

[source,java,options="nowrap"]
----
@PlanningSolution
public class CloudBalance {
@PlanningScore
@JsonSerialize(using = ScoreJacksonJsonSerializer.class)
@JsonDeserialize(using = HardSoftScoreJacksonJsonDeserializer.class)
private HardSoftScore score;
...
}
----

For example, this will generate pretty JSON:
For example, this will generate this JSON:

[source,json]
----
{
...
"score":"0hard/-200soft"
...
}
----

The same applies for a bendable score:
[NOTE]
====
When reading a `BendableScore`, the `hardLevelsSize` and `softLevelsSize` implied in the JSON element,
must always be in sync with those defined in the `@PlanningScore` annotation in the solution class. For example:
[source,json]
----
{
"score":"[0/0]hard/[-100/-20/-3]soft"
...
}
----
This JSON implies the `hardLevelsSize` is 2 and thet `softLevelsSize` is 3,
which must be in sync with the `@PlanningScore` annotation:
[source,java,options="nowrap"]
----
@PlanningSolution
public class Schedule {
@PlanningScore
@JsonSerialize(using = ScoreJacksonJsonSerializer.class)
@JsonDeserialize(using = BendableScoreJacksonXmlAdapter.class)
@PlanningScore(bendableHardLevelsSize = 2, bendableSoftLevelsSize = 3)
private BendableScore score;
...
}
----
====

For example, with a `hardLevelsSize` of `2` and a `softLevelsSize` of ``3``, that will generate:
When a field is the `Score` supertype (instead of a specific type such as `HardSoftScore`),
`PolymorphicScoreJacksonJsonSerializer` and `PolymorphicScoreJacksonJsonDeserializer` are used
to record the score type in the json, so it can be deserialized too:

[source,java,options="nowrap"]
----
@PlanningSolution
public class CloudBalance {
@PlanningScore
private Score score;
...
}
----

For example, this will generate this JSON:

[source,json]
----
{
"score":{"HardSoftScore":"0hard/-200soft"}
...
"score":"[0/0]hard/[-100/-20/-3]soft"
}
----

The `hardLevelsSize` and `softLevelsSize` implied, when reading a bendable score from a JSON element, must always be in sync with those in the solver.


[[integrationWithSoaAndEsb]]
== SOA and ESB

Expand Down
@@ -0,0 +1,120 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.optaplanner.persistence.jackson.api;

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.score.buildin.bendable.BendableScore;
import org.optaplanner.core.api.score.buildin.bendablebigdecimal.BendableBigDecimalScore;
import org.optaplanner.core.api.score.buildin.bendablelong.BendableLongScore;
import org.optaplanner.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore;
import org.optaplanner.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore;
import org.optaplanner.core.api.score.buildin.hardsoftdouble.HardSoftDoubleScore;
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
import org.optaplanner.core.api.score.buildin.simple.SimpleScore;
import org.optaplanner.core.api.score.buildin.simplebigdecimal.SimpleBigDecimalScore;
import org.optaplanner.core.api.score.buildin.simpledouble.SimpleDoubleScore;
import org.optaplanner.core.api.score.buildin.simplelong.SimpleLongScore;
import org.optaplanner.persistence.jackson.api.score.PolymorphicScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.PolymorphicScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.ScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.bendable.BendableScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.bendable.BendableScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.bendablebigdecimal.BendableBigDecimalScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.bendablebigdecimal.BendableBigDecimalScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.bendablelong.BendableLongScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.bendablelong.BendableLongScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardmediumsoft.HardMediumSoftScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardmediumsoft.HardMediumSoftScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardsoft.HardSoftScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardsoft.HardSoftScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardsoftdouble.HardSoftDoubleScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardsoftdouble.HardSoftDoubleScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardsoftlong.HardSoftLongScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.hardsoftlong.HardSoftLongScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.simple.SimpleScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.simple.SimpleScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.simplebigdecimal.SimpleBigDecimalScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.simplebigdecimal.SimpleBigDecimalScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.simpledouble.SimpleDoubleScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.simpledouble.SimpleDoubleScoreJacksonJsonSerializer;
import org.optaplanner.persistence.jackson.api.score.buildin.simplelong.SimpleLongScoreJacksonJsonDeserializer;
import org.optaplanner.persistence.jackson.api.score.buildin.simplelong.SimpleLongScoreJacksonJsonSerializer;

/**
* This class adds all Jackson serializers and deserializers.
*
*/
public class OptaPlannerJacksonModule {

/**
* @return never null, register it with {@link ObjectMapper#registerModule(Module)}.
*/
public static Module createModule() {
SimpleModule module = new SimpleModule("OptaPlanner");

// For non-subtype Score fields/properties, we also need to record the score type
module.addSerializer(Score.class, new PolymorphicScoreJacksonJsonSerializer());
module.addDeserializer(Score.class, new PolymorphicScoreJacksonJsonDeserializer());

module.addSerializer(SimpleScore.class, new SimpleScoreJacksonJsonSerializer());
module.addDeserializer(SimpleScore.class, new SimpleScoreJacksonJsonDeserializer());
module.addSerializer(SimpleLongScore.class, new SimpleLongScoreJacksonJsonSerializer());
module.addDeserializer(SimpleLongScore.class, new SimpleLongScoreJacksonJsonDeserializer());
module.addSerializer(SimpleDoubleScore.class, new SimpleDoubleScoreJacksonJsonSerializer());
module.addDeserializer(SimpleDoubleScore.class, new SimpleDoubleScoreJacksonJsonDeserializer());
module.addSerializer(SimpleBigDecimalScore.class, new SimpleBigDecimalScoreJacksonJsonSerializer());
module.addDeserializer(SimpleBigDecimalScore.class, new SimpleBigDecimalScoreJacksonJsonDeserializer());
module.addSerializer(HardSoftScore.class, new HardSoftScoreJacksonJsonSerializer());
module.addDeserializer(HardSoftScore.class, new HardSoftScoreJacksonJsonDeserializer());
module.addSerializer(HardSoftLongScore.class, new HardSoftLongScoreJacksonJsonSerializer());
module.addDeserializer(HardSoftLongScore.class, new HardSoftLongScoreJacksonJsonDeserializer());
module.addSerializer(HardSoftDoubleScore.class, new HardSoftDoubleScoreJacksonJsonSerializer());
module.addDeserializer(HardSoftDoubleScore.class, new HardSoftDoubleScoreJacksonJsonDeserializer());
module.addSerializer(HardSoftBigDecimalScore.class, new HardSoftBigDecimalScoreJacksonJsonSerializer());
module.addDeserializer(HardSoftBigDecimalScore.class, new HardSoftBigDecimalScoreJacksonJsonDeserializer());
module.addSerializer(HardMediumSoftScore.class, new HardMediumSoftScoreJacksonJsonSerializer());
module.addDeserializer(HardMediumSoftScore.class, new HardMediumSoftScoreJacksonJsonDeserializer());
module.addSerializer(HardMediumSoftLongScore.class, new HardMediumSoftLongScoreJacksonJsonSerializer());
module.addDeserializer(HardMediumSoftLongScore.class, new HardMediumSoftLongScoreJacksonJsonDeserializer());
module.addSerializer(BendableScore.class, new BendableScoreJacksonJsonSerializer());
module.addDeserializer(BendableScore.class, new BendableScoreJacksonJsonDeserializer());
module.addSerializer(BendableLongScore.class, new BendableLongScoreJacksonJsonSerializer());
module.addDeserializer(BendableLongScore.class, new BendableLongScoreJacksonJsonDeserializer());
module.addSerializer(BendableBigDecimalScore.class, new BendableBigDecimalScoreJacksonJsonSerializer());
module.addDeserializer(BendableBigDecimalScore.class, new BendableBigDecimalScoreJacksonJsonDeserializer());

return module;
}

}
@@ -0,0 +1,20 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Jackson binders.
*/
package org.optaplanner.persistence.jackson.api;
@@ -0,0 +1,99 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.optaplanner.persistence.jackson.api.score;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.score.buildin.bendable.BendableScore;
import org.optaplanner.core.api.score.buildin.bendablebigdecimal.BendableBigDecimalScore;
import org.optaplanner.core.api.score.buildin.bendablelong.BendableLongScore;
import org.optaplanner.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore;
import org.optaplanner.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore;
import org.optaplanner.core.api.score.buildin.hardsoftdouble.HardSoftDoubleScore;
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
import org.optaplanner.core.api.score.buildin.simple.SimpleScore;
import org.optaplanner.core.api.score.buildin.simplebigdecimal.SimpleBigDecimalScore;
import org.optaplanner.core.api.score.buildin.simpledouble.SimpleDoubleScore;
import org.optaplanner.core.api.score.buildin.simplelong.SimpleLongScore;
import org.optaplanner.core.api.score.constraint.ConstraintMatch;
import org.optaplanner.core.impl.score.definition.ScoreDefinition;

/**
* {@inheritDoc}
*/
public class PolymorphicScoreJacksonJsonDeserializer extends JsonDeserializer<Score> {

@Override
public Score deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
Iterator<Map.Entry<String, JsonNode>> iterator = node.fields();
if (!iterator.hasNext()) {
throw new IllegalStateException("A polymorphic score field's JSON node (" + node.toString()
+ ") doesn't have any field.");
}
Map.Entry<String, JsonNode> entry = iterator.next();
if (iterator.hasNext()) {
throw new IllegalStateException("A polymorphic score field's JSON node (" + node.toString()
+ ") has multiple fields.");
}
String scoreClassSimpleName = entry.getKey();
String scoreString = entry.getValue().asText();
if (scoreClassSimpleName.equals(SimpleScore.class.getSimpleName())) {
return SimpleScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(SimpleLongScore.class.getSimpleName())) {
return SimpleLongScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(SimpleDoubleScore.class.getSimpleName())) {
return SimpleDoubleScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(SimpleBigDecimalScore.class.getSimpleName())) {
return SimpleBigDecimalScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(HardSoftScore.class.getSimpleName())) {
return HardSoftScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(HardSoftLongScore.class.getSimpleName())) {
return HardSoftLongScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(HardSoftDoubleScore.class.getSimpleName())) {
return HardSoftDoubleScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(HardSoftBigDecimalScore.class.getSimpleName())) {
return HardSoftBigDecimalScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(HardMediumSoftScore.class.getSimpleName())) {
return HardMediumSoftScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(HardMediumSoftLongScore.class.getSimpleName())) {
return HardMediumSoftLongScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(BendableScore.class.getSimpleName())) {
return BendableScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(BendableLongScore.class.getSimpleName())) {
return BendableLongScore.parseScore(scoreString);
} else if (scoreClassSimpleName.equals(BendableBigDecimalScore.class.getSimpleName())) {
return BendableBigDecimalScore.parseScore(scoreString);
} else {
throw new IllegalArgumentException("Unrecognized scoreClassSimpleName (" + scoreClassSimpleName
+ ") for scoreString (" + scoreString + ").");
}
}

}

0 comments on commit 348069a

Please sign in to comment.