How to configure Spring data SolrCloud connection with Basic Authentication - basic-authentication

I have configure Solr 6.2.1 as SolrCloud. Later I have Configured Basic Authentication.
I am going to configure Spring data solr 2.0.4.RELEASE with Solrj 6.2 and this is my code:
#Configuration
#EnableSolrRepositories(basePackages = { "ir.saeed.server.solr" }, multicoreSupport = true)
public class SearchContext {
#Value("${solr.host}")
private String host;
#Value("${solr.port}")
private Integer port;
#Value("${solr.username}")
private String username;
#Value("${solr.password}")
private String password;
#Value("${zkHost}")
private String zkHost;
#Value("${solr.coreName}")
private String collectionName;
#Bean
public SolrTemplate solrTemplate() {
return new SolrTemplate(solrClientFactory());
}
#Bean
public BasicCredentialsProvider credentialsProvider() {
BasicCredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
return provider;
}
#Bean
public SolrClientFactory solrClientFactory() {
return new HttpSolrClientFactory(solrClient(), "", credentialsProvider().getCredentials(AuthScope.ANY), "BASIC");
}
#Bean
public SolrClient solrClient() {
return new CloudSolrClient.Builder().withZkHost(zkHost).build();
}
}
But when i run my web application this Exception occures:
10:51:48,110 org.springframework.data.solr.UncategorizedSolrException: nested exception is java.lang.NullPointerException
10:51:48,111 at org.springframework.data.solr.core.SolrTemplate.execute(SolrTemplate.java:172)
10:51:48,111 at org.springframework.data.solr.core.SolrTemplate.executeSolrQuery(SolrTemplate.java:509)
10:51:48,111 at org.springframework.data.solr.core.SolrTemplate.query(SolrTemplate.java:504)
10:51:48,111 at org.springframework.data.solr.core.SolrTemplate.doQueryForPage(SolrTemplate.java:338)
10:51:48,111 at org.springframework.data.solr.core.SolrTemplate.queryForPage(SolrTemplate.java:350)
How can I resolve the issue?
I think my Configuration is incorrect

I found a workaround for those who has same issue
Extends your own HttpSolrClientFactory. the problem caused by LBHttpSolrClient httpClient not being setup properly. correct setup should be like the following block if (solrClient instanceof LBHttpSolrClient) {...}
AuthHttpSolrClientFactory.java
#SuppressWarnings("deprecation")
public class AuthHttpSolrClientFactory extends HttpSolrClientFactory {
public AuthHttpSolrClientFactory(SolrClient solrClient, String core, Credentials credentials, String authPolicy) {
super(solrClient, core, credentials, authPolicy);
Assert.notNull(solrClient, "solrClient must not be null");
if (authPolicy != null) {
Assert.hasText(authPolicy);
}
appendBasicAuthentication(credentials, authPolicy, this.getSolrClient());
}
private void appendBasicAuthentication(Credentials credentials, String authPolicy, SolrClient solrClient) {
if( credentials != null) {
if (solrClient instanceof HttpSolrClient) {
HttpSolrClient httpSolrClient = (HttpSolrClient) solrClient;
if (assertHttpClientInstance(httpSolrClient.getHttpClient())) {
AbstractHttpClient httpClient = (AbstractHttpClient) httpSolrClient.getHttpClient();
httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), credentials);
httpClient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, Arrays.asList(authPolicy));
}
}
else if (solrClient instanceof LBHttpSolrClient) {
LBHttpSolrClient lbhttpSolrClient = (LBHttpSolrClient) solrClient;
if (assertHttpClientInstance(lbhttpSolrClient.getHttpClient())) {
AbstractHttpClient httpClient = (AbstractHttpClient) lbhttpSolrClient.getHttpClient();
httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), credentials);
httpClient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, Arrays.asList(authPolicy));
}
}
}
}
private boolean assertHttpClientInstance(HttpClient httpClient) {
Assert.isInstanceOf(AbstractHttpClient.class, httpClient,
"HttpClient has to be derivate of AbstractHttpClient in order to allow authentication.");
return true;
}
}
beans-solr.xml
<solr:solr-client id="solrClient" url="${solr.host}" />
<bean id="credentials" class="org.apache.http.auth.UsernamePasswordCredentials">
<constructor-arg type="java.lang.String" value="${solr.credentials}"/>
</bean>
<bean id="solrClientFactory" class="com.example.solr.AuthHttpSolrClientFactory" scope="singleton">
<constructor-arg ref="solrClient" />
<constructor-arg name="core">
<null />
</constructor-arg>
<constructor-arg ref="credentials" />
<constructor-arg type="java.lang.String" value="BASIC"/>
</bean>
<bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate" scope="singleton">
<constructor-arg ref="solrClientFactory" />
<constructor-arg name="requestMethod">
<value type="org.springframework.data.solr.core.RequestMethod">POST</value>
</constructor-arg>
</bean>

