Vert.x Service Proxies: Just after I change service methods from returning Future<T> to Uni<T> of Smallrye Mutiny, the code generation failed - vert.x

I've sucessfully generated service proxies for service having methods returning Future<T>,
but just after I changed those methods to return Uni<T> according to API Translation - Smallrye Mutiny Vert.x bindings,
when I try to execute mvn clean compile it always tells me this error message :
Could not generate model for com.example.beers.BarmanService#giveMeAStaticBeer(java.lang.String): Proxy methods must have void or Fluent returns
I would need your help to enlighten me how to fix it.
I put those codes on GitHub, and these are some critical ones:
//BarmanService.java
#VertxGen
#ProxyGen
public interface BarmanService {
Uni<Beer> giveMeAStaticBeer(String customerName);
Uni<Integer> getMyBill(String customerName);
Uni<Void> payMyBill(String customerName);
static BarmanService createProxy(Vertx vertx, String address) {
return new BarmanServiceVertxEBProxy(vertx, address);
}
}
//BarmanServiceImpl.java
public class BarmanServiceImpl implements BarmanService {
Map<String, Integer> bills;
public BarmanServiceImpl() {
this.bills = new HashMap<>();
}
#Override
public Uni<Beer> giveMeAStaticBeer(String customerName) {
Beer beer = new Beer("Workshop River Stout", "English Stout", 5);
return Uni.createFrom().item(() -> beer);
}
#Override
public Uni<Integer> getMyBill(String customerName) {
return Uni.createFrom().item(() -> bills.get(customerName));
}
#Override
public Uni<Void> payMyBill(String customerName) {
bills.remove(customerName);
System.out.println("Removed debt of " + customerName);
return Uni.createFrom().voidItem();
}
}
//package-info.java
#ModuleGen(groupPackage = "com.example", name = "beers")
package com.example.beers;
import io.vertx.codegen.annotations.ModuleGen;
<!-- //pom.xml -->
<dependencies>
<!-- // ... -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-codegen</artifactId>
<classifier>processor</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-service-proxy</artifactId>
</dependency>
<!-- // ... -->
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>smallrye-mutiny-vertx-core</artifactId>
<version>2.30.1</version>
</dependency>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>vertx-mutiny-generator</artifactId>
<version>2.30.1</version>
</dependency>
<!-- // ... -->
</dependencies>
PS In the beginning, when I generated service proxies for service having methods returning Future<T>, there is a generated class returning Uni<T>, but I have no idea how to use it:
package com.example.mutiny.beers;
#io.smallrye.mutiny.vertx.MutinyGen(com.example.beers.BarmanService.class)
public class BarmanService {
public static final io.smallrye.mutiny.vertx.TypeArg<BarmanService> __TYPE_ARG = new io.smallrye.mutiny.vertx.TypeArg<>( obj -> new BarmanService((com.example.beers.BarmanService) obj),
BarmanService::getDelegate
);
private final com.example.beers.BarmanService delegate;
public BarmanService(com.example.beers.BarmanService delegate) {
this.delegate = delegate;
}
public BarmanService(Object delegate) {
this.delegate = (com.example.beers.BarmanService)delegate;
}
/**
* Empty constructor used by CDI, do not use this constructor directly.
**/
BarmanService() {
this.delegate = null;
}
public com.example.beers.BarmanService getDelegate() {
return delegate;
}
#CheckReturnValue
public io.smallrye.mutiny.Uni<com.example.beers.Beer> giveMeAStaticBeer(String customerName) {
return io.smallrye.mutiny.vertx.UniHelper.toUni(delegate.giveMeAStaticBeer(customerName));}
public com.example.beers.Beer giveMeAStaticBeerAndAwait(String customerName) {
return giveMeAStaticBeer(customerName).await().indefinitely();
}
public void giveMeAStaticBeerAndForget(String customerName) {
giveMeAStaticBeer(customerName).subscribe().with(io.smallrye.mutiny.vertx.UniHelper.NOOP);
}
// ...
public static com.example.mutiny.beers.BarmanService createProxy(io.vertx.mutiny.core.Vertx vertx, String address) {
com.example.mutiny.beers.BarmanService ret = com.example.mutiny.beers.BarmanService.newInstance((com.example.beers.BarmanService)com.example.beers.BarmanService.createProxy(vertx.getDelegate(), address));
return ret;
}
public static BarmanService newInstance(com.example.beers.BarmanService arg) {
return arg != null ? new BarmanService(arg) : null;
}
}

