问题
I am using Mule 3.5.2 and I have a REST service that send and receives JSON messages. The service is for both Norway and Sweden. All dates are send as strings, but Sweden and Norway have different formats. I know by the URL which country calls our service. I am using custom date serializers and deserializers.
I can kind of cheat when receiving JSON message, the formats are different enough that in my custom deserializer I can try one format. If that fails I just try the other. However: How do I serialize in the right format?
It doesn't seem to exist any way to send a parameter to the serializer that this particular message goes to Norway so use this date format...and the next goes to Sweden use another format etc.
Code that I have, that may help:
@GET
@Path("{country:se|no}/{id}")
public Response webservice(@PathParam("country") String country,
@PathParam("id") String id) {
country = country.toUpperCase();
WebServiceResponse response = doWebServiceStuff(id, country)
return Response.ok(reponse).build();
}
Response has a .language() method, but that seems just to affect the headers.
@JsonAutoDetect
public class WebServiceResponse {
@JsonSerialize(using = JsonDateSerializer.class)
@JsonDeserialize(using = JsonDateDeserializer.class)
private Date date;
public void setDate(Date d) { this.date = d; }
public Date getDate() { return this.date; }
}
Serializer today. I would like it to adapt whether it is going to a Norwegian user or Swedish user.
public class JsonDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
provider.getConfig().getDateFormat()
}
}
Deserializer. It have about the same problem but I could surround it with try/catch...if the Swedish date format is not valid, try to parse with the Norwegian number instead and throw RuntimeException if it still is a problem.
public class JsonDateDeserializer extends JsonDeserializer<Date> {
@Override
public Date deserialize(JsonParser parser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
String dateText = parser.getText();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
return dateFormat.parse(dateText);
} catch (ParseException e) {
// TODO Auto-generated catch block
throw new RuntimeException("Can't parse date " + dateText, e);
}
}
}
Btw...I am using codehaus version of Jackson as that seems to work with Mule. I tried FasterXML-version but that didn't use my custom serializers nor the new fancy annotation based formatters (so you don't need custom serializers). Version 1.9.11 to be exact.
Again: the question is, how can I control the date format based on conditions from for instance the URL (more exactly from the outside) for each message. I know in webservice-method (the first code block) which country I am talking with but not in the serializer...
Outcome
The solution I got provided below was really a solution that would fix my issue, but I do beleive it is not possible to get it working in Mule 3.5.2 EE. However, if using Mule 3.6.0 or 3.7.0 (that seems to be the latest version now) this will probably be the solution for you, as well as others that might use other frameworks.
Not mentioned in the comments, but I did try commenting out "String country = uriInfo.getPathParameters().getFirst("country");" and hardcoded country to "no" and I did get Norwegian date format. When recompiling it with "se" I did get Swedish format, so the solution really works even though I could not get it to work.
Update2
I did have a discussion with Mule Support. In 3.5.x of Mule and older, jersey-json and jackson-jaxrs are shipped and it is a bit random (and depends on different environments) which it loads. One can remove jersey-json from $MULE_HOME/lib/opt. 3.6.x and later will only ships which jackson-jaxrs.
As I sit on a system with many flows that works, I have not the time to test if removing jersey-json doesn't break anything (as removing the file will affect all flows and not just this one). Basicly 3.6.x and later will have the better control over Jersey (choosing Providers etc.), and will make it possible to get this working.
回答1:
"...how can I control the date format based on conditions from for instance the URL (more exactly from the outside) for each message"
Though a little bit more work, one way is to create different ObjectMappers configured differently for each type of request. To determine which one will be used we can make the decision inside a ContextResolver. We could inject a UriInfo into the resolver, to get the value of the @PathParam("country"). Then make the decision from that, which mapper will be used. For example
@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper sweMapper;
private final ObjectMapper norMapper;
private final ObjectMapper defaultMapper;
@Context
private UriInfo uriInfo;
public ObjectMapperContextResolver() {
defaultMapper = new ObjectMapper();
sweMapper = new ObjectMapper();
SimpleModule sweModule = new SimpleModule("SweModule", new Version(1,0,0,null));
sweModule.addDeserializer(Date.class, new JsonDateDeserializer(sweFormat));
sweModule.addSerializer(Date.class, new JsonDateSerializer(sweFormat));
sweMapper.registerModule(sweModule);
norMapper = new ObjectMapper();
SimpleModule norModule = new SimpleModule("NorModule", new Version(1,0,0,null));
norModule.addDeserializer(Date.class, new JsonDateDeserializer(norFormat));
norModule.addSerializer(Date.class, new JsonDateSerializer(norFormat));
norMapper.registerModule(norModule);
}
@Override
public ObjectMapper getContext(Class<?> type) {
String country = uriInfo.getPathParameters().getFirst("country");
if (country == null) {
return defaultMapper;
}
switch (country) {
case "se": return sweMapper;
case "no": return norMapper;
default: return defaultMapper;
}
}
}
The reason we are using three mapper is for one, they are expensive to create. Secondly, configuring them is not thread-safe. And since the ContextResolver will be a singleton, only one of the mappers will be used for the application. So we just create three for different cases.
If you go this route, you should also remember to remove the serialization annotations from the field.
UPDATE
So with Jersey 2.6, it seems there is a problem with the above solution. It just fails on startup. The solution I was able to find was to not use this dependency
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>${jersey-version}</version>
</dependency>
Seem loading of some part of this module causes it to fail. Instead just use the Pure Jackson dependency (which the above actually pulls in and uses itself).
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.13</version>
</dependency>
Note: jersey-json:1.6 uses 1.7.1 of the above dependency. I just switched to use the latest 1.x version. So you may or may not want to switch it back.
Get rid of any you might have for the old artifact, i.e
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
And add the Jackson package as a package to scan
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>
com.your.packages,
org.codehaus.jackson.jaxrs
</param-value>
</init-param>
Or if you are using some Mule specific configuration, just register these
org.codehaus.jackson.jaxrs.JacksonJaxbJsonProviderorg.codehaus.jackson.jaxrs.JacksonMappingExceptionMapperorg.codehaus.jackson.jaxrs.JacksonParseExceptionMapper
来源:https://stackoverflow.com/questions/31827192/jackson-json-date-format-serialization-based-on-condition