I would like to use the new Rest DSL in Apache Camel 2.14 to create a rest interface. I would like to use the Jetty component and I have a basic example setup like this:
Spring Security Configuration
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:spring-security="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
http://camel.apache.org/schema/spring-security http://camel.apache.org/schema/spring-security/camel-spring-security.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<spring-security:http auto-config="true" use-expressions="true" >
<spring-security:intercept-url pattern="/**" access="isFullyAuthenticated()"/>
<spring-security:http-basic></spring-security:http-basic>
</spring-security:http>
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="true"/>
<property name="decisionVoters">
<list>
<bean class="org.springframework.security.access.vote.RoleVoter"/>
</list>
</property>
</bean>
<spring-security:authentication-manager alias="authenticationManager">
<spring-security:authentication-provider user-service-ref="userDetailsService"/>
</spring-security:authentication-manager>
<spring-security:user-service id="userDetailsService">
<spring-security:user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN"/>
<spring-security:user name="user" password="user" authorities="ROLE_USER"/>
</spring-security:user-service>
<authorizationPolicy id="admin" access="ROLE_ADMIN"
authenticationManager="authenticationManager"
accessDecisionManager="accessDecisionManager"
xmlns="http://camel.apache.org/schema/spring-security"/>
<authorizationPolicy id="user" access="ROLE_USER"
xmlns="http://camel.apache.org/schema/spring-security"/>
Camel Route Configuration
restConfiguration().component("jetty").host("0.0.0.0").port(24999).bindingMode(RestBindingMode.json).dataFormatProperty("prettyPrint", "true");
rest("address").description("Contains services for addresses").
consumes("application/json").
produces("application/json").
get().
route().policy("admin").
to("bean:restAddressApi?method=queryAddress").endRest();
When I try to access this protected URL using wget with this:
wget --http-user=admin --http-password=admin http://localhost:24999/address/
Then I get this error in the console:
org.apache.camel.CamelAuthorizationException: Cannot find the Authentication instance.. Exchange[Message: [Body is null]]
at org.apache.camel.component.spring.security.SpringSecurityAuthorizationPolicy.beforeProcess(SpringSecurityAuthorizationPolicy.java:72)
at org.apache.camel.component.spring.security.SpringSecurityAuthorizationPolicy$AuthorizeDelegateProcess.process(SpringSecurityAuthorizationPolicy.java:120)
at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:416)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:191)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:118)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:80)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:191)
at org.apache.camel.component.jetty.CamelContinuationServlet.service(CamelContinuationServlet.java:150)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:668)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:684)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:503)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1086)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:429)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1020)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.handler.ResourceHandler.handle(ResourceHandler.java:406)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.Server.handle(Server.java:370)
at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:494)
at org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:971)
at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:1033)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:644)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:696)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:53)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
at java.lang.Thread.run(Thread.java:745)
What am I missing in my config to get this working?
Two options.
You can use basic auth with jetty rest DSL. I just spent some time working with basic auth, a jetty rest DSL service, and camel 2.15. I wrote up my experiences here: http://www.mooreds.com/wordpress/archives/2065 . I found the camel-jetty wiki page a bit out of date, full code here: https://github.com/mooreds/camel-rest-jetty-auth
You can use spring security (at least with version 2.15.2 of camel and 4.0.1.RELEASE of spring security, which is what I am using). To do so, you need to make sure you create a processor class, as outlined here: http://camel.apache.org/spring-security.html#SpringSecurity-Authentication and put it in front of every route before you call the policy. You also need to create an exception handler to present a nice message to an unauthorized user--here's what I did in my java routebuilder: onException(org.apache.camel.CamelAuthorizationException.class).handled(true).transform(simple("Access Denied with the Policy of ${exception.policyId} !")).setHeader(Exchange.HTTP_RESPONSE_CODE, simple("401"));
Here is how I solved it using Apache Camel 2.15.2. I use this backend together with an AngularJS frontent where I use an httpInterceptor to add the authenitcation header.
I use JWT to store a tenantId in the authorization header. This tenantId is used together with Hibernate and its Multi Tenancy Support.
Front end login controller, using AngularJs and written in TypeScript:
/// <reference path="../reference.ts"/>
module Controllers {
export class Credentials {
username:string;
password:string;
}
export interface ILoginControllerScope extends ng.IScope {
credentials:Credentials;
login:()=>void;
}
export class LoginController {
private _scope:ILoginControllerScope;
static $inject = ['$scope', '$http', 'UserService', '$location'];
constructor($scope:ILoginControllerScope, $http:ng.IHttpService, UserService:UserService, $location:ng.ILocationService) {
this._scope = $scope;
$scope.credentials = new Credentials();
$scope.login = ()=> {
$http.post('/api/authenticate', $scope.credentials)
.success(function (data:string, status:any, headers:any, config:any) {
// Remove all " characters from string.
data = data.replace(/"/gi, '');
UserService.setSecurityToken(data);
$location.path('/');
})
.error(function (data:any, status:any, headers:any, config:any) {
});
}
}
}
}
#Override
public void process(Exchange exchange) throws Exception {
Map<String, String> credentials = exchange.getIn().getBody(HashMap.class);
String username = credentials.get("username");
String password = credentials.get("password");
// Login logic that returns object() containing user info
exchange.getIn().setBody(jwtService.generateToken(tenantConfiguration));
}
}
}
This is the class JwtService that is responsible for creating a JWT Token:
package com.me.services;
import com.me.TenantConfiguration;
import com.google.common.collect.Maps;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Random;
#Service
public class JwtService {
public static final String TENANT_ID = "tenant_id";
public static final String ROLE = "role";
#Value("${jwt.subject}")
private String jwtSubject;
private byte[] signingKey;
private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
public SignatureAlgorithm getSignatureAlgorithm() {
return signatureAlgorithm;
}
public byte[] getSigningKey() {
if (this.signingKey == null) {
// Generate new signingKey
Random random = new SecureRandom();
signingKey = new byte[64];
random.nextBytes(signingKey);
}
return this.signingKey;
}
public String getJwtSubject() {
return jwtSubject;
}
public String generateToken(TenantConfiguration tenantConfiguration){
Map<String, Object> claims = Maps.newHashMap();
claims.put(TENANT_ID, tenantConfiguration.getSessionId());
String token = Jwts.builder().setSubject(this.getJwtSubject()).setClaims(claims).signWith(this.getSignatureAlgorithm(), this.getSigningKey()).compact();
return token;
}
}
To use this in a route I define it like this:
rest("api/messages").description("Restful API for messages").
get().id("GetMessages").route().to("springSecurityContextLoader").policy(authorizationPolicy).to("bean:restMessageApi?method=getAllMessages").endRest().
SpringSecurityContextloader
#Service
public class SpringSecurityContextLoader implements Processor {
#Inject
private JwtService jwtService;
#Override
public void process(Exchange exchange) throws Exception {
String authorization = exchange.getIn().getHeader("Authorization", String.class);
Jwt jwt = Jwts.parser().setSigningKey(jwtService.getSigningKey()).parse(authorization);
Map<String, Object> claims = (Map<String, Object>) jwt.getBody();
String tenantId = claims.get(JwtService.TENANT_ID).toString();
Authentication authentication = new PreAuthenticatedAuthenticationToken(tenantId, "something", Lists.newArrayList(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
Taken from https://camel.apache.org/jetty.html
Jetty handlers and security configuration
You can configure a list of Jetty handlers on the endpoint, which can be useful for enabling advanced Jetty security features. These handlers are configured in Spring XML as follows:
<-- Jetty Security handling -->
<bean id="userRealm" class="org.mortbay.jetty.plus.jaas.JAASUserRealm">
<property name="name" value="tracker-users"/>
<property name="loginModuleName" value="ldaploginmodule"/>
</bean>
<bean id="constraint" class="org.mortbay.jetty.security.Constraint">
<property name="name" value="BASIC"/>
<property name="roles" value="tracker-users"/>
<property name="authenticate" value="true"/>
</bean>
<bean id="constraintMapping" class="org.mortbay.jetty.security.ConstraintMapping">
<property name="constraint" ref="constraint"/>
<property name="pathSpec" value="/*"/>
</bean>
<bean id="securityHandler" class="org.mortbay.jetty.security.SecurityHandler">
<property name="userRealm" ref="userRealm"/>
<property name="constraintMappings" ref="constraintMapping"/>
</bean>
Related
From what I've read of the Spring #ContextConfiguration annotation, it's possible to load multiple XML context files, or multiple JavaConfig classes. What I need is to load from one XML context file and one class. All the examples I've seen either load all XML, or all classes, but not both.
I'm trying to do this because I want my test class, which is just there to verify expected Spring wiring, to load my default applicationContext.xml file (presently just a copy stored in "src/test/resources, and trying to figure out how to directly specify the default one) along with a JavaConfig class that specifies some JNDI resources that need to be available. For the purposes of my test, I only need to set those JNDI resources to dummy strings, but I'd really like to specify them in an inline static class instead of an external XML file, because my tests are going to have to verify that some settings are equal to those dummy strings, and it's more maintainable if both the values and the checks are in the same file.
What I have so far, and what I've tried, can be illustrated with this:
#RunWith(SpringRunner.class)
#ContextConfiguration(value = {"/testApplicationContext.xml", "/testResources.xml"})
//#ContextHierarchy({
// #ContextConfiguration("/testApplicationContext.xml"),
// #ContextConfiguration(classes = SpringWiringTest.Config.class)
//})
#TestPropertySource(properties = { "env = tomcat", "doNotifications = false" })
public class SpringWiringTest {
And this at the end of the class:
#Configuration
public static class Config {
#Bean public String uslDatasourcesList() { return "abc"; }
#Bean public String atgDatasourcesList() { return "abc"; }
#Bean public String uslTableNamePrefixsList() { return "abc"; }
#Bean public String atgTableNamePrefixsList() { return "abc"; }
#Bean public String doNotifications() { return "false"; }
#Bean public DataSource abc() { return new DriverManagerDataSource(); }
}
If I comment out the first #ContextConfiguration and comment back in the #ContextHierarchy block, I get an error like this:
Error creating bean with name 'uslDatasourcesList': Invocation of init
method failed; nested exception is
javax.naming.NoInitialContextException: Need to specify class name in
environment or system property, or as an applet parameter, or in an
application resource file: java.naming.factory.initial
Update:
Using the guideline of picking either JavaConfig or XML as the "entry point" to configuration, here are some modified excerpts that show what I'm trying:
#RunWith(SpringRunner.class)
#ContextConfiguration
//#ContextConfiguration(value = {"file:src/main/webapp/WEB-INF/applicationContext.xml", "/testResources.xml"})
#TestPropertySource(properties = { "env = tomcat", "doNotifications = false" })
public class SpringWiringTest {
...
#BeforeClass
public static void setup() throws Exception {
SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
DataSource ds = new DriverManagerDataSource();
builder.bind("java:comp/env/abc", ds);
}
...
#Configuration
#ImportResource("file:src/main/webapp/WEB-INF/applicationContext.xml")
public static class Config {
#Bean public String uslDatasourcesList() { return "abc"; }
#Bean public String atgDatasourcesList() { return "abc"; }
#Bean public String uslTableNamePrefixsList() { return "abc"; }
#Bean public String atgTableNamePrefixsList() { return "abc"; }
#Bean public String doNotifications() { return "false"; }
#Bean public DataSource abc() { return new DriverManagerDataSource(); }
}
}
When I run my test, the bottom "Caused by" in the exception says this:
Caused by: javax.naming.NameNotFoundException: Name
[uslDatasourcesList] not bound; 1 bindings: [java:comp/env/abc]
In the alternate version, using the commented-out "#ContextConfiguration" (and commenting out the Config class and its annotations), this error does not occur.
Note that this the meat of my "testResources.xml" file:
<bean id="uslDatasourcesList" class="java.lang.String"> <constructor-arg value="abc"/> </bean>
<bean id="atgDatasourcesList" class="java.lang.String"> <constructor-arg value="abc"/> </bean>
<bean id="uslTableNamePrefixList" class="java.lang.String"> <constructor-arg value="abc"/> </bean>
<bean id="atgTableNamePrefixList" class="java.lang.String"> <constructor-arg value="abc"/> </bean>
<bean id="doNotifications" class="java.lang.String"> <constructor-arg value="false"/> </bean>
<bean id="abc" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
</bean>
Note that the bean mentioned in the error message, "uslDatasourcesList" is defined in both versions, but it's not working in the version with JavaConfig and XML mixed.
It almost appears that the beans in the "#ImportResource" annotation are evaluated on their own, before the beans declared in the JavaConfig class are merged into it.
This is clearly documented in the Spring Reference Manual in the section named Mixing XML, Groovy scripts, and annotated classes.
In summary, ...
... you will have to pick one as the entry point, and that one will have to include or import the other.
Thus, the following should hopefully solve your problem.
#RunWith(SpringRunner.class)
#ContextConfiguration
#TestPropertySource(properties = { "env = tomcat", "doNotifications = false" })
public class SpringWiringTest {
// ...
#Configuration
#ImportResource({"/testApplicationContext.xml", "/testResources.xml"})
static class Config {
// ...
}
}
I am trying to implement XML validation using Spring Integration <int-xml:validating-filter />. I followed the discussion in usage of spring integration's schema validator?. The problem statement is the same but with an additional parameter. Instead to hard coding the value in schema-location="xyz.xsd", rather I want to dynamically select the appropriate xsd file for respective incoming xml or DOMSource inputs.
I also followed http://forum.spring.io/forum/spring-projects/integration/121115-dynamic-schema-location-for-xml-validating-filter-component where Gary Russell mentioned:
There's no direct support for dynamic schemas, but you can provide a
custom XmlValidator using the xml-validator attribute (mutually
exclusive with schema location)
Once you've introspected your document to find the schema you wish to
validate against, simply delegate to a validator that has been
configured to validate against that schema.
You can use a XmlValidatorFactory to create each validator; see the
XmlValidatingMessageSelector for how to create a validator, once you
know the schema location
Since the comments dates back to the year 2012, is there an approach now in spring integration to validate input xml by dynamically selecting appropriate schema? If not can anyone provide an example on how to implement?
Following is my spring integration configuration:
<int:gateway id="applicationServiceGateway" service-interface="abc.IGateway"
default-request-channel="applicationRequestChannel" default-reply-channel="applicationResponseChannel"
error-channel="errorProcessingChannel" />
<int:chain id="serviceRequestValidation" input-channel="applicationRequestChannel" output-channel="responseChannel">
<!-- How to do -->
<int-xml:validating-filter xml-validator="xmlValidator"
schema-type="xml-schema"
throw-exception-on-rejection="true" />
<int:service-activator id="schematronValidationActivator" ref="schematronValidator" method="validate" />
</int:chain>
<bean id="xmlValidator" class="abc.validator.DomSourceValidator" />
Here is my Validator class defined:
import org.springframework.xml.validation.ValidationErrorHandler;
import org.springframework.xml.validation.XmlValidator;
import org.xml.sax.SAXParseException;
public class DomSourceValidator implements XmlValidator {
#Override
public SAXParseException[] validate(Source source) throws IOException {
/* How to implement this method?
Using XPath I can identify the root node from 'source' and then load
the appropriate XSD file. But don't know how to proceed
or what should be 'return'(ed) from here.
Any example is much appreciated.
*/
return null;
}
#Override
public SAXParseException[] validate(Source source, ValidationErrorHandler errorHandler) throws IOException {
// TODO Auto-generated method stub
return null;
}
}
Which is the best way of implementing the XML validator using Spring Integration?
There have been no changes since that comment.
As I said there, your validator needs to use XmlValidatorFactory to create a validator for each schema; then call a specific validator for each message; something like:
String schema = determineSchema(source);
XmlValidator val = lookupValidatorForSchema(schema);
if (val == null) {
// create a new one and add it to the map.
}
return val.validate(source);
If it helps other folks who are trying to do the same
Based on Gary's suggestion, I have come out with an implementation of XmlValidator by dynamically identifying input XML and then selecting appropriate Schema file to apply the validation.
Below is my spring integration configuration:
<int:gateway id="applicationServiceGateway" service-interface="abc.IGateway" default-request-channel="applicationRequestChannel" default-reply-channel="applicationResponseChannel" error-channel="errorProcessingChannel" />
<int:chain id="serviceRequestValidation" input-channel="applicationRequestChannel" output-channel="responseChannel">
<int-xml:validating-filter xml-validator="xmlValidator"
schema-type="xml-schema"
throw-exception-on-rejection="true" /> <!-- a MessageRejectedException is thrown in case validation fails -->
<int:service-activator id="schematronValidationActivator" ref="schematronValidator" method="validate" />
</int:chain>
<bean id="xmlValidator" class="abc.validator.DomSourceValidator">
<constructor-arg>
<map key-type="java.lang.String" value-type="java.lang.String">
<entry key="OTA_AirAvailRQ" value="common/schemas/FS_OTA_AirAvailRQ.xsd" />
<entry key="OTA_AirBookModifyRQ" value="common/schemas/FS_OTA_AirBookModifyRQ.xsd" />
<entry key="OTA_AirBookRQ" value="common/schemas/FS_OTA_AirBookRQ.xsd" />
</map>
</constructor-arg>
</bean>
To demonstrate I have used the OTA schema files to construct a map as constructor-arg. The map key is the root node from the XML file from the gateway and value is the location of the xsd file; and form the key-value pair map.
Refer to the below implementation class how this map is being used to identify the input XML and apply the validation.
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
import org.springframework.xml.validation.ValidationErrorHandler;
import org.springframework.xml.validation.XmlValidator;
import org.springframework.xml.validation.XmlValidatorFactory;
import org.xml.sax.SAXParseException;
public class DomSourceValidator implements XmlValidator {
private static final Log LOGGER = LogFactory.getLog(DomSourceValidator.class);
private Map<String, String> schemaMap;
private static Map<String, XmlValidator> validatorMap = new HashMap<>();
public DomSourceValidator(Map<String, String> schemaMap) {
this.schemaMap = schemaMap;
}
#PostConstruct
private void init() throws IOException {
LOGGER.info("Constructing Validators from schema resource list ...");
Assert.notEmpty(schemaMap, "No schema resource map found");
if (validatorMap.isEmpty()) {
XmlValidator validator = null;
for (Map.Entry<String, String> entry : schemaMap.entrySet()) {
validator = createValidatorFromResourceUri(entry.getValue());
validatorMap.put(entry.getKey(), validator);
}
}
}
#Override
public SAXParseException[] validate(Source source) throws IOException {
Assert.notNull(schemaMap, "No validator(s) defined");
XmlValidator validator = lookupValidator(source);
return validator.validate(source);
}
#Override
public SAXParseException[] validate(Source source, ValidationErrorHandler errorHandler) throws IOException {
// Skip implementation
return null;
}
private XmlValidator lookupValidator(Source source) {
String reqType = determineRequestType(source);
LOGGER.info("Loading validator for type: " + reqType);
XmlValidator xmlValidator = validatorMap.get(reqType);
Assert.notNull(xmlValidator, "No validator found for type: " + reqType);
return xmlValidator;
}
private String determineRequestType(Source source) {
if (source instanceof DOMSource) {
return ((DOMSource) source).getNode().getFirstChild().getNodeName();
}
return null;
}
private XmlValidator createValidatorFromResourceUri(String schemaResource) throws IOException {
Assert.notNull(schemaResource);
return XmlValidatorFactory.createValidator(new ClassPathResource(schemaResource), XmlValidatorFactory.SCHEMA_W3C_XML);
}
}
As soon as the spring bean id="xmlValidator" is initialized, the #PostConstruct kicks in to create Validator instances using XmlValidatorFactory from the resource URIs to have a pre-initialized validators.
If there is a validation error, a org.springframework.integration.MessageRejectedException: Message was rejected due to XML Validation errors is thrown (ref. throw-exception-on-rejection="true" in the <int-xml:validating-filter />).
The above implementation works perfectly fine for me. One can customize it further, or post another version to achieve the same.
Note
Instead of using a <int-xml:validating-filter />, one can also use a <int:service-activator /> in the <int-chain />, as logically <int-xml:validating-filter /> does not actually do any filter logic. But it serves the purpose.
I want to use read files in one directory and move to another directory, I am using spring-integration for this. I want to execute the task(move files to output directory) when request coming from another system. I do not want to run file mover repeatedly, Is there a way to do this in Spring Integration?
Thank you in advance,
Udeshika
There are some tricks you can do with pollers, such as the FireOnceTrigger I mentioned in this answer. But in this case, probably the simplest solution is, instead of using an inbound adapter, define a <bean/> of type FileReadingMessageSource in your context; get a reference to it in your main() (context.getBean(FileReadingMessagesource.class)). Keep calling receive() and send the message received to the first channel in your flow (or use a <gateway/>.
When receive() returns null, exit.
Example for read specified file from samba share without poller:
FileService.java bean:
import org.springframework.context.ApplicationContext;
import org.springframework.integration.smb.session.SmbSession;
import org.springframework.integration.smb.session.SmbSessionFactory;
...
#Component
public class FileService {
private #Autowired ApplicationContext appContext;
...
private byte[] getFileContent( final String filename ) {
final SmbSessionFactory smbSessionFactory = (SmbSessionFactory) appContext.getBean("MySmbSession");
final SmbSession smbSession = smbSessionFactory.getSession();
final ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
smbSession.read("mysharefolder\" + filename, output);
} catch (final IOException e) {
throw new IllegalStateException(e);
}
return output.toByteArray();
}
}
spring_integration.xml config file:
...
<!-- smb configuration for samba smb://[[[domain;]username[:password]#]server[:port]/[[share/[dir/]file]]][?[param=value[param2=value2[...]]] -->
<bean id="MySmbSession" class="org.springframework.integration.smb.session.SmbSessionFactory">
<property name="host" value="${samba.host}"/>
<property name="port" value="${samba.port}"/>
<property name="username" value="${samba.username}"/>
<property name="password" value="${samba.password}"/>
<property name="shareAndDir" value="${samba.shareAndDir}"/>
<property name="replaceFile" value="true"/>
</bean>
...
application.yml config file:
samba:
host : '${SAMBA_HOST:172.16.0.7}'
hostAlias : '${SAMBA_HOST_ALIAS:mywinsrv}'
port : '${SAMBA_PORT:445}'
username : '${SAMBA_USER_NAME:DMZ\myusername}'
password : '${SAMBA_USER_PASS:mypassword}'
shareAndDir : '${SAMBA_SHARE_AND_DIR:myDocs$\myproject\}'
1) Is there a way to unmarshall MTOM message using Camel Spring-WS component?
2) I tried with Camel JAXB dataformat. It didn't work. Datahandler doesn't have any content. Does JAXB dataformat support MTOM?
<dataFormats>
<jaxb id="jaxb" contextPath="com.mycompany.hr.jaxb"/>
</dataFormats>
<route>
<from uri="spring-ws:rootqname:{http://mycompany.com/hr/schemas}HolidayRequest?endpointMapping=#endpointMapping" />
<unmarshal ref="jaxb"/>
<process ref="testProcessor" />
</route>
3) I thought MTOM is not enabled in JAXB dataformat. So I created a custom dataformat using MTOM enabled JAXB2Marshaller. But still facing the same issue.
import java.io.InputStream;
import java.io.OutputStream;
import javax.xml.transform.Source;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
public class MtomDataFormat implements DataFormat {
public void marshal(Exchange arg0, Object arg1, OutputStream arg2)
throws Exception {
// TODO Auto-generated method stub
}
public Object unmarshal(Exchange exchange, InputStream is) throws Exception {
Source source = exchange.getContext().getTypeConverter().mandatoryConvertTo(Source.class, is);
Jaxb2Marshaller mar = new Jaxb2Marshaller();
mar.setContextPath("com.mycompany.hr.jaxb");
mar.setMtomEnabled(true);
return mar.unmarshal(source);
}
}
Spring configuration
<bean id="endpointMapping"
class="org.apache.camel.component.spring.ws.bean.CamelEndpointMapping">
</bean>
<bean id="testProcessor" class="TestProcessor" />
<bean id="mtomDataFormat" class="MtomDataFormat" />
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="spring-ws:rootqname:{http://mycompany.com/hr/schemas}HolidayRequest?endpointMapping=#endpointMapping" />
<unmarshal ref="mtomDataFormat"/>
<process ref="testProcessor" />
</route>
</camelContext>
I am relatively new to the Spring Framework and Spring security.
I have used a custom authentication scheme, HTML:
<form action="j_spring_security_check">
<input type="text" name="j_username" value="abc"/>
<input type="text" name="j_password" value="abc"/>
<input type="text" name="myCustom1" value="pqr"/> <!-- maybe type="hidden" -->
<input type="text" name="myCustom2" value="pqr"/> <!-- maybe type="hidden" -->
</form>
and the corresponding code:
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider
{
#Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken)
throws AuthenticationException
{
System.out.println("Method invoked : additionalAuthenticationChecks isAuthenticated ? :"+usernamePasswordAuthenticationToken.isAuthenticated());
}
#Override protected UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException
{
System.out.println("Method invoked : retrieveUser");
//I have Username,password:
//HOW CAN I ACCESS "myCustom1", "myCustom2" here ?
}
}
All above are great and perfect solutions.
But I have used a workaround kind of solution which works perfectly fine.
Used multitenant id for ThreadLocal
package com.mypackage.servlet;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.util.Assert;
public class ThreadLocalContextUtil implements Filter{
private static final ThreadLocal<Object> contextHolder =
new ThreadLocal<Object>();
public static void setTenantId(Object tenantId) {
Assert.notNull(tenantId, "customerType cannot be null");
contextHolder.set(tenantId);
}
public static Object getTenantId() {
return contextHolder.get();
}
public static void clearTenant() {
contextHolder.remove();
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// Set the tenant Id into a ThreadLocal object
ThreadLocalContextUtil.setTenantId(request);
if(chain != null)
chain.doFilter(request, response);
else {
//error
}
}
public void init(FilterConfig filterconfig) throws ServletException {
}
}
spring security xml
<security:http auto-config="true" use-expressions="true" access-denied-page="/forms/auth/403" >
<security:custom-filter before="FIRST" ref="tenantFilter" />
......
</security:http>
Access request object in your Authentication Class
HttpServletRequest currRequest = (HttpServletRequest) ThreadLocalContextUtil.getTenantId();
Then use the request object to get your custom parameters
If you need to use additional form parameters in order to manipulate the username and password, you can implement your own AuthenticationProcessingFilter
http://static.springsource.org/spring-security/site/apidocs/org/springframework/security/ui/webapp/AuthenticationProcessingFilter.html
This class will have full access to the HttpRequest and therefore all the additional parameters you submit. If your goal is to somehow use these values to modify the username and password, this is where you would do it.
The trick here is that you need to create a new AuthenicationToken (maybe) extending UsernameAndPasswordAuthenicationToken and as #emills says you need to then implement a new AuthenciationProcessingFilter to map the request values to the token and submit these to the AuthenicationManager.
Basically there are a couple of parts to implementing a custom authenication chain in spring-security
AuthenicationToken - details of the authenication request and it's result, ie contains credentials you require to authenticate
AuthenicationProvider - registered with the AuthenicationManager, accepts your AuthenicationToken and validates the user and returns a token with the granted authorities set
AuthenciationFilter - doesn't actually have to be a filter just using AbstractProcessingFilter will make your life a little easier
I would go this way:
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
...
<property name="authenticationDetailsSource">
<bean class="org.acegisecurity.ui.AuthenticationDetailsSourceImpl">
<property name="clazz"
value="com.MyAuthenticationDetails"/>
</bean>
</property>
</bean>
This is the class that holds the properties:
package com;
import javax.servlet.http.HttpServletRequest;
import org.acegisecurity.ui.WebAuthenticationDetails;
public class MyAuthenticationDetails extends WebAuthenticationDetails {
public MyAuthenticationDetails() {
super();
}
//This constructor will be invoqued by the filter
public MyAuthenticationDetails(HttpServletRequest request) {
super(request);
this.myCustom1 = request.getParameter("myCustom1");
}
public String getMyCustom1() {
return myCustom1;
}
private String myCustom1;
}
Now you have the username, password and the details.
I've done a similar thing, but different then anyone has suggested here. I am not saying this is the "right" way to do it - but it's worked very well for me. In the Principal object there is the user and there is also a Details object in the AuthenticationToken that you can store a Map(String, String) of other login info.
public class RequestFormDeatils extends SpringSecurityFilter {
protected void doFilterHttp(HttpServletRequest request, ...) {
SecurityContext sec = SecurityContextHolder.getContent();
AbstractAuthenticationToken auth = (AbstractAuthenticationToken)sec.getAuthentication();
Map<String, String> m = new HashMap<String, String>;
m.put("myCustom1", request.getParamtere("myCustom1"));
m.put("myCustom2", request.getParameter("myCustom2"));
auth.setDetails(m);
}
Now anywhere in your code you get use the SecurityContext to propagate this security related info without having to couple it to your UserDetails object, or pass it as arguments. I do this code in a SecurityFilter at the end of the Spring Security Filter chain.
<bean id="requestFormFilter" class="...RequestFormDetails">
<custom-filter position="LAST" />
</bean>