Renaming an XML/SOAP tag using Apache CXF - soap

I've got a SOAP web-service server using Apache CXF as implementation. Due to some external technical constraint I'd like to be able to rename some XML tags naming an operation parameter (which are deprecated) in the inbound SOAP request. I was reading about using Interceptors for this, but the documentation on how to setup/configure them is not very clear.
My code to publish an endpoint is the following:
Endpoint endpoint = Endpoint.create(
"http://schemas.xmlsoap.org/soap/", new MyServer());
endpoint.publish("ws/endpoint");
Ideally I'd like to add a filter only to a given endpoint (I have several of them).

Apache's documentations about interceptors are quite clear (IMO), anyway, there is a helloworld project (based on spring boot, cxf and maven) in my github profile which you can take a look for setting up interceptors (in fact it's a baisc autentication interceptor).
For setting up an interceptor (e.g InInterceptor), your class should extend AbstractPhaseInterceptor<Message> and override handleMessage(Message message) method, then in the constructor you should declare the phase in which the interceptor is going to be applied. Finally you have to instantiate it and apply in on an Endpoint.
As you said:
rename some XML tags naming an operation parameter (which are
deprecated) in the inbound SOAP request
I think the name of the operation parameter (in WSDL file) is something different from the argument of your web method. Suppose that there is method in your endpoint named addPerson:
#WebMethod
String addPerson(Person person) {
/*method logic*/
}
and Person class:
class Person {
private String firstName;
private String lastName;
private Date birthDate;
//getters and setters
}
in order to map lastName property to a different name, you have to annotate it with
#XmlElement(name = "sureName")
private String lastName;
after applying this anotation, sureName (in wsdl file) is going to be mapped to lastName.
In addition, there is #WebParam annotation which can be used for changing the name of web method arguments:
#WebMethod
String sayHello( #WebParam(name = "sureName") String lastName);
Hope it helps.

Related

Spring Cloud Feign Client #RequestParam with List parameter creates a wrong request

I have a Spring Clound Feign Client mapping defined as following
#RequestMapping(method = RequestMethod.GET, value = "/search/findByIdIn")
Resources<MyClass> get(#RequestParam("ids") List<Long> ids);
when I call
feignClient.get(Arrays.asList(1L,2L,3L))
according to what I can see in the debugger, the feign-core library forms the following request:
/search/findByIdIn?ids=1&ids=2&ids=3
instead of expected
/search/findByIdIn?ids=1,2,3
which would be correct for the server Spring Data REST endpoint declared in the same way as my Feign client method.
Thus, because of this issue, the request always returns empty set.
I have seen similar question, but it looks like the Feign client was working as I expect back in 2015.
I am using:
spring-cloud-starter-feign version 1.2.4.RELEASE
feign-httpclient version 9.4.0
feign-core version 9.4.0
Is there a way to correct the behaviour and "marry" the Spring Cloud Feign Client with the Spring Data REST defined endpoints?
I had the same issue with multiple occurence of the parametre instead of the expected comma separated sequence of items. The solution was really simple:
In my feign client I used arrays
feignClient.get(new Long[]{1L,2L,3L})
instead of collection/list:
feignClient.get(Arrays.asList(1L,2L,3L))
In Feign you can annotate your controller with the following
#CollectionFormat(feign.CollectionFormat.CSV) and it will process collections in
the CSV format findByIdIn?ids=1&ids=2&ids=3
Thanks #prola for your answer.
Just to add an explicit example, #CollectionFormat(feign.CollectionFormat.CSV) annotation targets a method; you can't apply globally to your Feign Client interface.
So each method will be similar to:
#RequestMapping(value = ["/objects"], method = [RequestMethod.GET])
#CollectionFormat(feign.CollectionFormat.CSV)
fun findById(
#RequestParam(value = "object.id", required = true) id: String,
#RequestParam(value = "object.fields", required = false) objectFields: List<String> = DEFAULT_FIELDS_LIST,
#RequestParam(value = "format") format: String = FORMAT,
): ResponseEntity<ObjectsDTO>
The result will be
/objects?object.fields=size,weight,location
instead of
/objects?object.fields=size&object.fields=weight&object.fields=location
You can also refer to:
1.16.Feign CollectionFormat support
OpenFeign #542: Support Multiple Collection Formats
I've just battled with this today, and the solution for me was surprisingly simple.
If you use brackets [] for denoting query array:
Resources<MyClass> get(#RequestParam("ids[]") List<Long> ids);
it will create a request that looks like this
/search/findByIdIn?ids[]=1&ids[]=2&ids[]=3
Most server side frameworks will interpret this as an array.
If your server is also in spring then you can pick this up like this
#GetMapping("/search/findByIdIn")
public ResponseEntity findByIdIn(#RequestParam("ids[]") List<Long> ids) { ... }
Just keep in mind that the query has to be encoded, [] gets encoded to %5B%5D.

How to set custom object in body of message in cxf?

I have a REST webservice with method custom (GET).
#GET
#Path("/custom")
public UIResponse custom(final UIParameters uiParameters){...}
As you can see this method has one argument. This is my custom object.
Object UIParameters is built from argument given as query string.
eg. http://example.com/custom?objectType=article
UIParameters object will contain one field:
UIParameters {
String objectType = "article";
}
I have tried to use InInterceptor to get this parameter from URL, build UIParameter object and set Content of message. Unfortunatelly it doesn't work.
After that I've provide MessageBodyReader for UIParameters but it still doesn't work.
What should I do to achive this goal?
Thanks
Update:
In InInterceptor I've copied query string to http headers. Now part of URL where user place parameters is accessible in my MessageBodyReader. Here I can build my object UIParameters.
Everything works fine but I don't think that this solution is the best.
Does somebody know better solution?
AnnotationQueryParam("") allows to get all the query parameters injected.
You do not need an interceptor and it is not the recommended way. See CXF documentation http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Parameterbeans
#GET
#Path("/custom")
public UIResponse custom(#QueryParam("") UIParameters uiParameters)
class UIParameters {
String objectType;
}
If you want to build the bean yourself using the query parameters use #Context UriInfo annotation
#GET
#Path("/custom")
public UIResponse custom( #Context UriInfo uriInfo){
MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
new UIParameters().Builder()
.objectType(params.getFirst("type"))
.build();
}

Response and DTO objects missing from XSD

I'm using the latest version of ServiceStack with NuGet. I've got a basic service setup that works fine with the JsonServiceClient and is passing all of our unit tests as expected.
Unfortunately I'm also trying to support SOAP and Visual Studios "Add Service Refernece" proxy generation for interop with other groups who want to continue using that sort of framework.
What I'm running into is that our DTO's including the Response objects are not being populated into the XSD's or wsdl's that ServiceStack is generating via the metadata page. This seems very odd in conjunction with the fact that when viewing the individual SOAP Operation examples the correct Response and DTO's are shown in the generated example.
An Example Response:
Namespace Operations
<DataContract>
Public Class GetItemResponse
Implements IHasResponseStatus
<DataMember>
Public Property ResponseStatus As ResponseStatus Implements IHasResponseStatus.ResponseStatus
<DataMember>
Public Property Item As Item
End Class
End Namespace
An Example DTO:
Namespace Types
<DataContract>
Public Class Item
<DataMember>
Public Property ItemIdentifier As String
<DataMember>
Public Property ItemId As Guid?
<DataMember>
Public Property ItemName As String
<DataMember>
Public Property Description As String
<DataMember>
Public Property InstallDate As DateTime?
<DataMember>
Public Property FeatureNumber As String
<DataMember>
Public Property ModelNumber As String
End Class
End Namespace
I'm using the Assembly: ContractNamespace attribute to set the XML namespace as well.
I've downloaded the source code and done some investigation on my own. I'm not sure how recent this change is but it appears that in order for your DTO's and Response objects to be included in the wsdl you need to add a query string like "?includeAllTypes=true".
I'm also using the ServiceStack BasicAuthProvider setup which is causing AssignRoles and UnAssignRoles to be added to the Service automatically. The request and response objects for those calls are still failing to make it into the wsdl and causing "Add Service Reference" to fail. Fortunately we aren't making use of them so if I can find another configuration setting to remove them all should be working correctly.

Generating WADL request parameters for #InjectParam

I am using the #InjectParam to inject query parameters into a JAX-RS resource that contains #QueryParam annotated fields on a Jersey 1.12 implementation.
On the Resource:
#Path("query")
#GET
#Produces(MediaType.APPLICATION_XML)
public Query queryParam(#InjectParam Query query) {
return query;
}
And in the pojo that receives the injected parameters I have some JAXB and JAX-RS annotations.
#XmlRootElement
public class Query {
#QueryParam("value1")
String value1;
}
A simple test from a REST client:
http://localhost:8888/sandbox/query?value1=hello3
Produces the correct results:
<query>
<value1>hello3</value1>
</query>
That's great, but I also use the wadl-maven-plugin to generate a client which uses the WADL file to produce client code. The WADL file does not include the necessary request parameters that would be there if the #QueryParam annotation was included in the resource method parameters. Subsequently my client is produced to accept no parameters:
SandboxApi.sandbox().query().getAsQuery()
instead of accepting a populated generated client pojo.
Query queryClient = new Query();
queryClient.setValue1("hello3");
SandboxApi.sandbox().query().getAsQuery(queryClient);
Anyone know of a magic annotation I can put on the Jersey Resource that will produce a WADL with the right information so Wadl2Java could generate a client that will accept the POJO and subsequently send the appropriate fields as query parameters?
A response provided (via a private conversation) from some of the fine people working on Jersey:
#Path("query")
#GET
#Produces(MediaType.APPLICATION_XML)
public Query queryParam(#QueryParam("value1") String value1,
#InjectParam Query query) {
return query;
}
query param "value1" should appear in generated WADL.
In-built WADL generator does not contain support for these cases and I'm not sure whether it will be supported anytime soon.

Spring DefaultMessageListenerContainer/SimpleMessageListenerContainer (JMS/AMQP) Annotation configuration

So I'm working on a project where many teams are using common services and following a common architecture. One of the services in use is messaging, currently JMS with ActiveMQ. Pretty much all teams are required to follow a strict set of rules for creating and sending messages, namely, everything is pub-subscribe and the messages that are sent are somewhat like the following:
public class WorkDTO {
private String type;
private String subtype;
private String category;
private String jsonPayload; // converted custom Java object
}
The 'jsonPayload' comes from a base class that all teams extend from so it has common attributes.
So basically in JMS, everyone is always sending the same kind of message, but to different ActiveMQ Topics. When the message (WorkDTO) is sent via JMS, first it is converted into a JSON object then it is sent in a TextMessage.
Whenever a team wishes to create a subscriber for a topic, they create a DefaultMessageListenerContainer and configure it appropriately to receive messages (We are using Java-based Spring configuration). Basically every DefaultMessageListenerContainer that a team defines is pretty much the same except for maybe the destination from which to receive messages and the message handler.
I was wondering how anyone would approach further abstracting the messaging configuration via annotations in such a case? Meaning, since everyone is pretty much required to follow the same requirements, could something like the following be useful:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Listener {
String destination();
boolean durable() default false;
long receiveTimeout() default -1; // -1 use JMS default
String defaultListenerMethod() default "handleMessage";
// more config details here
}
#Listener(destination="PX.Foo", durable=true)
public class FooListener {
private ObjectMapper mapper = new ObjectMapper(); // converts JSON Strings to Java Classes
public void handleMessage(TextMessage message){
String text = message.getText();
WorkDTO dto = mapper.readValue(text, WorkDto.class);
String payload = dto.getPayload();
String type = dto.getType();
String subType = dto.getSubType();
String category = dto.getCategory();
}
}
Of course I left out the part on how to configure the DefaultMessageListenerContainer by use of the #Listener annotation. I started looking into a BeanFactoryPostProcessor to create the necessary classes and add them to the application context, but I don't know how to do all that.
The reason I ask the question is that we are switching to AMQP/RabbitMQ from JMS/ActiveMQ and would like to abstract the messaging configuration even further by use of annotations. I know AMQP is not like JMS so the configuration details would be slightly different. I don't believe we will be switching from AMQP to something else.
Here teams only need to know the name of the destination and whether they want to make their subscription durable.
This is just something that popped into my head just recently. Any thoughts on this?
I don't want to do something overly complicated though so the other alternative is to create a convenience method that returns a pre-configured DefaultMessageListenerContainer given a destination and a message handler:
#Configuration
public class MyConfig{
#Autowired
private MessageConfigFactory configFactory;
#Bean
public DefaultMessageListenerContainer fooListenerContainer(){
return configFactory.getListenerContainer("PX.Foo", new FooListener(), true);
}
}
class MessageConfigFactory {
public DefaultMessageListenerContainer getListener(String destination, Object listener, boolean durable) {
DefaultMessageListenerContainer l = new DefaultMessageListenerContainer();
// configuration details here
return l;
}
}