com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDateTime] from String - rest

I have Java8 LocalDateTime in my Jax-RS REST APIs. My webapp is deployed in wildfly10. When i make a POST call(which includes LocalDateTime as parameter) i get following exception;
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDateTime] from String value ('2016-06-02T00:08:25.605Z'); no single-String constructor/factory method
at [Source: io.undertow.servlet.spec.ServletInputStreamImpl#396d1714; line: 2, column: 3] (through reference chain: com.leightonobrien.core.model.base.Company["created"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:843)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:277)
at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:284)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1150)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:153)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:144)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:523)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:101)
at com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap.findDeserializeAndSet(BeanPropertyMap.java:285)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:248)
Based on following guides ;
Wildfly throws "Unable to find a constructor that takes a String param or a valueOf() or fromString() method for javax.ws.rs.QueryParam" error
and
jaxrs could not find my custom (de)serializers for joda.money type
and
https://github.com/FasterXML/jackson-datatype-jsr310
I have written my provider and registered in the application path;
package com.test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
#Provider
public class LocalDateTimeConverterProvider extends JacksonJsonProvider implements ParamConverterProvider {
private final LocalDateTimeConverter converter = new LocalDateTimeConverter();
#Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
if (!rawType.equals(LocalDateTime.class))
return null;
return (ParamConverter<T>) converter;
}
public class LocalDateTimeConverter implements ParamConverter<LocalDateTime> {
#Override
public LocalDateTime fromString(String value) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(value, formatter);
return dateTime;
}
#Override
public String toString(LocalDateTime value) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String formattedDateTime = value.format(formatter);
return formattedDateTime;
}
}
public LocalDateTimeConverterProvider() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.registerModule(new JavaTimeModule());
setMapper(mapper);
}
}
import javax.ws.rs.ApplicationPath;
#ApplicationPath("/rest")
public class RestApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>();
set.add(com.test.JsonMoneyProvider.class);
set.add(com.test.DurtaionConverterProvider.class);
set.add(com.test.LocalDateTimeConverterProvider.class);
set.add(com.test.MoneyConverterProvider.class);
...
I make POST call like;
curl -X POST --header 'Content-Type: application/json' -d '{
"created": "2016-06-02T00:08:25.605Z",
"updated": "2016-06-02T00:08:25.605Z",
"id": 0,
"code": "string",
"name": "string",
"abn": "string",
"addresses": [
{
"id": 0,
"address1": "string",
"address2": "string",
"city": "string",
"state": "string",
"postcode": "string",
"country": "string",
"gps": {
"latitude": {
"latitude": 0,
"value": 0
},
"longitude": {
"longitude": 0,
"value": 0
}' 'http://localhost:8080/test2dbwar/rest/Companys'
How can I overcome above issue? Now I'm clueless..I tried to put all stuff (try to avoid wildfly's jax-rs complex support issue, jackson serialization issue) and sort out the issue..
Any help?

The first question you linked to involves conversion of #XxxParam annotations. This is what the ParamConverterProvider is for. This is a completely different (de)serailization process from entity body (de)serialization.
For entity (de)serialization, MessageBodyReader/MessageBodyWriters are used. Jackson provides one such implementation, in its JacksonJsonProvider/JacksonJaxbJsonProvider, which you are currently using, whether you know it or not. So the configuration for the LocalDataTime needs to somehow be configured with that provider.
The only way to configure Jackson support for the LocalDateTime serialization, is through it's ObjectMapper. You can create a custom Json(De)Serializer, as mentioned in this post (Option two), or you can use the JSR310Module (that already has custom Json(De)Serializer for LocalDateTime), mentioned in this post.
To actually configure the JacksonJsonProvider/JacksonJaxbJsonProvider to use your configured ObjectMapper, you can use a ContextResolver, as mentioned in both of the previous links, or you can construct the JacksonJsonProvider with the ObjectMapper, and register that provider
ObjectMapper mapper = new ObjectMapper();
mapper..configurModuleOrSerializer
JacksonJsonProvider provider = new JacksonJsonProvider(mapper);
registerProvier(provider)
Personally I would just go with the ContextResolver, as mentioned in the links. The difference is that with the above code, the ObjectMapper is provided to the provider explicitly, while with the ContextResolver, during runtime, the provider will search the JAX-RS registry for a ContextResolver of type ObjectMapper, and then obtain it that way.

In my case, adding the following works for me.
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.8.9</version
</dependency>
and configure the ObjectMapper like this:
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());

Related

How to deserialize Date ISO String to DateTime Object in built_value serialization in dart?

I wanna serialize a json object including an ISOString date to a dart object using built value.
this is a sample json:
{
"title": "test",
"description": "test description",
"date": "2020-06-05T11:42:38.585Z",
"creator": {
"email": "test#test.com"
}
}
this is the model:
abstract class Post implements Built<Post, PostBuilder> {
#nullable
#BuiltValueField(wireName: '_id')
String get id;
String get title;
String get description;
DateTime get date;
#nullable
User get creator;
Post._();
static Serializer<Post> get serializer => _$postSerializer;
factory Post([updates(PostBuilder b)]) = _$Post;
factory Post.fromJson(Map<String, dynamic> map) =>
serializers.deserializeWith(Post.serializer, map);
Map<String, dynamic> toJson() =>
serializers.serializeWith(Post.serializer, this);
}
and this is the error:
Deserializing '[title, test1, description, test1 description, date, 2020-06-05T...' to
'Post' failed due to: Deserializing '2020-06-05T11:42:38.585Z' to 'DateTime' failed due
to: type 'String' is not a subtype of type 'int' in type cast
how do I fix that?
You can import the Iso8601DateTimeSerializer directly from built_value - don't copy the file over to your project.
Your final serializers.dart should look like:
import 'package:built_value/iso_8601_date_time_serializer.dart';
import 'package:built_value/iso_8601_duration_serializer.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
part 'serializers.g.dart';
#SerializersFor([
// your built value classes
])
final Serializers serializers = (_$serializers.toBuilder()
..add(Iso8601DateTimeSerializer())
..add(Iso8601DurationSerializer())
..addPlugin(StandardJsonPlugin())
).build();
You need to add a custom DateTime serializer that you can find here: Iso8601DateTimeSerializer
create a new dart file (I named it iso8601_date_time_serializer.dart)
paste the code from 1
add the import to your serializers.dart file (import 'iso8601_date_time_serializer.dart';)
edit your serializers.g.dart file
Serializers _$serializers = (new Serializers().toBuilder()
..add(Iso8601DateTimeSerializer())
..add(Post.serializer) // I assume you have this in yours
..addPlugin(StandardJsonPlugin()))
.build();
Please note that this modification might be deleted if you regenerate the code with build_runner.
In case you want to dig deeper, I got the answer from built_value GitHub issue 454

