The component receives a serialized message
as a string and a serialized schema
as a string. The message
contains key-value pairs or just the values. The schema
describes how to turn that message
into a Java object. Firstly, we parse the schema
into a Java object (schema format is known in advance). There is an enum
with possible data providers
, which imply the message
format (it can be XML, JSON, or something custom). So, the schema
contains this enum
value as well as some additional information (like the keys if the message
only has the values, so we can later map already known key names to values and we don't need to send the keys every time over the wire).
Now, that's how it works:
// Get the schema serialization format.
String schemaFormat = "XML";
// Get the schema.
Serialized schemaSerialized = new Serialized(
"<DataProvider format=\"FIXED_LENGTH_STRING\">\n" +
" <variable name=\"agentId\" len=\"5\"/>\n" +
" <variable name=\"callsPerDay\" len=\"3\"/>\n" +
"</DataProvider>");
// Parse the schema.
Schema schemaAbstract = Deserializer.deserializeSchema(
schemaSerialized,
FormatSchema.valueOf(schemaFormat));
// Downcast the schema to its real type.
XMLSchema schema = (XMLSchema) schemaAbstract;
// Get the data from the wire. metric1 | metric2
Serialized msgSerialized = new Serialized("12230015");
// Deserialize the data.
Message msg = Deserializer.deserializeMessage(msgSerialized, schema);
// Serialize the message into JSON
String msgJson= msg.toJSONString();
Result: msgJson is now: {"Metrics":{"agentId":"12230","callsPerDay":"15"}}
Now, I'll explain the classes briefly (too much code to fit here):
class
Serialized: Has a String
private field, sets in the constructor, there is a getter.
interface
Deserialized extends
Serializable: just an empty interface. Should I implement Serializable? I don't really know much about it, just seems appropriate.
interface
Schema extends
Deserialized: represents an abstract Java Object for any kind of schema
. Has a getter for FormatMessage enum
value.
interface
Message extends
Deserialized: represents an abstract Java Object for any kind of message
. Has a toJSONString() method.
interface
Parser: empty. Because for a message I need 2 parameters, for schema one.
interface
ParserMessage extends
Parser: has a method Message parse(Serialized, Schema)
interface
ParserSchema extends
Parser: has a method Schema parse(Serialized)
interface
Format: empty
enum
FormatMessage implements
Format
enum
FormatSchema implements
Format
For each FormatMessage value, there is a concrete class for Message and ParserMessage. For each FormatSchema value, there is a concrete class for Schema and ParserSchema. Obviously, ParserSchema returns its corresponding Schema and the same with messages.
Now, the main part:
class
Deserializer: has 2 methods: deserializeMessage
and deserializeSchema
. For both he accepts a Format (for message in a form of schema). Inside those two methods this class uses a Strategy pattern
by calling methods named setParserMessage
and setParserSchema
. Inside those methods, there is a call to a ParserFactory which holds all the concrete Parser static instances and gives away references to them by switch-casing
by the given Format on the appropriate enum
.
The problem is that I'm afraid that the Deserializer class abstracts everything too much that I have to cast a lot, even the caller has to cast a little. My team accepted this design, but I just want to make sure :)
UPD: The code for Deserializer
and the ParserFactory
as requested.
/**
* Used to deserialize schemas and the corresponding to them messages.
* Abstracts all the Parser implementations and uses the Strategy design pattern to switch between them based on the Format using the ParserFactory.
*/
public class Deserializer {
private static ParserMessage parserMessage;
private static ParserSchema parserSchema;
private Deserializer() {} // Imitating a static class
/**
* Deserializes a schema into the concrete Schema object.
* The returned object can be later used to deserialize the actual messages sent by the corresponding provider.
*
* @param schema a serialized schema
* @param schemaFormat the serialization type of the schema
* @return this schema as a Java object
*/
public static Schema deserializeSchema(Serialized schema,
FormatSchema schemaFormat
) {
setParserSchema(schemaFormat); // Strategy pattern
return parserSchema.parse(schema);
}
/**
* Deserializes a message into the concrete Message object.
*
* @param msg a serialized message
* @param schema the schema as a Java object
* @return this message as a Java object
*/
public static Message deserializeMessage(Serialized msg,
Schema schema
) {
setParserMessage(schema.getFormatMessage()); // Strategy pattern
return parserMessage.parse(msg, schema);
}
private static void setParserMessage(FormatMessage msgFormat) { // Strategy pattern
parserMessage = ParserFactory.getParserMessage(msgFormat); // Factory pattern
}
private static void setParserSchema(FormatSchema configFileFormat) { // Strategy pattern
parserSchema = ParserFactory.getParserSchema(configFileFormat); // Factory pattern
}
}
/**
* Abstracts all the Parser implementations using the Factory design pattern.
* Defines a Factory that gives away the reference at the Parser implementation instance based on the given Format implementation instance (which is an enum option value).
* <p>
* The factory contains a lazy-initialized static instance of each currently available Parser implementation.
*/
public class ParserFactory {
private static final XMLSchemaParser XML_SCHEMA_PARSER = new XMLSchemaParser();
private static final FIXED_LENGTH_STRINGMessageParser FIXED_LENGTH_STRING_MESSAGE_PARSER = new FIXED_LENGTH_STRINGMessageParser();
private ParserFactory() {} // imitating a static class
/**
* Returns the ParserMessage instance for parsing messages of specific FormatMessage.
*
* @param msgFormat the format of the acquired Serialized message
* @return a reference at the corresponding static ParserMessage instance. Null if not found
* @see FormatMessage All message formats
*/
public static ParserMessage getParserMessage(FormatMessage msgFormat) {
switch (msgFormat) {
case FIXED_LENGTH_STRING:
return getFixedLengthStringMessageParserInstance();
default:
return null;
}
}
/**
* Returns the ParserSchema instance for parsing schemas of specific FormatSchema.
*
* @param schemaFormat format of the Serialized schema
* @return reference at the corresponding static ParserSchema instance. Null if not found
* @see FormatSchema All schema formats
*/
public static ParserSchema getParserSchema(FormatSchema schemaFormat) {
switch (schemaFormat) {
case XML:
return getXmlSchemaParser();
default:
return null;
}
}
private static XMLSchemaParser getXmlSchemaParser() {
return XML_SCHEMA_PARSER;
}
private static FixedLengthStringMessageParser getFixedLengthStringMessageParserInstance() {
return FIXED_LENGTH_STRING_MESSAGE_PARSER;
}
}