22
33import java .io .IOException ;
44import java .io .Serializable ;
5+ import java .io .StringWriter ;
56import java .util .ArrayList ;
67import java .util .List ;
78
8- import org .maproulette .client .exception .MapRouletteRuntimeException ;
9- import org .maproulette .client .utilities .ObjectMapperSingleton ;
10-
11- import com .fasterxml .jackson .annotation .JsonValue ;
9+ import com .fasterxml .jackson .core .JsonFactory ;
1210import com .fasterxml .jackson .core .JsonGenerator ;
1311import com .fasterxml .jackson .core .JsonParser ;
14- import com .fasterxml .jackson .core .JsonProcessingException ;
1512import com .fasterxml .jackson .databind .DeserializationContext ;
1613import com .fasterxml .jackson .databind .JsonNode ;
14+ import com .fasterxml .jackson .databind .ObjectMapper ;
1715import com .fasterxml .jackson .databind .SerializerProvider ;
1816import com .fasterxml .jackson .databind .annotation .JsonDeserialize ;
1917import com .fasterxml .jackson .databind .annotation .JsonSerialize ;
2422import lombok .Builder ;
2523import lombok .Data ;
2624import lombok .NoArgsConstructor ;
25+ import lombok .NonNull ;
26+ import lombok .extern .slf4j .Slf4j ;
2727
2828/**
2929 * @author mcuthbert
3434@ AllArgsConstructor
3535@ JsonDeserialize (using = RuleList .RuleListDeserializer .class )
3636@ JsonSerialize (using = RuleList .RuleListSerializer .class )
37+ @ Slf4j
3738public class RuleList implements Serializable
3839{
3940 private static final String KEY_CONDITION = "condition" ;
4041 private static final String KEY_RULES = "rules" ;
4142 private static final long serialVersionUID = -1085774480815117637L ;
4243
43- private String condition ;
44- private List < RuleList > ruleList ;
45- private List < PriorityRule > rules ;
44+ @ Builder . Default
45+ @ NonNull
46+ private String condition = "" ;
4647
47- public boolean isSet ()
48- {
49- return this .condition != null && this .rules != null && !this .rules .isEmpty ();
50- }
48+ @ Builder .Default
49+ @ NonNull
50+ private List <RuleList > ruleList = new ArrayList <>();
5151
52- @ JsonValue
53- public String toJson ()
52+ @ Builder .Default
53+ @ NonNull
54+ private List <PriorityRule > rules = new ArrayList <>();
55+
56+ public boolean isSet ()
5457 {
55- try
58+ // A condition is needed
59+ if (this .condition == null || this .condition .isEmpty ())
5660 {
57- return ObjectMapperSingleton . getMapper (). writeValueAsString ( this ) ;
61+ return false ;
5862 }
59- catch (final JsonProcessingException e )
63+ // It is possible to have an empty 'rules' and a non-empty nested rule list.
64+ // It is invalid for both to be empty at the same time.
65+ if (this .rules .isEmpty () && this .ruleList .isEmpty ())
6066 {
61- throw new MapRouletteRuntimeException ( e ) ;
67+ return false ;
6268 }
69+
70+ return true ;
6371 }
6472
6573 /**
@@ -104,8 +112,7 @@ private static void serializeRuleListHelper(final List<RuleList> ruleListList,
104112 }
105113 }
106114
107- @ Override
108- public void serialize (final RuleList value , final JsonGenerator gen ,
115+ private void serializeRuleListAsObject (final RuleList value , final JsonGenerator gen ,
109116 final SerializerProvider serializers ) throws IOException
110117 {
111118 gen .writeStartObject ();
@@ -125,11 +132,46 @@ public void serialize(final RuleList value, final JsonGenerator gen,
125132 }
126133 gen .writeEndArray ();
127134 gen .writeEndObject ();
135+ gen .flush ();
136+ }
137+
138+ /**
139+ * Serialize a RuleList in a format that is compatible with the existing scala backend
140+ * service. The end format must be a json escaped string and not an object. The deserializer
141+ * supports either format: a json string or an object. <br>
142+ * <br>
143+ * For example:
144+ * "highPriorityRule":"{\"condition\":\"AND\",\"rules\":[{\"value\":\"priority_pd.3\",\"type\":\"string\",\"operator\":\"equal\"}]}"
145+ * {@inheritDoc}
146+ */
147+ @ Override
148+ public void serialize (final RuleList value , final JsonGenerator gen ,
149+ final SerializerProvider serializers ) throws IOException
150+ {
151+ // If the RuleList is not "set", write an empty object string.
152+ if (!value .isSet ())
153+ {
154+ gen .writeString ("{}" );
155+ return ;
156+ }
157+
158+ // First create a temporary jsongenerator to hold the serialized nested RuleList object.
159+ final StringWriter stringWriter = new StringWriter ();
160+ final JsonGenerator tempGen = new JsonFactory ().setCodec (gen .getCodec ())
161+ .createGenerator (stringWriter );
162+ serializeRuleListAsObject (value , tempGen , serializers );
163+
164+ // Now get the serialized RuleList as a string and write it as a plain string.
165+ final String ruleListAsString = stringWriter .toString ();
166+ gen .writeString (ruleListAsString );
167+ stringWriter .close ();
168+ tempGen .close ();
128169 }
129170 }
130171
131172 /**
132- * Deserialize a {@code RuleList}
173+ * Deserialize a {@code RuleList}. The serialized format may be either a json escaped string or
174+ * an object.
133175 */
134176 public static class RuleListDeserializer extends StdDeserializer <RuleList >
135177 {
@@ -146,9 +188,11 @@ public RuleListDeserializer(final Class<?> valueClass)
146188 private static RuleList buildRuleListHelper (final JsonNode node ,
147189 final DeserializationContext ctxt )
148190 {
191+ // When the serialized format lacks required values, just create an empty RuleList to
192+ // avoid NPE.
149193 if (node .get ("condition" ) == null )
150194 {
151- return null ;
195+ return RuleList . builder (). build () ;
152196 }
153197 final RuleList ret = RuleList .builder ().condition (node .get ("condition" ).asText ())
154198 .ruleList (new ArrayList <>()).rules (new ArrayList <>()).build ();
@@ -175,12 +219,40 @@ private static RuleList buildRuleListHelper(final JsonNode node,
175219 return ret ;
176220 }
177221
222+ /**
223+ * Deserialize the escaped json string or object into a RuleList. <br>
224+ * <br>
225+ * For example the escaped json string looks like this
226+ * "{\"condition\":\"AND\",\"rules\":[{\"value\":\"priority_pd.3\",\"type\":\"string\",\"operator\":\"equal\"}]}"
227+ * or like this
228+ * {"condition":"AND","rules":[{"value":"priority_pd.3","type":"string","operator":"equal"}]}
229+ * {@inheritDoc}
230+ */
178231 @ Override
179232 public RuleList deserialize (final JsonParser jsonParser , final DeserializationContext ctxt )
180233 throws IOException
181234 {
182- final JsonNode node = jsonParser .getCodec ().readTree (jsonParser );
183- return buildRuleListHelper (node , ctxt );
235+ // The json could be an escaped string representation or an object representation of a
236+ // RuleList
237+ final JsonNode tree = jsonParser .readValueAsTree ();
238+
239+ if (tree .isContainerNode ())
240+ {
241+ // It's an object representation, pass it on for parsing
242+ return buildRuleListHelper (tree , ctxt );
243+ }
244+ else
245+ {
246+ // First read out the string which is the escaped json of the RuleList
247+ final String ruleListString = jsonParser .getCodec ().treeToValue (tree , String .class );
248+
249+ // Take the string and convert it to a JsonNode
250+ final JsonNode ruleListNode = ((ObjectMapper ) jsonParser .getCodec ())
251+ .readTree (ruleListString );
252+
253+ // Recursively parse the node tree
254+ return buildRuleListHelper (ruleListNode , ctxt );
255+ }
184256 }
185257 }
186258}
0 commit comments