Yes, your configuration seems incorrect. I had same issue like yours
I would like to use apache solr version 6.6.0 and spring data solr version 2.0.8 (bought by spring boot starter). It turned out that the version from spring data solr doesn't have support for apache solr version > 5, because when you traced
at org.springframework.data.solr.core.SolrTemplate.execute(SolrTemplate.java:172), it's very clear that when the solrTemplate is going to createClientForCore, it will clone from the cloudSolrClient that we have been configured
the problem is in String zkHost = (String)readField(solrClient, "zkHost");
* it will return null since in apache solr version > 5, the zkHost is stored in "clusterStateProvider" not in the same level as "cloudSolrClient"
Solved:
If you want keep using spring data solr version 2, you need to downgrade the apache solr version

There are several reasons why your code is not working, but they are lied mainly to spring-data-solr missing features.
First of all spring-data-solr version 2.0.4 does not have support for Solr 5 (cloud) features.
So this is the reason why you are getting the NullPointerException in the method org.springframework.data.solr.server.support.SolrClientUtils#cloneLBHttpSolrClient
I've tried to see whether it the scenario exposed by you works with the latest SNAPSHOT (2.1.0-SNAPSHOT) of spring-data-solr and after a few modifications on the SolrContext spring configuration class :
#Configuration
#EnableSolrRepositories(basePackages = {"com.acme.solr"})
// notice that the multicoresupport is left false
// see org.springframework.data.solr.repository.config.SolrRepositoryConfigExtension#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource) for details
public class SolrContext {
#Bean
public Credentials credentials(#Value("${solr.username}") String username, #Value("${solr.password}") String
password) {
return new UsernamePasswordCredentials(username, password);
}
#Bean
public BasicCredentialsProvider credentialsProvider(Credentials credentials) {
BasicCredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(AuthScope.ANY, credentials);
return provider;
}
#Bean
public SolrClientFactory solrClientFactory(SolrClient solrClient, Credentials credentials) {
return new HttpSolrClientFactory(solrClient, "", credentials, "BASIC");
}
// create a solrtemplate bean, so that it is used in
// org.springframework.data.solr.repository.support.SolrRepositoryFactoryBean#doCreateRepositoryFactory method
// for using org.springframework.data.solr.repository.support.SolrRepositoryFactory#SolrRepositoryFactory(org.springframework.data.solr.core.SolrOperations) constructor
#Bean
public SolrTemplate solrTemplate(SolrClientFactory solrClientFactory){
return new SolrTemplate(solrClientFactory);
}
#Bean
public CloudSolrClient solrClient(#Value("${zkHost}") String zkHost) {
CloudSolrClient solrClient = new CloudSolrClient.Builder().withZkHost(zkHost).build();
solrClient.setDefaultCollection("gettingstarted");
return solrClient;
}
}
I still received a 401 authentication issue when performing a solr request (when the basic authentication was enabled on solr).
In a vanilla solrj application, this is how you'd make an authenticated request:
CloudSolrClient solr = new CloudSolrClient.Builder()
.withZkHost("localhost:9983")
.build();
SolrQuery query = new SolrQuery();
query.setQuery("*:*");
SolrRequest<QueryResponse> req = new QueryRequest(query);
req.setBasicAuthCredentials("solr", "SolrRocks");
QueryResponse rsp = req.process(solr, "gettingstarted");
System.out.println("numFound: " + rsp.getResults().getNumFound());
When looking whether the method SolrRequest#setBasicAuthCredentials(String, String) is used in the code of spring-data-solr I didn't notice anywhere this method being used. So, it is very likely, that this feature is not implemented yet not even on the SNAPSHOT build of the spring-data-solr.
I've created a feature request on spring-data-solr project to add support for this functionality.