Only apply modifyResponseBody for certain content-type

I am using GatewayFilterSpec.modifyResponseBody (marked as a "BETA" feature) to rewrite JSON payloads. This works well as long as the response payloads are in fact of content-type application/json. In my case, that is unfortunately not always guaranteed, and I would like it to only apply the modifyResponseBody if the reponse has the Content-Type: application/json header, else skip the filter. Is this possible with Spring Cloud Gateway, and how to do this? Thank you.
Now I'm getting this:
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/html' not supported
at org.springframework.web.reactive.function.BodyInserters.lambda$null$11(BodyInserters.java:329)
at java.util.Optional.orElseGet(Optional.java:267)
at org.springframework.web.reactive.function.BodyInserters.lambda$bodyInserterFor$12(BodyInserters.java:325)
Here is a "solution", one that has all sorts of problems:
package my_package;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import static org.springframework.http.MediaType.APPLICATION_JSON;
#Component
#Primary
public class JsonOnlyModifyResponseBodyGatewayFilterFactory extends ModifyResponseBodyGatewayFilterFactory {
public JsonOnlyModifyResponseBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) {
super(codecConfigurer);
}
#Override
public GatewayFilter apply(Config config) {
return new MyModifyResponseGatewayFilter(config);
}
public class MyModifyResponseGatewayFilter extends ModifyResponseGatewayFilter {
MyModifyResponseGatewayFilter(Config config) {
super(config);
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse serverHttpResponse = getServerHttpResponseFromSuper(exchange);
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
#Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (APPLICATION_JSON.isCompatibleWith(getDelegate().getHeaders().getContentType())) {
return serverHttpResponse.writeWith(body);
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build());
}
private ServerHttpResponse getServerHttpResponseFromSuper(ServerWebExchange exchange) {
ServerHttpResponse[] serverHttpResponse = new ServerHttpResponse[1];
//noinspection UnassignedFluxMonoInstance
super.filter(exchange, chain -> {
serverHttpResponse[0] = chain.getResponse(); // capture the response when the super sets it
return null;
});
return serverHttpResponse[0];
}
}
}
The chosen approach is in lieu of just changing a copy of the existing ModifyResponseBodyGatewayFilterFactory. This allows version upgrades of Spring Boot Gateway to bring in minor changes of ModifyResponseBodyGatewayFilterFactory. But since JsonOnlyModifyResponseBodyGatewayFilterFactory is very dependent on the implementation of ModifyResponseBodyGatewayFilterFactory, this may easily get broken. Another flaw of this solution is that I had to put an #Primary annotation to avoid a required a single bean, but 2 were found exception, but it overrides the default which would presumably affect other uses of modifyResponseBody. It's ugly to call super.filter and not use its result. And so on. So, while this "works", it doesn't, well, fill me with joy.

