Error handling in Spring RabbitMQ - spring-mvc

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);
}

Related

Catching Message Handling Exceptions with the #Exceptionhandler

I have two application e.g) A, B
A has a Saga
B is just web application
A sent Command messages to B and
B sent exception for that Command to A's Saga and A's Saga received well
and B have a #ExceptionHandler which I hope to be invoked but it's not working
How can I make them be invoked?
EDIT
this is A application's Saga that sends command messages to B application
and deals with exception which B sent
#Saga
public class OrderSaga {
#Autowired
private transient CommandGateway commandGateway;
#StartSaga
#SagaEventHandler(associationProperty = "orderId")
public void handle(CreateOrderEvent evt) {
String paymentId = UUID.randomUUID().toString();
SagaLifecycle.associateWith("paymentId", paymentId);
commandGateway.send(new CreatedPaymentCommand(paymentId, evt.getUserId(),evt.getFoodPrice())).exceptionally(exp -> {
System.out.println("got it");
System.out.println(exp.getMessage());
return null;
});
}
}
this is B application that throws exception for test
#Aggregate
#NoArgsConstructor
public class PaymentAggregate {
#AggregateIdentifier
private String paymentId;
private String userId;
private PaymentStatus status;
#CommandHandler
public PaymentAggregate(CreatedPaymentCommand cmd) {
throw new IllegalStateException("this exception was came from payment aggregates");
// AggregateLifecycle.apply(new CreatedPaymentEvent(cmd.getPaymentId(),
// cmd.getUserId(),cmd.getMoney()));
}
#ExceptionHandler(resultType = IllegalStateException.class)
public void error(IllegalStateException exp) {
System.out.println(exp.getMessage());
}
// I want this #ExceptionHandler to be invoked
#EventSourcingHandler
public void on(CreatedPaymentEvent evt) {
this.paymentId = evt.getPaymentId();
this.userId = evt.getUserId();
}
}
A application catch exception well like below
2021-08-24 11:46:43.534 WARN 14244 --- [ault-executor-2] o.a.c.gateway.DefaultCommandGateway : Command 'com.common.cmd.CreatedPaymentCommand' resulted in org.axonframework.commandhandling.CommandExecutionException(this exception was came from payment aggregates)
got it
this exception was came from payment aggregates
but B is not I thought that B's #ExceptionHandler will catch that exception
in short, How can I make B's #ExceptionHandler to be invoked
It doesn't work right now because the exception is thrown from the constructor of your aggregate.
As you are using a constructor command handler, there is no instance present yet.
And without an instance, Axon Framework cannot spot the #ExceptionHandler annotated method you've set up.
This is the only missing point for the exception handlers at this stage. Honestly, the reference guide should be a bit more specific about this. I am sure this will change in the future, though.
There's a different approach for having a command handler that constructs the aggregate and that can use the #ExceptionHandler: with the #CreationPolicy annotation. The reference guide has this to say about it, by the way.
Thus, instead of having a constructor command handler, you would set up a regular command handler using the AggregateCreationPolicy.ALWAYS.
That would adjust your sample like so:
#Aggregate
#NoArgsConstructor
public class PaymentAggregate {
#AggregateIdentifier
private String paymentId;
private String userId;
private PaymentStatus status;
#CommandHandler
#CreationPolicy(AggregateCreationPolicy.ALWAYS)
public void handle(CreatedPaymentCommand cmd) {
throw new IllegalStateException("this exception was came from payment aggregates");
// AggregateLifecycle.apply(new CreatedPaymentEvent(cmd.getPaymentId(),
// cmd.getUserId(),cmd.getMoney()));
}
#ExceptionHandler(resultType = IllegalStateException.class)
public void error(IllegalStateException exp) {
System.out.println(exp.getMessage());
}
// I want this #ExceptionHandler to be invoked
#EventSourcingHandler
public void on(CreatedPaymentEvent evt) {
this.paymentId = evt.getPaymentId();
this.userId = evt.getUserId();
}
}
Please give this a try in your application, #YongD.

Getting org.springframework.retry.TerminatedRetryException with RetryListener