Had the same issue usind Solr 7.7 and spring-boot-starter-data-solr 2.1.14 (uses spring-data-solr-4.0.17.RELEASE)
Tried a few ways, including creating custom HttpSolrClientFactory. It works but actually makes 2 calls to Solr and the first one returns
401 Unauthorized.
I fix the problem extending the CloudSolrClient (trying to do correct auth as it described in Basic Auth with SolrJ)
It makes only one call to Solr and uses Basic auth
public class BasicAuthCloudSolrClient extends CloudSolrClient {
private final Credentials credentials;
/**
* Create a new client object that connects to Zookeeper using BASIC Authentication and is always aware
* of the SolrCloud state. If there is a fully redundant Zookeeper quorum and
* SolrCloud has enough replicas for every shard in a collection, there is no
* single point of failure. Updates will be sent to shard leaders by default.
*
* #param builder a {#link BasicAuthCloudSolrClient.Builder} with the options used to create the client.
*/
protected BasicAuthCloudSolrClient(Builder builder) {
super(builder);
this.credentials = builder.credentials;
}
#Override
public QueryResponse query(String collection, SolrParams params, SolrRequest.METHOD method)
throws
SolrServerException, IOException {
QueryRequest request = new QueryRequest(params, method);
request.setBasicAuthCredentials(credentials.getUserPrincipal().getName(),
credentials.getPassword());
return request.process(this, collection);
}
/**
* Constructs {#link BasicAuthCloudSolrClient} instances from provided configuration.
*/
public static class Builder extends CloudSolrClient.Builder {
protected Credentials credentials;
/**
* #deprecated use other constructors instead. This constructor will be changing visibility in an upcoming release.
*/
#Deprecated
public Builder() {
}
/**
* Provide a series of ZK hosts which will be used when configuring {#link CloudSolrClient} instances.
*
* #param zkHosts a List of at least one ZooKeeper host and port (e.g. "zookeeper1:2181")
* #param credentials a credentials to connect to Solr.
*/
public Builder(List<String> zkHosts, Credentials credentials) {
super(zkHosts, empty());
this.credentials = credentials;
}
/**
* Create a {#link CloudSolrClient} based on the provided configuration.
*/
public BasicAuthCloudSolrClient build() {
if (Objects.isNull(credentials)) {
throw new IllegalArgumentException(
"Credentials must be provided to initialize BasicAuthCloudSolrClient");
}
if (stateProvider == null) {
if (!zkHosts.isEmpty()) {
stateProvider = new ZkClientClusterStateProvider(zkHosts, zkChroot);
} else if (!this.solrUrls.isEmpty()) {
try {
stateProvider = new HttpClusterStateProvider(solrUrls, httpClient);
} catch (Exception e) {
throw new RuntimeException(
"Couldn't initialize a HttpClusterStateProvider (is/are the "
+ "Solr server(s), " + solrUrls + ", down?)", e);
}
} else {
throw new IllegalArgumentException("Both zkHosts and solrUrl cannot be null.");
}
}
return new BasicAuthCloudSolrClient(this);
}
#Override
public BasicAuthCloudSolrClient.Builder getThis() {
return this;
}
}
Configuration looks like this:
#Bean
public Credentials solrCredentials(#Value("${solr.username}") String username, #Value("${solr.password}") String
password) {
return new UsernamePasswordCredentials(username, password);
}
#Bean
public SolrClientFactory solrClientFactory(SolrClient solrClient,
Credentials solrCredentials) {
return new HttpSolrClientFactory(solrClient, solrCredentials, AuthSchemes.BASIC);
}
#Bean
public SolrTemplate solrTemplate(SolrClientFactory solrClientFactory){
return new SolrTemplate(solrClientFactory);
}
#Bean
public SolrClient solrClient(Credentials solrCredentials) {
if (isNotEmpty(properties.getZkHosts())) {
BasicAuthCloudSolrClient solrClient =
new BasicAuthCloudSolrClient.Builder(properties.getZkHosts(), solrCredentials).build();
solrClient.setDefaultCollection(properties.getCollection());
return solrClient;
} else {
throw new IllegalStateException("ZkHosts is required for application startup.");
}
}

Related

Spring OAuth2 Making `state` param at least 32 characters long

I am attempting to authorize against an external identity provider. Everything seems setup fine, but I keep getting a validation error with my identity provider because the state parameter automatically tacked onto my authorization request is not long enough:
For example:
&state=uYG5DC
The requirements of my IDP say that this state param must be at least 32-characters long. How can I programmatically increase the size of this auto-generated number?
Even if I could generate this number myself, it is not possible to override with other methods I have seen suggested. The following attempt fails because my manual setting of ?state=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz is superceded by the autogenerated param placed after it during the actual request:
#Bean
public OAuth2ProtectedResourceDetails loginGovOpenId() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails() {
#Override
public String getUserAuthorizationUri() {
return super.getUserAuthorizationUri() + "?state=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
}
};
details.setClientId(clientId);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setScope(Arrays.asList("openid", "email"));
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(true);
return details;
}
The 6-character setting seems to be set here, is there a way to override this?
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/RandomValueStringGenerator.java
With the help of this post:
spring security StateKeyGenerator custom instance
I was able to come up with a working solution.
In my configuration class marked with these annotations:
#Configuration
#EnableOAuth2Client
I configured the following beans:
#Bean
public OAuth2ProtectedResourceDetails loginGovOpenId() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
AuthorizationCodeResourceDetails details = new
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setScope(Arrays.asList("openid", "email"));
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(true);
return details;
}
#Bean
public StateKeyGenerator stateKeyGenerator() {
return new CustomStateKeyGenerator();
}
#Bean
public AccessTokenProvider accessTokenProvider() {
AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider();
accessTokenProvider.setStateKeyGenerator(stateKeyGenerator());
return accessTokenProvider;
}
#Bean
public OAuth2RestTemplate loginGovOpenIdTemplate(final OAuth2ClientContext clientContext) {
final OAuth2RestTemplate template = new OAuth2RestTemplate(loginGovOpenId(), clientContext);
template.setAccessTokenProvider(accessTokenProvider());
return template;
}
Where my CustomStateKeyGenerator implementation class looks as follows:
public class CustomStateKeyGenerator implements StateKeyGenerator {
// login.gov requires state to be at least 32-characters long
private static int length = 32;
private RandomValueStringGenerator generator = new RandomValueStringGenerator(length);
#Override
public String generateKey(OAuth2ProtectedResourceDetails resource) {
return generator.generate();
}
}