I just figured it out by myself. About to change service methods from returning Future<T> to Uni<T>,
The wrong apporach I did:
Edit package-info to remove useFutures = true
Edit service interfaces and change returning types
Edit service implimentations and change returning types, also change logic
Edit verticles to handle Uni<T> returned from service
And it turned out that the first three steps I did is unnecessary,
the suitable approach is:
Wrap vertx:
io.vertx.mutiny.core.Vertx mutinyVertx = new io.vertx.mutiny.core.Vertx(vertx);
Change the use of service interface to the generated one
import com.example.mutiny.beers.BarmanService;
Use the wrapped vertx:
BarmanService barmanService = BarmanService.createProxy(mutinyVertx, "beers.services.myapplication");
Edit verticles to handle Uni<T> returned from service
My problem has been solved, but I am not sure is it a good apporach to manually wrap the vertx on the MainVerticle launched by io.vertx.core.Launcher: io.vertx.mutiny.core.Vertx mutinyVertx = new io.vertx.mutiny.core.Vertx(vertx);, any suggestions guys?

Related

spring cloud stream kafka - Functional approach: Consumer is never called when producing tombstone records

The issue is pretty much the same as https://github.com/spring-cloud/spring-cloud-stream-binder-kafka/issues/455 but for the Functional approach
#Bean
public Consumer<Message<Foo>> fooConsumer() {
return message -> {
String key = // From message header - KafkaHeaders.RECEIVED_MESSAGE_KEY
Foo payLoad = message.getPayload();
if (payLoad == null) {
deleteSomething(key);
} else {
addSomething(key, payLoad);
}
};
}
When a proper 'Foo' (json) is produced the fooConsumer is invoked. But when a 'null' payload/tombstone is produced the consumer function is never called.
Workaround I tried with Custom 'Convertor' approach which is working:
#Component
public class KafkaNullConverter extends MappingJackson2MessageConverter {
#Override
protected Object convertFromInternal(Message<?> message, #NonNull Class<?> targetClass, Object conversionHint) {
if (message.getPayload() instanceof KafkaNull) {
return Foo.builder().isDeleted(true).build(); // Note: new `isDeleted` field added to `Foo`
}
return super.convertFromInternal(message, targetClass, conversionHint);
}
}
The fooConsumer then can check payload.isDeleted() to handle null case.
But this is verbose, pollutes pojo/model classes and have to repeat for every consumers.
I understand that spring-integration cannot working with null. But are there any better/standard approach to handle the 'tombstone' use-case with Functional approach?
Version: 3.0.4.RELEASE
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
This was recently fixed on the main branch https://github.com/spring-cloud/spring-cloud-stream-binder-kafka/pull/936 and https://github.com/spring-cloud/spring-cloud-function/issues/557

SOAP path resolution for dependent XSD-file not understood