I'm using spring-kafka 2.2.8.RELEASE and using #KafkaListener annotation to create a consumer and here is my consumer configuration code.
#Bean
public <K,V> ConcurrentKafkaListenerContainerFactory<String, Object> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(primaryConsumerFactory());
factory.setRetryTemplate(retryTemplate());
return factory;
}
#Bean
public DefaultKafkaConsumerFactory<Object, Object> primaryConsumerFactory() {
return new DefaultKafkaConsumerFactory<>(MyConsumerConfig.getConfigs());
}
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setListeners(new RetryListener[]{myKafkaRetryListener});
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(Integer.parseInt(3));
retryTemplate.setRetryPolicy(retryPolicy);
ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
exponentialBackOffPolicy.setInitialInterval(500);
//As per the spring-kafka documentation, maxInterval (60000 ms) should be set less than max.poll.interval.ms (600000 ms)
exponentialBackOffPolicy.setMaxInterval(60000);
retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);
return retryTemplate;
}
Here is my custom retry listener code:
#Component
public class MyRetryListener implements RetryListener {
#Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
System.out.println("##### IN open method");
return false;
}
#Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("##### IN close method");
}
#Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("##### Got an error and will retry");
}
}
Now, when I'm sending a message to a test topic, and in the consumer I'm throwing a TimeoutException so that the retry will trigger and here is my consumer code.
#KafkaListener(topics = "CONSUMER_RETRY_TEST_TOPIC")
public void listen(ConsumerRecord message) throws RetriableException {
System.out.println("CONSUMER_RETRY testing - Received message with key "+message.key()+" on topic " + CONSUMER_RETRY_TEST_TOPIC + " \n \n ");
throw new TimeoutException();
}
With the above code configuration, the retry is not triggered and 'onError' method of my custom retry listener is never invoked and I'm getting the below error. Please suggest what am i missing here?
org.springframework.retry.TerminatedRetryException: Retry terminated abnormally by interceptor before first attempt
See the JavaDocs for RetryListener.open().
<T,E extends Throwable> boolean open(RetryContext context,
RetryCallback<T,E> callback)
Called before the first attempt in a retry. For instance, implementers can set up state that is needed by the policies in the RetryOperations. The whole retry can be vetoed by returning false from this method, in which case a TerminatedRetryException will be thrown.
Type Parameters:
T - the type of object returned by the callback
E - the type of exception it declares may be thrown
Parameters:
context - the current RetryContext.
callback - the current RetryCallback.
Returns:
true if the retry should proceed.
You need to return true not false.

SoapFault handling with Spring WS client - WebServiceGatewaySupport and WebServiceTemplate

I am trying to write a Spring WS client using WebServiceGatewaySupport. I managed to test the client for a successful request and response. Now I wanted to write test cases for soap faults.
public class MyClient extends WebServiceGatewaySupport {
public ServiceResponse method(ServiceRequest serviceRequest) {
return (ServiceResponse) getWebServiceTemplate().marshalSendAndReceive(serviceRequest);
}
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringBootTest(classes = SpringTestConfig.class)
#DirtiesContext
public class MyClientTest {
#Autowired
private MyClient myClient;
private MockWebServiceServer mockServer;
#Before
public void createServer() throws Exception {
mockServer = MockWebServiceServer.createServer(myClient);
}
}
My question is how do i stub the soap fault response in the mock server, so that my custom FaultMessageResolver will be able to unmarshall soap fault?
I tried couple of things below, but nothing worked.
// responsePayload being SoapFault wrapped in SoapEnvelope
mockServer.expect(payload(requestPayload))
.andRespond(withSoapEnvelope(responsePayload));
// tried to build error message
mockServer.expect(payload(requestPayload))
.andRespond(withError("soap fault string"));
// tried with Exception
mockServer.expect(payload(requestPayload))
.andRespond(withException(new RuntimeException));
Any help is appreciated. Thanks!
Follow Up:
Ok so, withSoapEnvelope(payload) I managed to get the controller to go to my custom MySoapFaultMessageResolver.
public class MyCustomSoapFaultMessageResolver implements FaultMessageResolver {
private Jaxb2Marshaller jaxb2Marshaller;
#Override
public void resolveFault(WebServiceMessage message) throws IOException {
if (message instanceof SoapMessage) {
SoapMessage soapMessage = (SoapMessage) message;
SoapFaultDetailElement soapFaultDetailElement = (SoapFaultDetailElement) soapMessage.getSoapBody()
.getFault()
.getFaultDetail()
.getDetailEntries()
.next();
Source source = soapFaultDetailElement.getSource();
jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setContextPath("com.company.project.schema");
Object object = jaxb2Marshaller.unmarshal(source);
if (object instanceof CustomerAlreadyExistsFault) {
throw new CustomerAlreadyExistsException(soapMessage);
}
}
}
}
But seriously!!! I had to unmarshall every message and check the instance of it. Being a client I should be thorough with all possible exceptions of the service here, and create custom runtime exceptions and throw it from the resolver. Still at the end, its been caught in WebServiceTemplate and re thrown as just a runtime exception.
You could try with something like this:
#Test
public void yourTestMethod() // with no throw here
{
Source requestPayload = new StringSource("<your request>");
String errorMessage = "Your error message from WS";
mockWebServiceServer
.expect(payload(requestPayload))
.andRespond(withError(errorMessage));
YourRequestClass request = new YourRequestClass();
// TODO: set request properties...
try {
yourClient.callMethod(request);
}
catch (Exception e) {
assertThat(e.getMessage()).isEqualTo(errorMessage);
}
mockWebServiceServer.verify();
}
In this part of code mockWebServiceServer represents the instance of MockWebServiceServer class.

How to configure Spring data SolrCloud connection with 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.");
}
}

