Set dynamic custom headers in http-outbound gateway - spring

I have http outbound gateway :
<int-http:outbound-gateway encode-uri="true" http-method="POST" header-mapper="headerMappper"
charset="UTF-8" url="{url}" expected-response-type="java.lang.String">
<int-http:uri-variable name="url" expression="headers.uri"/>
</int-http:outbound-gateway>
Header Mapper bean configuration :
<bean class="com.cc.gateway.HeaderMapper"/>
public class HeaderMapper extends org.springframework.integration.http.support.DefaultHttpHeaderMapper{
#Bean("headerMappper")
public HeaderMapper mapHeader()
{
this.setOutboundHeaderNames(getHeaderMapper());
this.setUserDefinedHeaderPrefix("");
return this;
}
public String[] getHeaderMapper()
{
Object [] headersArray =new HeadersConfig().getHeaders().keySet().toArray();
return Arrays.copyOf(headersArray,headersArray.length,String[].class);
}
}
How I can set header mapper configuration on every request ?
My configuration reads only once at deployment time.

The DefaultHttpHeaderMapper isn't structured for that kind of use; it is not thread-safe to change the mapped headers for each request.
You would have to override so many methods to make it thread-safe, it would probably be easier to just implement your own custom HeaderMapper<HttpHeaders>.
If you only have one message being sent at a time, though, simply override fromHeaders() and update the headers to map before calling super.fromHeaders().
That said, it's rather unusual to want to dynamically change mapped headers.

Related

Set spring integration header into soap header in spring integration (without interceptor/ threadlocal)

Is there anyway to set SOAP-ENV header in spring integration xml from spring integration headers ?
I tried below approach and expected bId to be populated to soap headers
<int:chain id="soapcall" input-channel="soapchannel">
<int-xml:header-enricher>
<int-xml:header name="bId" expression="headers['bId']"/>
</int-xml:header-enricher>
<int-ws:outbound-gateway uri="${soap.url}" interceptor="myInterceptor">
</int-ws:outbound-gateway>
</int:chain>
However Actual output doesn't have the header. see below:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:n1="http://namespace/n1">
<SOAP-ENV:Header/>
<SOAP-ENV:Body> ...
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I also looked into below approach inside chain before calling outbound gateway. It seems this header enricher takes only soap action as header. So this throws exception action headers not found
<int-ws:header-enricher >
<int-ws:soap-action expression="headers['bId']"/>
</int-ws:header-enricher>
I also looked into this post. But I couldn't get this compiled inside a chain.
I'm not sure what is the <int-xml:header-enricher>, but it doesn't matter.
What you need is called header-mapper on the <int-ws:outbound-gateway>.
Its code looks like:
#Override
protected void populateStandardHeaders(Map<String, Object> headers, SoapMessage target) {
String soapAction = getHeaderIfAvailable(headers, WebServiceHeaders.SOAP_ACTION, String.class);
if (!StringUtils.hasText(soapAction)) {
soapAction = "\"\"";
}
target.setSoapAction(soapAction);
}
#Override
protected void populateUserDefinedHeader(String headerName, Object headerValue, SoapMessage target) {
SoapHeader soapHeader = target.getSoapHeader();
if (headerValue instanceof String) {
QName qname = QNameUtils.parseQNameString(headerName);
soapHeader.addAttribute(qname, (String) headerValue);
}
}
So, by default it maps WebServiceHeaders.SOAP_ACTION as a standard one, and all those user defined if they are String and only as attribute of the target SOAP-ENV:Header element.
Those user defined headers can be mapped by the:
<xsd:attribute name="mapped-request-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of names of SOAP Headers to be mapped from the SOAP request into the MessageHeaders.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
If you need to populate sub-elements to the <SOAP-ENV:Header>, you don't have choice unless extend DefaultSoapHeaderMapper and override that populateUserDefinedHeader() and use SoapHeader.addHeaderElement() API.
Also look into the Reference Manual.

Camel & CXF & REST: ERROR No message body writer has been found for class java.util.ArrayList, ContentType: application/json