namespace prefix for generated jersey rest response xml

I'm generating rest responses (using Jersey) from jaxb models. And for some of the responses, the generated XML has namespace prefix (ns2) added to the namespace attribute although they all exists in the same namespace. But for others, it is perfectly fine.
With my analysis, I think it happens when there is a complex element (another jaxb model) is being used inside one. But all these models are declared in same namespace in package-info.java.
Here is the code.
XYZModel.class
package int.xyxp.model;
#XmlType(name="xyztype")
#XmlRootElement(name="xyz")
#XmlSeeAlso({XModel.class, YModel.class, Z.class})
#XmlAccessorType(XmlAccessType.FIELD)
public class XYZModel extends VModel {
#XmlElement(name="code")
private String code;
#XmlElementWrapper(name="refs", namespace="http://reference.com/ref")
#XmlElementRef
private List<XModel> refs = new ArrayList<XModel>(0);
//continues
package-info.java
#javax.xml.bind.annotation.XmlSchema(
namespace = "http://reference.com/ref",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package int.xyxp.model;
generated XML
<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<ns2:xyz version="1.0" xmlns:ns2="http://reference.com/ref">
<ns2:code>15</ns2:code>
<ns2:refs/>
</ns2:xyz>
expected XML (without prefix, by assuming default namespace).
<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<xyz version="1.0" xmlns="http://reference.com/ref">
<code>15</code>
<refs/>
</xyz>
any thoughts. Thanks.
[EDIT]
After I tried to insert my preferred namespace prefix and it doesn't work even. so probably the package-info.java is used only for namespace and not for selecting the namespace prefix.
package-info.java
#javax.xml.bind.annotation.XmlSchema(
namespace = "http://reference.com/ref",
xmlns = {
#javax.xml.bind.annotation.XmlNs(prefix = "ref", namespaceURI = "http://reference.com/ref"),
},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package int.xyxp.model;
NOTE: I have overridden MessageBodyWriter to provide my own namespace ("my"). Even though I have returned empty "", it takes ns2 by default when its empty. So this answers works if you want to have your own namespace instead of default "ns2".
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.Providers;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper;
#Produces(value=MediaType.APPLICATION_XML)
public class WSNamespaceWriter implements MessageBodyWriter<Object>{
#Context
protected Providers providers;
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
System.out.println("Calling MessageWriter writetable--> " + type.getName());
return true;
}
public void writeTo(Object object, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException,
WebApplicationException {
try {
System.out.println("Calling MessageWriter-->");
ContextResolver<JAXBContext> resolver
= providers.getContextResolver(JAXBContext.class, mediaType);
JAXBContext jaxbContext;
if(null == resolver || null == (jaxbContext = resolver.getContext(type))) {
jaxbContext = JAXBContext.newInstance(type);
}
Marshaller m = jaxbContext.createMarshaller();
NamespacePrefixMapper mapper = new NamespacePrefixMapper() {
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
System.out.println ("Called NAMESPACE----------" + namespaceUri);
if ("http://www.example.com".equals(namespaceUri)
|| ("").equals(namespaceUri)) {
System.out.println ("Called NAMESPACE return --------");
return "my"; // my own namespace
}
System.out.println ("Called NAMESPACE return ns--------");
return "";
}
};
m.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", mapper);
m.marshal(object, entityStream);
} catch(JAXBException jaxbException) {
throw new WebApplicationException(jaxbException);
}
}
public long getSize(Object t, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return -1;
}
}