Injecting jms resource in servlet & best practice for MDB

using ejb 3.1, servlet 3.0 (glassfish server v3)
Scenario:
I have MDB that listen to jms messages and give processing to some other session bean (Stateless).
Servelet injecting jms resource.
Question 1: Why servlet can`t inject jms resources when they use static declaration ?
#Resource(mappedName = "jms/Tarturus")
private static ConnectionFactory connectionFactory;
#Resource(mappedName = "jms/StyxMDB")
private static Queue queue;
private Connection connection;
and
#PostConstruct
public void postConstruct() {
try {
connection = connectionFactory.createConnection();
} catch (JMSException e) {
e.printStackTrace();
}
}
#PreDestroy
public void preDestroy() {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
The error that I get is :
[#|2010-05-03T15:18:17.118+0300|WARNING|glassfish3.0|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=35;_ThreadName=Thread-1;|StandardWrapperValve[WorkerServlet]:
PWC1382: Allocate exception for
servlet WorkerServlet
com.sun.enterprise.container.common.spi.util.InjectionException:
Error creating managed object for
class
ua.co.rufous.server.services.WorkerServiceImpl
at
com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.createManagedObject(InjectionManagerImpl.java:312)
at
com.sun.enterprise.web.WebContainer.createServletInstance(WebContainer.java:709)
at
com.sun.enterprise.web.WebModule.createServletInstance(WebModule.java:1937)
at
org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1252)
Caused by:
com.sun.enterprise.container.common.spi.util.InjectionException:
Exception attempting to inject
Unresolved Message-Destination-Ref
ua.co.rufous.server.services.WorkerServiceImpl/queue#java.lang.String#null
into class
ua.co.rufous.server.services.WorkerServiceImpl
at
com.sun.enterprise.container.common.impl.util.InjectionManagerImpl._inject(InjectionManagerImpl.java:614) at
com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.inject(InjectionManagerImpl.java:384)
at
com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.injectInstance(InjectionManagerImpl.java:141)
at
com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.injectInstance(InjectionManagerImpl.java:127)
at
com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.createManagedObject(InjectionManagerImpl.java:306)
... 27 more Caused by:
com.sun.enterprise.container.common.spi.util.InjectionException:
Illegal use of static field private
static javax.jms.Queue
ua.co.rufous.server.services.WorkerServiceImpl.queue
on class that only supports
instance-based injection at
com.sun.enterprise.container.common.impl.util.InjectionManagerImpl._inject(InjectionManagerImpl.java:532) ... 31 more |#]
my MDB :
/**
* asadmin commands
* asadmin create-jms-resource --restype javax.jms.ConnectionFactory jms/Tarturus
* asadmin create-jms-resource --restype javax.jms.Queue jms/StyxMDB
* asadmin list-jms-resources
*/
#MessageDriven(mappedName = "jms/StyxMDB", activationConfig =
{
#ActivationConfigProperty(propertyName = "connectionFactoryJndiName", propertyValue = "jms/Tarturus"),
#ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
})
public class StyxMDB implements MessageListener {
#EJB
private ActivationProcessingLocal aProcessing;
public StyxMDB() {
}
public void onMessage(Message message) {
try {
TextMessage msg = (TextMessage) message;
String hash = msg.getText();
GluttonyLogger.getInstance().writeInfoLog("geted jms message hash = " + hash);
} catch (JMSException e) {
}
}
}
everything work good without static declaration:
#Resource(mappedName = "jms/Tarturus")
private ConnectionFactory connectionFactory;
#Resource(mappedName = "jms/StyxMDB")
private Queue queue;
private Connection connection;
Question 2:
what is the best practice for working with MDB : processing full request in onMessage() or calling another bean(Stateless bean in my case) in onMessage() method that would process it.
Processing including few calls to soap services, so the full processing time could be for a 3 seconds.
Thank you.
Answers:
1. You cannot inject a resource into a static field. Injection into member fields occurs during object construction, static fields are not part of the object (only part of the class). In addition EJBs and servlets are threaded objects so this could possibly be dangerous to do.
2. If splitting the processing up into multiple EJB(s) makes sense do it that way, otherwise processing in onMessage() is perfectly valid.
One additional suggestion that I can give, is that you should take a look at CDI which is a new addition to the EE 6 specification and provides rich dependency injection.
Are you using an MDB to perform asynchronous operations, Servlet 3.0 has some neat asynchronous capabilities. I suggest you watch the entire presentation if you are not familiar with Servlet 3.0.

Resources