I have two XSD-files, one is imported to the other. When retrieving the WSDL (via SoapUI) the imported xsd-file is not found.
Error loading [http://localhost:8294/authentication/shared-environment.xsd]:
org.apache.xmlbeans.XmlException: org.apache.xmlbeans.XmlException: error:
Unexpected end of file after null
The two of the xsd-files reside in the same folder:
src/main/resources
- auth-attributes.xsd
- shared-environment.xsd
The "auth-attributes.xsd" looks like this:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://dto.shared.auth.appl.com"
xmlns:Q1="http://dto.shared.auth.appl.com"
xmlns:Q3="http://dto.common.appl.com" elementFormDefault="qualified">
<xs:import namespace="http://dto.common.appl.com"
schemaLocation="shared-environment.xsd" />
.........
.........
.........
The WS-Adapter is defined this way:
#EnableWs
#Configuration
public class BackendServerConfig extends WsConfigurerAdapter {
#Bean
ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<MessageDispatcherServlet>(servlet, "/authentication/*");
}
#Bean(name = "authentication")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema authenticationSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("SharePort");
wsdl11Definition.setLocationUri("/authentication");
wsdl11Definition.setTargetNamespace("http://dto.shared.auth.timetracker.appl.com");
wsdl11Definition.setSchema(authenticationSchema);
return wsdl11Definition;
}
#Bean
public XsdSchema authenticationSchema() {
return new SimpleXsdSchema(new ClassPathResource("auth-attributes.xsd"));
}
I'm not really familiar with WSDL. The JAXB source generation from the XSDs is fine but the WSDL resolution fails. I't seems I need a method to tell the WSDL-building mechanism where to retrieve the imported XSDs.
The "DefaultWsdl11Definition" has got the method setSchemaCollection() to register multiple XSD-definitions. I used this method instead of setSchema - this helped - at least a bit. The error message has gone and the Soap-requests are answered correctly.
Only, when "adding" a new project to SoapUI, the client does not create automatically requests - for whatever reason - this only works with "import" from webserver.
The WS-Configuration now looks like this:
#EnableWs
#Configuration
public class BackendServerConfig extends WsConfigurerAdapter {
#Bean
ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<MessageDispatcherServlet>(servlet, "/authentication/*");
}
#Bean(name = "authentication")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema authenticationSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("SharePort");
wsdl11Definition.setLocationUri("/authentication");
wsdl11Definition.setTargetNamespace("http://dto.shared.auth.app.com");
wsdl11Definition.setSchemaCollection(schemaCollection());
return wsdl11Definition;
}
private XsdSchemaCollection schemaCollection() {
CommonsXsdSchemaCollection commonsXsdSchemaCollection = new CommonsXsdSchemaCollection(
new ClassPathResource("auth-attributes.xsd"),
new ClassPathResource("shared-environment.xsd"));
commonsXsdSchemaCollection.setInline(true);
return commonsXsdSchemaCollection;
}

Spring boot REST API Returning a List/Array Formatting issue

I am developing spring boot based web services API. I need to return a list of things (ProductData) for the GET response.
This is what the response looks like
<ProductDataList>
<ProductData>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
</ProductData>
</ProductDataList>
But I don't need the extra <ProductData> tag.
I need the response as below.
<ProductDataList>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
</ProductDataList>
Any idea why an extra tag is generated?
I have below in my WebMvcConfig file.
#Bean
public MappingJackson2XmlHttpMessageConverter xmlConverter() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.propertyNamingStrategy(PropertyNamingStrategy.
PascalCaseStrategy.PASCAL_CASE_TO_CAMEL_CASE);
builder.serializationInclusion(JsonInclude.Include.NON_EMPTY);
builder.failOnUnknownProperties(false);
MappingJackson2XmlHttpMessageConverter xmlConverter =
new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build());
return xmlConverter;
}
In my controller I have
#RequestMapping(value = "/productdata")
#ResponseBody
public ProductDataList getProductData(#RequestParam final String[] ids) {
ArrayList<ProductData> products = productDataService.getProductData(ids);
ProductData[] pdArray = new ProductData[products.size()];
products.toArray(pdArray);
ProductDataList productDataList = new ProductDataList();
productDataList.setProductData(pdArray);
return productDataList;
}
This is my ProductDataList class.
public class ProductDataList{
ProductData[] productData;
public ProductData[] getProductData() {
return productData;
}
public void setProductData(ProductData[] productData) {
this.productData = productData;
}
}
Edit 1.
When I return ArrayList<ProductData> the response was like this.
<ArrayList>
<item>...</item>
<item>...</item>
<item>...</item>
</ArrayList>
Edit 2.
After adding annotation JsonTypeInfo I made some progress, but not quite there to what I wanted.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
public class ProductData {}
<ProductDataList>
<item _type="ProductData">...</item>
<item _type="ProductData">...</item>
<item _type="ProductData">...</item>
<ProductDataList>
Objects aren't built that way. It's doing exactly as you're asking:
<ProductDataList> <!-- the ProductDataList object -->
<ProductData> <!-- the property containing the array -->
<ProductData>...</ProductData> <!-- each ProductData object -->
<ProductData>...</ProductData>
<ProductData>...</ProductData>
</ProductData>
</ProductDataList>
This is to ensure that other properties on the ProductDataList object would also have a spot inside the tags, e.g.
<ProductDataList>
<MyArray>...</MyArray>
<MyProperty></MyProperty>
</ProductDataList>
To get around this, you might as well try to cut out the Object middle-man.
#RequestMapping(value = "/productdata")
#ResponseBody
public ArrayList<ProductData> getProductDataList(#RequestParam final String[] ids) {
return productDataService.getProductData(ids);
}
If it works at all (I seem to remember JAXB not being able to parse ArrayLists), your ObjectMapper will give you...
<ArrayList>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
</ArrayList>
...instead of the root tag that you're hoping for. But if it DOES work, then just create a class that extends ArrayList and doesn't do anything, then return it instead.
public class ProductDataList<E> extends ArrayList<E>{ ... }
And then...
#RequestMapping(value = "/productdata")
#ResponseBody
public ProductDataList<ProductData> getProductDataList(#RequestParam final String[] ids) {
return (ProductDataList) productDataService.getProductData(ids);
}
Happy hunting.
Kieron
After some effort I was able to get this resolved. Key thing is to have #JacksonXmlElementWrapper(useWrapping = false) in the Object as mentioned in this answer
#JacksonXmlRootElement(localName = "ProductDataList")
public class ProductDataList {
#JacksonXmlProperty(localName = "ProductData")
#JacksonXmlElementWrapper(useWrapping = false)
ProductData[] productDataArray = null;
public ProductData[] getProductDataArray() {
return productDataArray;
}
public void setProductDataArray(ProductData[] productDataArray) {
this.productDataArray = productDataArray;
}
}
Now the response looks like just as I wanted to be.
<ProductDataList>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
</ProductDataList>