eclipselink + #convert(json) + postgres + list property

I'm using eclipselink 2.6 as a persistence provider of spring data jpa, that in my understanding, now allows you to serialize a subtree of an entity as json using the internal moxy serializer.
So I'm trying to mix this to migrate from embedded element collections to a serialized json using the json datatype of postgres.
I have an entity named Product, and this entity have the following mapped property:
#Convert(Convert.JSON)
private List<MetadataIndex> indexes=new ArrayList<MetadataIndex> ();
In which metadata index is a simple class with a few string properties.
I would like to convert this list of object into a json and store it into a column of json data type in postgres.
I thought that the above code should suffice, but it does not. The application crashes on boot (can't create entitymanager factory - npe somwhere inside eclipselink).
If I change the converter to #Convert(Convert.SERIALIZED) it works. It creates a field on the table Products named indexes of type bytea and store the serialized list in it.
Is this an eclipselink bug or I'm missing something?
Thank you.
well, I've used a custom eclipselink converter to convert my classes into json objects, then store them into the db using directly the postgres driver. This is the converter.
import fr.gael.dhus.database.jpa.domain.MetadataIndex;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.sessions.Session;
import org.postgresql.util.PGobject;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
/**
* Created by fmarino on 20/03/2015.
*/
#Converter
public class JsonConverter implements org.eclipse.persistence.mappings.converters.Converter {
private static ObjectMapper mapper = new ObjectMapper();
#Override
public Object convertObjectValueToDataValue(Object objectValue, Session session) {
try {
PGobject out = new PGobject();
out.setType("jsonb");
out.setValue( mapper.writerWithType( new TypeReference<Collection<MetadataIndex>>() {} )
.writeValueAsString(objectValue) );
return out;
} catch (IOException e) {
throw new IllegalArgumentException("Unable to serialize to json field ", e);
} catch (SQLException e) {
throw new IllegalArgumentException("Unable to serialize to json field ", e);
}
}
#Override
public Object convertDataValueToObjectValue(Object dataValue, Session session) {
try {
if(dataValue instanceof PGobject && ((PGobject) dataValue).getType().equals("jsonb"))
return mapper.reader( new TypeReference<Collection<MetadataIndex>>() {}).readValue(((PGobject) dataValue).getValue());
return "-";
} catch (IOException e) {
throw new IllegalArgumentException("Unable to deserialize to json field ", e);
}
}
#Override
public boolean isMutable() {
return false;
}
#Override
public void initialize(DatabaseMapping mapping, Session session) {
}
}
as you can see I use jackson for serialization, and specify the datatype as Collection. You can use the type you want here.
Inside my classes, I've mapped my field with this:
#Convert(converter = JsonConverter.class)
#Column (nullable = true, columnDefinition = "jsonb")
adding also this annotation to the class:
#Converter(converterClass = JsonConverter.class, name = "jsonConverter")
To make things works properly with jackson I've also added to my MetadataIndex class this annotation, on the class element:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#class")
I personally like using directly the postgres driver to store those kind of special datatype. I didn't manage to achieve the same with hibernate.
As for the converter, I've would preferred a more general solution, but jackson forced me to state the object type I want to convert. If you find a better way to do it, let me know.
With a similar approach, I've also manage to use the hstore datatype of postgres.

jax-ws change soap response [duplicate]

How can I modify the namespace of the response like this:
old response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:GetAmountResponse xmlns:ns2="http://ws.dsi.otn.com/dab">
<etat>0</etat>
<montant>500.0</montant>
</ns2:GetAmountResponse>
</soap:Body>
</soap:Envelope>
new response wanted :
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetAmountResponse xmlns="http://ws.dsi.otn.com/dab">
<etat>0</etat>
<montant>500.0</montant>
</GetAmountResponse>
</soap:Body>
</soap:Envelope>
I want to remove the ns2 namespce prefix.
In the first case, the GetAmountResponse is in namespace http://ws.dsi.otn.com/dab while etat and montant are in a default (empty) namespace.
In the new message you want, GetAmountResponse, etat and montant are all in namespace http://ws.dsi.otn.com/dab.
The namespaces can be controlled from the namespaces of your classes. Use the same namespace in all and you will have them in the same namespace, leave classes with defaults and they default to empty namespace.
For example, if you were to have something like this in your web service class:
#WebMethod
public
#WebResult(name = "getAmountResponse", targetNamespace = "http://ws.dsi.otn.com/dab")
AmountResponse getAmount(
#WebParam(name = "getAmountRequest", targetNamespace = "http://ws.dsi.otn.com/dab") AmountRequest request) {
AmountResponse response = new AmountResponse();
response.setEtat(0);
response.setMontant(500.0);
return response;
}
with a response class like this:
#XmlRootElement
public class AmountResponse {
private int etat;
private double montant;
// getter and setters omitted
}
you will end up with the first type of soap message.
But if you change the response class to look like this instead:
#XmlRootElement(namespace = "http://ws.dsi.otn.com/dab")
#XmlAccessorType(XmlAccessType.NONE)
public class AmountResponse {
#XmlElement(namespace = "http://ws.dsi.otn.com/dab")
private int etat;
#XmlElement(namespace = "http://ws.dsi.otn.com/dab")
private double montant;
// getters and setter omitted
}
you will bring all tags in the same namespace and you get something equivalent to the new type of message you want. I said equivalent because I don't think you will get exactly this:
<GetAmountResponse xmlns="http://ws.dsi.otn.com/dab">
<etat>0</etat>
<montant>500.0</montant>
</GetAmountResponse>
It's more likely to get something like this instead:
<ns2:getAmountResponse xmlns:ns2="http://ws.dsi.otn.com/dab">
<ns2:etat>0</ns2:etat>
<ns2:montant>500.0</ns2:montant>
</ns2:getAmountResponse>
It's the same "XML meaning" for both messages although they don't look the same.
If you absolutely want it to look like that, I think you will have to go "low level" and use something like a SOAP handler to intercept the response and modify it. But be aware that it won't be a trivial task to change the message before it goes on the wire.
logical handler are enough to transform to the message as expected :
package com.ouertani.slim;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.LogicalMessage;
import javax.xml.ws.handler.LogicalHandler;
import javax.xml.ws.handler.LogicalMessageContext;
import javax.xml.ws.handler.MessageContext;
/**
*
* #author ouertani
*/
public class MyLogicalHandler implements LogicalHandler<LogicalMessageContext> {
#Override
public boolean handleMessage(LogicalMessageContext messageContext) {
/// extract state and amount
int state = 0;
double amount = 200.0;
transform(messageContext, state, amount);
return false;
}
public boolean handleFault(LogicalMessageContext messageContext) {
return true;
}
public void close(MessageContext context) {
}
private void transform( LogicalMessageContext messageContext, int etat, double montant){
LogicalMessage msg = messageContext.getMessage();
String htom = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"+
"<soap:Body>"+
"<GetAmountResponse xmlns=\"http://ws.dsi.otn.com/dab\">"+
"<etat>"+etat+"</etat>"+
"<montant>"+montant+"</montant>"+
"</GetAmountResponse>"+
"</soap:Body>"+
"</soap:Envelope>";
InputStream is = new ByteArrayInputStream(htom.getBytes());
Source ht = new StreamSource(is);
msg.setPayload(ht);
}
}
This is a very old question, still it is yet to be effectively answered. This week I faced a very similar problem. My application is invoking a Soap web-service provided by a legacy system whose XML is response syntactically wrong with some empty characters (line break, or tabs or white spaces) before XML declaration. In my scenario I could not change the legacy system to fix its response so changing the response before parsing was the only alternative I was left with.
Here is my solution:
I have added the following maven dependencies to my application:
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.xml.ws</groupId>
<artifactId>jaxws-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.0</version>
</dependency>
Then I have registered a Java SPI custom implementation of “com.oracle.webservices.impl.internalspi.encoding.StreamDecoder”. This class is invoked immediately before the XML parse with the corresponding response InputStream, so at this point you can read the response InputStream or wrap/proxy it and make any change to jax-ws response before parsing. In my case I just remove some invisible characters before first visible character.
My StreamDecoder SPI implementation:
package sample.streamdecoder;
import com.oracle.webservices.impl.encoding.StreamDecoderImpl;
import com.oracle.webservices.impl.internalspi.encoding.StreamDecoder;
import com.sun.xml.ws.api.SOAPVersion;
import com.sun.xml.ws.api.message.AttachmentSet;
import com.sun.xml.ws.api.message.Message;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
public class MyStreamDecoder implements StreamDecoder {
//JAX-WS default implementation
private static final StreamDecoderImpl streamDecoder = new StreamDecoderImpl();
#Override
public Message decode(InputStream inputStream, String charset, AttachmentSet attachmentSet, SOAPVersion soapVersion) throws IOException {
//Wrapping inputStream
InputStream wrapped = wrapInputStreamStrippingBlankCharactersBeforeXML(inputStream, charset);
//Delegating further processing to default StreamDecoder
return streamDecoder.decode(wrapped, charset, attachmentSet, soapVersion);
}
private InputStream wrapInputStreamStrippingBlankCharactersBeforeXML(InputStream inputStream, String charset) throws IOException {
int WHITESPACE = (int) Charset.forName(charset).encode(" ").get();
int LINE_BREAK = (int) Charset.forName(charset).encode("\n").get();
int TAB = (int) Charset.forName(charset).encode("\t").get();
return new InputStream() {
private boolean xmlBegin = true;
#Override
public int read() throws IOException {
int read = inputStream.read();
if (!xmlBegin) {
return read;
} else {
while (WHITESPACE == read
|| LINE_BREAK == read
|| TAB == read) {
read = inputStream.read();
}
xmlBegin = false;
}
return read;
}
};
}
}
In order to register it, just create a file “META-INF/services/ com.oracle.webservices.impl.internalspi.encoding.StreamDecoder” named “” and write the fully qualified name of your SPI implementation on the first line like that:
Content of file META-INF/services/ com.oracle.webservices.impl.internalspi.encoding.StreamDecoder :
sample.streamdecoder.MyStreamDecoder
Now every response will be passed to you implementation before parse.