Is it possible to run Spring WebFlux and MVC (CXF, Shiro, etc.) services together in Undertow?

We are looking at implementing a few services using the new Spring 5 "Reactive" API.
We currently use, somewhat dependent on MVC, Apache CXF and Apache Shiro for our REST services and security. All of this runs in Undertow now.
We can get one or the other to work but not both together. It appears when we switch over to the reactive application it knocks out the servlets, filters, etc. Conversely, when we use the MVC-style application it does not see the reactive handlers.
Is it possible to run the Spring 5 Reactive services alongside REST/servlet/filter components or customize the SpringBoot startup to run REST and Reactive services on different ports?
Update:
I "seem" to be able to get the reactive handlers working doing this but I don't know if this is the right approach.
#Bean
RouterFunction<ServerResponse> routeGoodbye(TrackingHandler endpoint)
{
RouterFunction<ServerResponse> route = RouterFunctions
.route(GET("/api/rx/goodbye")
.and(accept(MediaType.TEXT_PLAIN)), endpoint::trackRedirect2);
return route;
}
#Bean
RouterFunction<ServerResponse> routeHello(TrackingHandler endpoint)
{
RouterFunction<ServerResponse> route = RouterFunctions
.route(GET("/api/rx/hello")
.and(accept(MediaType.TEXT_PLAIN)), endpoint::trackRedirect);
return route;
}
#Bean
ContextPathCompositeHandler servletReactiveRouteHandler(TrackingHandler handler)
{
final Map<String, HttpHandler> handlers = new HashMap<>();
handlers.put("/hello", toHttpHandler((this.routeHello(handler))));
handlers.put("/goodbye", toHttpHandler(this.routeGoodbye(handler)));
return new ContextPathCompositeHandler(handlers);
}
#Bean
public ServletRegistrationBean servletRegistrationBean(final ContextPathCompositeHandler handlers)
{
ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(
new ReactiveServlet(handlers),
"/api/rx/*");
registrationBean.setLoadOnStartup(1);
registrationBean.setAsyncSupported(true);
return registrationBean;
}
#Bean
TrackingHandler trackingEndpoint(final TrackingService trackingService)
{
return new TrackingHandler(trackingService,
null,
false);
}
public class ReactiveServlet extends ServletHttpHandlerAdapter
{
ReactiveServlet(final HttpHandler httpHandler)
{
super(httpHandler);
}
}
Ok, after playing around with this for too long I finally seemed to be able to cobble together a solution that works for me. Hopefully this is the right way to do what I need to do.
Now, executing normal CXF RESTful routes shows me Undertow using a blocking task and executing my Reactive routes shows me undertow using NIO directly. When I tried using the ServletHttpHandler it looked like it was just invoking the service as a Servlet 3 async call.
The handlers are running completely separate from each other and allows me to run my REST services beside my reactive services.
1) Create an annotation that will be used to map the RouterFunction to an Undertow Handler
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface ReactiveHandler
{
String value();
}
2) Create an UndertowReactiveHandler "Provider" so that I can lazily get the injected RouterFunction and return the UndertowHttpHandler when I configure Undertow.
final class UndertowReactiveHandlerProvider implements Provider<UndertowHttpHandlerAdapter>
{
#Inject
private ApplicationContext context;
private String path;
private String beanName;
#Override
public UndertowHttpHandlerAdapter get()
{
final RouterFunction router = context.getBean(beanName, RouterFunction.class);
return new UndertowHttpHandlerAdapter(toHttpHandler(router));
}
public String getPath()
{
return path;
}
public void setPath(final String path)
{
this.path = path;
}
public void setBeanName(final String beanName)
{
this.beanName = beanName;
}
}
3) Create the NonBLockingHandlerFactory (implements BeanFactoryPostProcessor). This looks for any of my #Bean methods that have been annotated with "ReactiveHandler" and then dynamically creates a "UndertowReactiveHandlerProvider" bean for each annotated router function which is used later to provide the handlers to Undertow.
#Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException
{
final BeanDefinitionRegistry registry = (BeanDefinitionRegistry)configurableListableBeanFactory;
final String[] beanDefinitions = registry.getBeanDefinitionNames();
for (String name : beanDefinitions)
{
final BeanDefinition beanDefinition = registry.getBeanDefinition(name);
if (beanDefinition instanceof AnnotatedBeanDefinition
&& beanDefinition.getSource() instanceof MethodMetadata)
{
final MethodMetadata beanMethod = (MethodMetadata)beanDefinition.getSource();
final String annotationType = ReactiveHandler.class.getName();
if (beanMethod.isAnnotated(annotationType))
{
//Get the current bean details
final String beanName = beanMethod.getMethodName();
final Map<String, Object> attributes = beanMethod.getAnnotationAttributes(annotationType);
//Create the new bean definition
final GenericBeanDefinition rxHandler = new GenericBeanDefinition();
rxHandler.setBeanClass(UndertowReactiveHandlerProvider.class);
//Set the new bean properties
MutablePropertyValues mpv = new MutablePropertyValues();
mpv.add("beanName", beanName);
mpv.add("path", attributes.get("value"));
rxHandler.setPropertyValues(mpv);
//Register the new bean (Undertow handler) with a matching route suffix
registry.registerBeanDefinition(beanName + "RxHandler", rxHandler);
}
}
}
}
4) Create the Undertow ServletExtension. This looks for any UndertowReactiveHandlerProviders and adds it as an UndertowHttpHandler.
public class NonBlockingHandlerExtension implements ServletExtension
{
#Override
public void handleDeployment(DeploymentInfo deploymentInfo, final ServletContext servletContext)
{
deploymentInfo.addInitialHandlerChainWrapper(handler -> {
final WebApplicationContext ctx = getWebApplicationContext(servletContext);
//Get all of the reactive handler providers
final Map<String, UndertowReactiveHandlerProvider> providers =
ctx.getBeansOfType(UndertowReactiveHandlerProvider.class);
//Create the root handler
final PathHandler rootHandler = new PathHandler();
rootHandler.addPrefixPath("/", handler);
//Iterate the providers and add to the root handler
for (Map.Entry<String, UndertowReactiveHandlerProvider> p : providers.entrySet())
{
final UndertowReactiveHandlerProvider provider = p.getValue();
//Append the HttpHandler to the root
rootHandler.addPrefixPath(
provider.getPath(),
provider.get());
}
//Return the root handler
return rootHandler;
});
}
}
5) Under META-INF/services create a "io.undertow.servlet.ServletExtension" file.
com.mypackage.NonBlockingHandlerExtension
6) Create a SpringBoot AutoConfiguration that loads the post processor if Undertow is on the classpath.
#Configuration
#ConditionalOnClass(Undertow.class)
public class UndertowAutoConfiguration
{
#Bean
BeanFactoryPostProcessor nonBlockingHandlerFactoryPostProcessor()
{
return new NonBlockingHandlerFactoryPostProcessor();
}
}
7) Annotate any RouterFunctions that I want to map to an UndertowHandler.
#Bean
#ReactiveHandler("/api/rx/service")
RouterFunction<ServerResponse> routeTracking(TrackingHandler handler)
{
RouterFunction<ServerResponse> route = RouterFunctions
.nest(path("/api/rx/service"), route(
GET("/{cid}.gif"), handler::trackGif).andRoute(
GET("/{cid}"), handler::trackAll));
return route;
}
With this I can call my REST services (and Shiro works with them), use Swagger2 with my REST services, and call my Reactive services (and they do not use Shiro) in the same SpringBoot application.
In my logs, the REST call shows Undertow using the blocking (task-#) handler. The Reactive call shows Undertow using the non-blocking (I/O-# and nioEventLoopGroup) handler

Error handling in Spring RabbitMQ

I am trying to integrate Rabbit MQ broker in our Spring application. I am able to consume messages successfully but need to add Error handling.
Listener consumes the message and apply business logic to it, which include DB writes. The Business logic can throw exception.
In case of these exceptions I need to
Rollback the Db writes.
Write to error table in Db, indicating msg failure.
Message should not be re-queued.
For
requirement #1 - have added txManager in config.xml and annotated the Listner.listen() method with #Transactional
requirement #2 - have added Error handler and custom implementation of DefaultExceptionStrategey
requirement #3 - have set DefaultRequeueRejected=false
But when BusinessRuntimeException is thrown from Listener, ErrorHandler is not getting invoked.
Don't know what is missing.
Is errorHandler invoked only for some Exceptions?
Config.xml
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="allowCustomIsolationLevels" value="true" />
<rabbit:connection-factory id="rabbitConnectionFactory"/>
<rabbit:template id="rabbitTemplate" connection-
factory="rabbitConnectionFactory" message-converter="jsonMessageConverter"
channel-transacted="true"/>
<rabbit:admin id="rabbitAdmin" connection-factory="rabbitConnectionFactory"/>
RabbitMQConfiguration.java
#Configuration
#EnableRabbit
public class RabbitMqConfiguration {
#Autowired
private ConnectionFactory rabbitConnectionFactory;
#Autowired
private MessageConverter jsonMessageConverter;
#Bean
public SimpleRabbitListenerContainerFactory exportPartyListenerContainer() {
SimpleRabbitListenerContainerFactory listenerContainer = new SimpleRabbitListenerContainerFactory();
listenerContainer.setConnectionFactory(rabbitConnectionFactory);
listenerContainer.setMessageConverter(jsonMessageConverter);
listenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
listenerContainer.setChannelTransacted(true);
listenerContainer.setDefaultRequeueRejected(false);
listenerContainer.setErrorHandler(errorHandler());
return listenerContainer;
}
#Bean
public ErrorHandler errorHandler() {
return new ConditionalRejectingErrorHandler(new ExceptionStrategy());
} }
ExceptionStrategy.java
public class ExceptionStrategy extends DefaultExceptionStrategy {
#Autowired
private Dao daoBean;
#Override
public boolean isFatal(Throwable t) {
if (t instanceof BusinessRuntimeException) {
BusinessRuntimeException businessException = (BusinessRuntimeException) t;
//db call
daoBean.updateRecordStaus();
return true;
}
if (t instanceof ListenerExecutionFailedException) {
ListenerExecutionFailedException lefe = (ListenerExecutionFailedException) t;
logger.error(
"Failed to process inbound message from queue " + lefe.getFailedMessage().getMessageProperties().getConsumerQueue()
+ "; failed message: " + lefe.getFailedMessage(),
t);
}
return super.isFatal(t);
}}
wrap your BusinessRuntimeException into a RuntimeException.
catch(BusinessRuntimeException e)
{
throw new RuntimeException(e);
}

Tomcat Spring Security - set session cookie expiration time

I'm building a web application based on an embeded Tomcat - 7.0.55, Spring-Boot-1.1.6, Spring-webmvc/core - 4.0.7 and Spring-Security - 3.2.5.
My configuration looks like this:
#Configuration
public class ServletCtxConfig {
#Bean
#Profile({ Profiles.PRODUCTION, Profiles.QA, Profiles.DEV })
EmbeddedServletContainerFactory servletContainerFactory() {
TomcatEmbeddedServletContainerFactory retVal = new TomcatEmbeddedServletContainerFactory();
retVal.setContextPath("contextPath");
retVal.setTomcatContextCustomizers(Arrays.asList(contextCustomizer()));
retVal.setPort(111);
Connector httpConnector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
httpConnector.setPort(123);
httpConnector.setRedirectPort(456);
retVal.addAdditionalTomcatConnectors(httpConnector);
return retVal;
}
#Bean
CustomCustomizer contextCustomizer() {
return new CustomCustomizer();
}
}
class CustomCustomizer implements TomcatContextCustomizer {
#Value("${session.timeout:10080}")
Integer sessionTimeOut;
#Override
public void customize(Context context) {
context.setSessionCookieName("comilion-fw");
context.setSessionTimeout(sessionTimeOut);
context.setUseHttpOnly(false);
}
}
I am able to set the session expiration time but it is not reflected on the cookie expiration time on the browser.
Can some one please instruct me how to set the cookie expiration time?
Try to access the servlet context during a web app init stage and set the value like this:
servletContext.getSessionCookieConfig().setMaxAge(600);
Have a look at WebApplicationInitializer and SpringServletContainerInitializer
And if you still somehow run web app using web.xml here you go jsessionid-cookie-with-expiration-date-in-tomcat
What I'v done eventually, is customizing the EmbeddedServletContainerFactory as follow:
#Bean
EmbeddedServletContainerFactory servletContainerFactory() {
logger.debug("Raising Embedded servlet container with port: ", port, " and context path: ", contextPath);
TomcatEmbeddedServletContainerFactory retVal = new TomcatEmbeddedServletContainerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
retVal.setContextPath(contextPath);
retVal.setTomcatContextCustomizers(Arrays.asList(contextCustomizer()));
retVal.addAdditionalTomcatConnectors(this.createConnection());
return retVal;
}
#Bean
CustomCustomizer contextCustomizer() {
return new CustomCustomizer();
}
class CustomCustomizer implements TomcatContextCustomizer {
#Value(Properties.$_SESSION_TIMEOUT)
Integer sessionTimeOut;
#Override
public void customize(Context context) {
context.setSessionCookieName("XXX");
context.setSessionTimeout(sessionTimeOut);
}
}

Unit Testing I18N RESTful Web Services with Spring, RestTemplate and Java Config

Trying to get Unit Tests to work when using Spring RestTemplate and I18N. Everything in the setup works fine for all the other test cases.
Based upon what I read, this is what I put into the Java Config:
#Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
return new LocaleChangeInterceptor();
}
#Bean
public DefaultAnnotationHandlerMapping handlerMapping() {
DefaultAnnotationHandlerMapping mapping = new DefaultAnnotationHandlerMapping();
Object[] interceptors = new Object[1];
interceptors[0] = new LocaleChangeInterceptor();
mapping.setInterceptors(interceptors);
return mapping;
}
#Bean
public AnnotationMethodHandlerAdapter handlerAdapter() {
return new AnnotationMethodHandlerAdapter();
}
Then in my usage with RestTemplate I have:
public MyEntity createMyEntity(MyEntity bean) {
Locale locale = LocaleContextHolder.getLocale();
String localeString = "";
if (locale != Locale.getDefault()) {
localeString = "?locale=" + locale.getLanguage();
}
HttpEntity<MyEntity> req = new HttpEntity<MyEntity>(bean);
ResponseEntity<MyEntity> response = restTemplate.exchange(restEndpoint + "/url_path" + localeString, HttpMethod.POST, req, MyEntity.class);
return response.getBody();
}
While this could be cleaned up a bit, it should work - but the LocalChangeInterceptor never gets invoked. I am debugging this now and will post again as soon as I figure it out - but in the hope this is a race condition that I lose - does anyone know why?
Was lucky and stumbled upon this thread. One of the notes clued me into the right direction. You don't need all those beans in the Java Config. But if you are using #EnableWebMvc as I am, but I didn't know it was important enough to even mention, all you need to do in your Java Config is:
#Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
return new LocaleChangeInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleChangeInterceptor());
super.addInterceptors(registry);
}
Add the one bean for the Interceptor and then override the method to add the interceptor. Here my configuration class (annotated with #Configuration and #EnableWebMvc) also extends WebMvcConfigurerAdapter, which should be common usage.
This, at least, worked for me. Hope it may help someone else.

Resources