In my Spring configuration file:
<bean id="jacksonJsonProvider" class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
<bean id="restJacksonProviderList" class="java.util.ArrayList">
<constructor-arg>
<list>
<ref bean="jacksonJsonProvider"/>
</list>
</constructor-arg>
</bean>
//......
<route id="RestMyRoute">
<from id="RestRequest" uri="cxfrs:/rest/MyService?resourceClasses=com.myself.services.MyService&bindingStyle=SimpleConsumer&providers=#restJacksonProviderList" />
<to uri="direct:doRoute" />
</route>
The Service interface:
#GET
#Path("/my/something/{id}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#WebMethod
#WebResult(name = "getSomethingResponse")
public List<MySomething> getSomething(
#PathParam("id") #WebParam(name = "id") String id);
The code above works! I can send the get request to the URl and I get a JSON response.
Now, I do a small change: Instead of defining the web service's URL (and the route) by XML configuration, I define them by Java code:
public class MyRoute extends RouteBuilder {
private String uriRest = "cxfrs:/rest/MyService?resourceClasses=com.myself.services.MyService&bindingStyle=SimpleConsumer&providers=#restJacksonProviderList";
#Override
public void configure() throws Exception {
from(uriRest).
to("log:input").
to("direct:doRoute").
to("log:output");
}
}
When I hit the web service URL, I am getting 500 Internal Server Error and in the logs (Tomcat) I see JAXRSUtils ERROR No message body writer has been found for class java.util.ArrayList, ContentType: application/json
Actually the debugger tells me that defining the URI by Java code is recognized, since I do hit the code inside the route.
I saw this error in many answers here, basically they say to add a Json provider and assign it to the CXF endpoint.
Seems to me like it is what I have done. But it does not work.
Any idea what I am doing wrong here?
As peeskillet said, it's because there isn't a list of providers registered under the name restJacksonProviderList. You can get the JndiContext like this and bind a list to it in the configure method of your routebuilder:
JndiContext registry = (JndiRegistry) context.getRegistry();
registry.bind("restJacksonProviderList", Arrays.asList(new JacksonJsonProvider()));
Edit after comments:
Change & for & in your cxfrs uri definition, & is only needed in xml.

Camel: forward message to dynamic destinations (from database)

I am using camel 2.8.4 in my app. my app will receive request from a queue, then the request will be validated by a Validator. Base on the content of the message, the Validator will forward the request to different destinations. Validator will be a POJO bean. Destinations will be get from database (this is a MUST). I prefer to use spring dsl for camelContext.
1. I dont know how to write the validator to forward req to destinations.
2. Can we use something similar like this
<to uri='method=getURI() bean='Validator''> in camelContext
<camelContext>
<route id="route-1">
<from uri="mq:queue:QUEUE"/>
<bean ref="Validator" method="validate"/>
<!--i would be great if we can use <to uri="dynamicURI-from-database"> here -->
</route>
</camelContext>
Class Validator{
public void validate(String req){
if (...)
//get uri1 from database
String uri1=getURI(..);
//forward req to uri1
...........
else
//get uri2 from database
String uri2=getURI(...);
//forward req to uri2
...........
}
public String getURI(..){
......
return uri;
}
}
Use the dynamic URI feature to generate a URI at runtime. You can invoke a processor which sets the URI in exchange and then use that in the to clause.
Something like :
process(new Procesor()
public void process(Exchange exchange){
exchange.setHeader("myURI",someURI);
});
and in the to clause
<to uri="${header.myURI}"/>
Did you have a look at the dynamic recipient list pattern: http://camel.apache.org/recipient-list.html ?
You can use toD provided the destination endpoints are Http APIs. Please refer the link : https://camel.apache.org/components/latest/eips/toD-eip.html

How to pass request parameter to 'default-target-url'

I am setting 'cat=1' in the hidden field in login.jsp page and was expecting it to be available on the default-target-url. Entry in spring-security.xml is,
<form-login login-page="/login.html" default-target-url="/index.html"
authentication-failure-url="/loginfailed.html" />
and in the controller,
#RequestMapping(value="/index", method = RequestMethod.GET)
public String index(HttpServletRequest request) {
String cat = request.getParameter("cat");
if (cat != null && cat.equalsIgnoreCase("1")) {
return "add";
}
return "redirect:/index.jsp";
}
but cant get request parameter value (cat is null) so I believe it is because 'default-target-url' redirects the request (and does not forward it?). Is it the case?
If yes then is there any way I can pass parameter to the 'default-target-url'?
I have changed implementation approach a bit. Details give below,
spring-security.xml
<form-login login-page="/login.html" authentication-success-handler-ref="feedSuccessHandler"
authentication-failure-url="/loginfailed.html" />
<logout logout-success-url="/loggedout.html"/>
<beans:bean id="feedSuccessHandler"
class="main.java.com.sp.utilities.FeedSuccessHandler">
</beans:bean>
FeedSuccessHandler.java
public class FeedSuccessHandler implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String cat = request.getParameter("cat");
if (cat != null && cat.equalsIgnoreCase("1")) {
response.sendRedirect(request.getContextPath()+"/add.html");
}else{
SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
if(savedRequest != null) {
response.sendRedirect(savedRequest.getRedirectUrl());
}else{
response.sendRedirect(request.getContextPath()+"/");
}
}
}
}
Application is working as desired also in future if I want to customize redirection based on roles, I can use same class.
It does redirect by defult, but there are a couple configuration options you can use to change this behavior. Both of them is defined on the AbstractAuthenticationTargetUrlRequestHandler which is the parent class of the two existing authentication success handler implementations (by default SavedRequestAwareAuthenticationSuccessHandler is used by the namespace configuration).
Set its targetUrlParameter property, so that it will check if the HTTP request has a parameter with that name. If so, it will redirect to the URL given in that request parameter.
Or set a custom redirectStrategy. The default implementation calls response.sendRedirect(), but you can change that as you like in your custom implementation.
You will have some difficulty though, because neither of these configuration points are exposed through the namespace configuration, so you will need to go a level deeper, and write the bean definitions manually.
The redirect is controlled by the Redirect Strategy definined in the redirectStrategy property of SimpleUrlAuthenticationSuccessHandler.
The Default for redirectStrategy is an instance of DefaultRedirectStrategy.
What you need to do is to implement you own redirectStrategy (implements RedirectStrategy).
And then configure it:
...
<bean id="usernamePasswordAuthenticationFilter">
...
<property name="authenticationSuccessHandler">
<bean
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="redirectStrategy">
<bean class="yourRedirectStrategy"/>
<property>
</bean>
</property>
</bean>

