Skip to content

Commit 3968d55

Browse files
author
Michael Cuthbert
authored
Merge pull request #73 from osmlab/ruleListJacksonSerializers
Update RuleList 'rules' to support expected types
2 parents 6174186 + 41ecefb commit 3968d55

13 files changed

+373
-20
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ dependencies
3636
implementation packages.lombok
3737
annotationProcessor packages.lombok
3838

39+
testImplementation packages.slf4j.simple
3940
testImplementation packages.junit
4041
testImplementation packages.mockito
4142

43+
integrationTestImplementation packages.slf4j.simple
4244
integrationTestImplementation packages.junit
4345

4446
checkstyle packages.checkstyle

src/integrationTest/java/org/maproulette/client/IntegrationBase.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
public class IntegrationBase
1515
{
1616
public static final String DEFAULT_PROJECT_NAME = "IntegrationTestProject1";
17+
public static final String ENVIRONMENT_SCHEME = "scheme";
1718
public static final String ENVIRONMENT_HOST = "host";
1819
public static final String ENVIRONMENT_PORT = "port";
1920
public static final String ENVIRONMENT_API_KEY = "apiKey";
@@ -26,6 +27,7 @@ public class IntegrationBase
2627
.build();
2728
private long defaultProjectIdentifier = -1;
2829
private long projectIdentifier = -1;
30+
private String scheme;
2931
private String host;
3032
private int port;
3133
private String apiKey;
@@ -114,14 +116,20 @@ public MapRouletteConfiguration getConfiguration()
114116
if (this.configuration == null)
115117
{
116118
this.configurationParamsSetUp();
117-
this.configuration = new MapRouletteConfiguration(this.host, this.port,
119+
this.configuration = new MapRouletteConfiguration(this.scheme, this.host, this.port,
118120
DEFAULT_PROJECT_NAME, this.apiKey);
119121
}
120122
return this.configuration;
121123
}
122124

123125
private void configurationParamsSetUp()
124126
{
127+
this.scheme = System.getenv(ENVIRONMENT_SCHEME);
128+
if (StringUtils.isEmpty(this.scheme))
129+
{
130+
this.scheme = "https";
131+
}
132+
125133
this.host = System.getenv(ENVIRONMENT_HOST);
126134
if (StringUtils.isEmpty(this.host))
127135
{

src/integrationTest/java/org/maproulette/client/batch/BatchUploaderIntegrationTest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@
1212
import org.maproulette.client.model.Challenge;
1313
import org.maproulette.client.model.Project;
1414
import org.maproulette.client.model.Task;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
1517

1618
/**
1719
* @author mcuthbert
1820
*/
1921
public class BatchUploaderIntegrationTest extends IntegrationBase
2022
{
23+
private static final Logger LOG = LoggerFactory.getLogger(BatchUploaderIntegrationTest.class);
2124
private static final int NUMBER_PROJECTS = 4;
2225
private static final int NUMBER_CHALLENGES = 3;
2326
private static final int NUMBER_TASKS = 10;
@@ -83,14 +86,17 @@ public void multipleProjectBatchTest() throws MapRouletteException
8386
final var projectIdentifier = this.getProjectAPI()
8487
.create(Project.builder().name(prefix + projectIndex).build()).getId();
8588
projectList.add(projectIdentifier);
89+
LOG.debug("Starting task add for project id={}", projectIdentifier);
8690
this.addDefaultTasks(batchUploader, projectIdentifier);
8791
}
8892
batchUploader.flushAll();
8993

94+
Assertions.assertEquals(projectList.size(), NUMBER_PROJECTS);
95+
9096
projectList.forEach(throwingConsumerWrapper(identifier ->
9197
{
9298
final var children = this.getProjectAPI().children(identifier, NUMBER_TASKS, 0);
93-
Assertions.assertEquals(NUMBER_PROJECTS, children.size());
99+
Assertions.assertEquals(NUMBER_CHALLENGES, children.size());
94100
children.forEach(throwingConsumerWrapper(child ->
95101
{
96102
final var tasks = this.getChallengeAPI().children(child.getId(), NUMBER_TASKS, 0);
@@ -111,6 +117,7 @@ private void addDefaultTasks(final BatchUploader uploader, final long parentIden
111117
for (int taskIndex = 0; taskIndex < NUMBER_TASKS; taskIndex++)
112118
{
113119
uploader.addTask(newChallenge, this.getDefaultTask(-1, "Task" + taskIndex));
120+
LOG.debug("Added task name=Task{} parentId={} challengeName={}", taskIndex, parentIdentifier, newChallenge.getName());
114121
}
115122
}
116123
}
Lines changed: 136 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
package org.maproulette.client.model;
22

3-
import java.io.Serializable;
4-
import java.util.List;
5-
6-
import org.maproulette.client.exception.MapRouletteRuntimeException;
7-
83
import com.fasterxml.jackson.annotation.JsonValue;
4+
import com.fasterxml.jackson.core.JsonGenerator;
5+
import com.fasterxml.jackson.core.JsonParser;
96
import com.fasterxml.jackson.core.JsonProcessingException;
10-
import com.fasterxml.jackson.databind.ObjectMapper;
11-
7+
import com.fasterxml.jackson.databind.DeserializationContext;
8+
import com.fasterxml.jackson.databind.JsonNode;
9+
import com.fasterxml.jackson.databind.SerializerProvider;
10+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
11+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
12+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
13+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
1214
import lombok.AllArgsConstructor;
1315
import lombok.Builder;
1416
import lombok.Data;
1517
import lombok.NoArgsConstructor;
18+
import org.maproulette.client.exception.MapRouletteRuntimeException;
19+
import org.maproulette.client.utilities.ObjectMapperSingleton;
20+
21+
import java.io.IOException;
22+
import java.io.Serializable;
23+
import java.util.ArrayList;
24+
import java.util.List;
1625

1726
/**
1827
* @author mcuthbert
@@ -21,13 +30,16 @@
2130
@Data
2231
@NoArgsConstructor
2332
@AllArgsConstructor
33+
@JsonDeserialize(using = RuleList.RuleListDeserializer.class)
34+
@JsonSerialize(using = RuleList.RuleListSerializer.class)
2435
public class RuleList implements Serializable
2536
{
2637
private static final String KEY_CONDITION = "condition";
2738
private static final String KEY_RULES = "rules";
2839
private static final long serialVersionUID = -1085774480815117637L;
2940

3041
private String condition;
42+
private List<RuleList> ruleList;
3143
private List<PriorityRule> rules;
3244

3345
public boolean isSet()
@@ -38,16 +50,128 @@ public boolean isSet()
3850
@JsonValue
3951
public String toJson()
4052
{
41-
final var mapper = new ObjectMapper();
4253
try
4354
{
44-
return String.format("{\"%s\":\"%s\",\"%s\":%s}", KEY_CONDITION, this.getCondition(),
45-
KEY_RULES, mapper.writeValueAsString(this.getRules()));
46-
}
47-
catch (final JsonProcessingException e)
55+
return ObjectMapperSingleton.getMapper().writeValueAsString(this);
56+
} catch (final JsonProcessingException e)
4857
{
4958
throw new MapRouletteRuntimeException(e);
5059
}
60+
}
61+
62+
public static class RuleListSerializer extends StdSerializer<RuleList>
63+
{
64+
65+
public RuleListSerializer()
66+
{
67+
this(null);
68+
}
5169

70+
public RuleListSerializer(Class<RuleList> t)
71+
{
72+
super(t);
73+
}
74+
75+
private static void serializeRuleListHelper(List<RuleList> ruleList, JsonGenerator gen) throws IOException
76+
{
77+
for (RuleList r : ruleList)
78+
{
79+
gen.writeStartObject();
80+
gen.writeStringField("condition", r.getCondition());
81+
gen.writeArrayFieldStart("rules");
82+
if (r.getRuleList() != null)
83+
{
84+
for (RuleList it : r.getRuleList())
85+
{
86+
serializeRuleListHelper(it.getRuleList(), gen);
87+
}
88+
}
89+
if (r.getRules() != null)
90+
{
91+
for (PriorityRule p : r.getRules())
92+
{
93+
gen.writeObject(p);
94+
}
95+
}
96+
gen.writeEndArray();
97+
gen.writeEndObject();
98+
}
99+
}
100+
101+
@Override
102+
public void serialize(RuleList value, JsonGenerator gen, SerializerProvider serializers) throws IOException
103+
{
104+
gen.writeStartObject();
105+
gen.writeStringField("condition", value.getCondition());
106+
gen.writeArrayFieldStart("rules");
107+
if (value.getRuleList() != null)
108+
{
109+
serializeRuleListHelper(value.getRuleList(), gen);
110+
}
111+
112+
if (value.getRules() != null)
113+
{
114+
for (PriorityRule p : value.getRules())
115+
{
116+
gen.writeObject(p);
117+
}
118+
}
119+
gen.writeEndArray();
120+
gen.writeEndObject();
121+
}
122+
}
123+
124+
public static class RuleListDeserializer extends StdDeserializer<RuleList>
125+
{
126+
127+
public RuleListDeserializer()
128+
{
129+
this(null);
130+
}
131+
132+
public RuleListDeserializer(final Class<?> vc)
133+
{
134+
super(vc);
135+
}
136+
137+
private static RuleList buildRuleListHelper(JsonNode node, DeserializationContext ctxt)
138+
{
139+
if (node.get("condition") == null)
140+
{
141+
return null;
142+
}
143+
final RuleList ret = RuleList.builder()
144+
.condition(node.get("condition").asText())
145+
.ruleList(new ArrayList<>())
146+
.rules(new ArrayList<>())
147+
.build();
148+
149+
for (JsonNode it : node.withArray("rules"))
150+
{
151+
if (it.get("condition") != null)
152+
{
153+
// If the child is a PriorityRule, do a recursive call to build the rule and add it to the list.
154+
RuleList child = buildRuleListHelper(it, ctxt);
155+
ret.getRuleList().add(child);
156+
} else
157+
{
158+
PriorityRule priorityRule = PriorityRule.builder()
159+
.type(it.get("type").asText())
160+
.operator(it.get("operator").asText())
161+
.value(it.get("value").asText())
162+
.build();
163+
ret.getRules().add(priorityRule);
164+
}
165+
}
166+
167+
return ret;
168+
}
169+
170+
@Override
171+
public RuleList deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
172+
{
173+
JsonNode node = p.getCodec().readTree(p);
174+
return buildRuleListHelper(node, ctxt);
175+
}
52176
}
53177
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.maproulette.client.utilities;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.fasterxml.jackson.databind.module.SimpleModule;
5+
import org.maproulette.client.model.RuleList;
6+
7+
public class ObjectMapperSingleton
8+
{
9+
private static volatile ObjectMapper mapper;
10+
11+
public static ObjectMapper getMapper()
12+
{
13+
if (mapper == null)
14+
{
15+
synchronized (ObjectMapper.class)
16+
{
17+
if (mapper == null)
18+
{
19+
mapper = new ObjectMapper();
20+
SimpleModule module = new SimpleModule();
21+
module.addSerializer(RuleList.class, new RuleList.RuleListSerializer());
22+
module.addDeserializer(RuleList.class, new RuleList.RuleListDeserializer());
23+
mapper.registerModule(module);
24+
}
25+
}
26+
}
27+
28+
return mapper;
29+
}
30+
}

src/main/java/org/maproulette/client/utilities/Utilities.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package org.maproulette.client.utilities;
22

3-
import java.io.IOException;
4-
53
import org.maproulette.client.exception.MapRouletteException;
64

7-
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import java.io.IOException;
86

97
/**
108
* A generic helper class
@@ -18,8 +16,7 @@ public static <T> T fromJson(final String json, final Class<T> clazz)
1816
{
1917
try
2018
{
21-
final var mapper = new ObjectMapper();
22-
return mapper.readValue(json, clazz);
19+
return ObjectMapperSingleton.getMapper().readValue(json, clazz);
2320
}
2421
catch (final IOException e)
2522
{

src/test/java/org/maproulette/client/serializer/ChallengeSerializationTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.maproulette.client.serializer;
22

33
import java.io.IOException;
4+
import java.util.ArrayList;
45
import java.util.Collections;
56

67
import org.junit.jupiter.api.Assertions;
@@ -13,6 +14,7 @@
1314
import org.maproulette.client.model.RuleList;
1415

1516
import com.fasterxml.jackson.databind.ObjectMapper;
17+
import org.maproulette.client.utilities.ObjectMapperSingleton;
1618

1719
/**
1820
* Tests whether a challenge can be read correctly from resources.
@@ -24,7 +26,7 @@ public class ChallengeSerializationTest
2426
private static final String DESCRIPTION = "DESCRIPTION";
2527
private static final String BLURB = "BLURB";
2628
private static final String INSTRUCTION = "INSTRUCTION";
27-
private final ObjectMapper mapper = new ObjectMapper();
29+
private final ObjectMapper mapper = ObjectMapperSingleton.getMapper();
2830

2931
@Test
3032
public void fullSerializationTest() throws IOException
@@ -147,10 +149,12 @@ public void serializationTest() throws Exception
147149
final var highPriority = RuleList.builder().condition("AND")
148150
.rules(Collections.singletonList(PriorityRule.builder().operator("equal")
149151
.type("string").value("priority_pd.3").build()))
152+
.ruleList(new ArrayList<>())
150153
.build();
151154
final var mediumPriority = RuleList.builder().condition("OR")
152155
.rules(Collections.singletonList(PriorityRule.builder().operator("equal")
153156
.type("string").value("priority_pd.2").build()))
157+
.ruleList(new ArrayList<>())
154158
.build();
155159

156160
Assertions.assertEquals(DESCRIPTION, deserializedChallenge.getDescription());

0 commit comments

Comments
 (0)