Jira SAL using PluginSettings

I looked for a way to store project-specific configurations for my plugin.
In the first step i only want to store a simple String like "Hello".
So, what i found is SAL and the PluginSettings.
https://developer.atlassian.com/docs/atlassian-platform-common-components/shared-access-layer/sal-services
This seems pretty easy to use but I don´t have any idea how to implement it into my code.
I used a WebWork taking place in the project administration section:
#Override
public String doDefault() throws Exception {
Project project = getProjectManager().getProjectObjByKey(_projectKey);
HttpServletRequest request = ExecutingHttpRequest.get();
request.setAttribute((new StringBuilder()).append("com.atlassian.jira.projectconfig.util.ServletRequestProjectConfigRequestCache").append(":project").toString(), project);
return INPUT;
}
#Override
protected String doExecute() throws Exception {
Project project = getProjectManager().getProjectObjByKey(_projectKey);
HttpServletRequest request = ExecutingHttpRequest.get();
request.setAttribute((new StringBuilder()).append("com.atlassian.jira.projectconfig.util.ServletRequestProjectConfigRequestCache").append(":project").toString(), project);
String param = request.getParameter("param");
return SUCCESS;
}
public void setProjectKey(String projectKey) {
_projectKey = projectKey;
}
public String getProjectKey() {
return _projectKey;
}
public String getBaseUrl() {
return ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL);
}
As SAL said i implemented a Settings-Class:
public CustomProjectSettings(
final PluginSettingsFactory pluginSettingsFactory,
final String projectKey) {
this.pluginSettingsFactory = pluginSettingsFactory;
this.projectKey = projectKey;
}
public void setValue(final String key, final String value) {
final PluginSettings settings = pluginSettingsFactory
.createSettingsForKey(projectKey);
settings.put(key, value);
}
public Object getValue(final String key) {
final PluginSettings settings = pluginSettingsFactory
.createSettingsForKey(projectKey);
return settings.get(key);
}
And I added the component in the xml:
<component-import key="pluginSettingsFactory" interface="com.atlassian.sal.api.pluginsettings.PluginSettingsFactory" />
So how do i connect and implement this into my webwork to say
protected String doExecute() throws Exception{
[...]
pluginSettings.setValue("Key", param);
[...]
}
It was easier than i thought.
I simply had to inject the Settings as a dependency for my WebWork:
public WebWorkAction(CustomProjectSettings settings){
this.settings = settings
}
The Settings-Class gets autowired by
<component-import key="pluginSettingsFactory"
interface="com.atlassian.sal.api.pluginsettings.PluginSettingsFactory" />
and by adding
<component key="settingsComponent"
class="com.xxx.CustomProjectSettings">
</component>

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.