Spring Webflow 2 and bookmarkable URLs

Currently due to the Post/Redirect/Get pattern all flow urls are something like <site_url>/flow_name?execution=? and input GET parameters are not preserved. Thus the users can't copy the url, or bookmark it.
Any suggestions how could this be done neatly ?
We can bookmark a SWF based application's URL by customising FlowHandlerAdapter of SWF API.
Here is a sample:
My SWF configuration file would have:
<bean id="flowController" class="org.springframework.webflow.mvc.servlet.FlowController">
<property name="flowHandlerAdapter" ref="customFlowHandlerAdapter" />
</bean>
<bean id="customFlowHandlerAdapter" class="com.xyz.CustomFlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
<property name="flowUrlHandler" >
<bean class="com.xyz.CustomURLFlowHandler" />
</property>
</bean>
My CustomFlowHandlerAdapter would have:
public class CustomFlowHandlerAdapter extends FlowHandlerAdapter {
...
#Override
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
FlowHandler flowHandler = (FlowHandler) handler;
checkAndPrepare(request, response, false);
String flowExecutionKey = this.getFlowUrlHandler()
.getFlowExecutionKey(request);
if (flowExecutionKey != null)
try {
ServletExternalContext context = createServletExternalContext(
request, response);
FlowExecutionResult result = this.getFlowExecutor().resumeExecution(
flowExecutionKey, context);
handleFlowExecutionResult(result, context, request, response,
flowHandler);
} catch(org.springframework.webflow.execution.repository.NoSuchFlowExecutionException ex){
response.sendRedirect(request.getRequestURI());
} catch(org.springframework.webflow.execution.repository.BadlyFormattedFlowExecutionKeyException ex){
response.sendRedirect(request.getRequestURI());
} catch (FlowException e) {
handleFlowException(e, request, response, flowHandler);
}
....
Here Iam catching NoSuchFlowExecutionException and am redirecting to the exact flow URL without any parameters. Here you can capture and re-include your parameters
Thus I am able to bookmark my URL from any state(always flow starts from first) also I will be able to send my own parameters if required.
you can always use and bookmark a link to one of your flow's start point.for instance you can do <site_url>/flow_name?personId=123&projectId=456 assuming you have two inputs to your flow personId and projectId. But you need to know the url (you will have to give it to the users), you cannot use the one on your address bar.
even if you want to do that, you won't be able to use and bookmark a link to a specific state in your flow (unless you add some logic to the start of your flow to direct you to a specific event depending on the value of an input).

Resources