Play Framework - ErrorHandler is not being invoked - java

I'm facing an issue in Play framework (2.6) while trying to handle an exception using the HttpErrorHandler mechanism in a specific scenario.
I have a simple ErrorHandler in the root package:
import play.Logger;
import play.http.HttpErrorHandler;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Results;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
public class ErrorHandler implements HttpErrorHandler {
#Override
public CompletionStage<Result> onClientError(Http.RequestHeader request, int statusCode, String message) {
Logger.error("client error");
return CompletableFuture.completedFuture(Results.status(statusCode, "A client error occurred: " + message));
}
#Override
public CompletionStage<Result> onServerError(Http.RequestHeader request, Throwable exception) {
Logger.error("server error");
return CompletableFuture.completedFuture(
Results.internalServerError("A server error occurred: " + exception.getMessage()));
}
}
I created a custom controller to handle the assets:
package controllers;
import org.apache.commons.lang3.StringUtils;
import play.api.mvc.Action;
import play.api.mvc.AnyContent;
import play.mvc.Controller;
import javax.inject.Inject;
public class AssetsCustomController extends Controller {
private final Assets assets;
#Inject
public AssetsCustomController(Assets assets) {
this.assets = assets;
}
public Action<AnyContent> at(String path, String file) {
if (StringUtils.isEmpty(path)) {
throw new IllegalArgumentException();
}
return assets.at(path, file, false);
}
}
I've checked the ErrorHandler and is working fine for all the cases except when the exception is thrown from this method in AssetsCustomController:
public Action<AnyContent> at(String path, String file) {
if (StringUtils.isEmpty(path)) {
throw new IllegalArgumentException();
}
return assets.at(path, file, false);
}
For instance, when the IllegalArgumentException is thrown, my ErrorHandler is ignored and Play uses his native error handler. Here is the stacktrace:
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[IllegalArgumentException: null]]
at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:255)
at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:182)
at play.api.http.DefaultHttpErrorHandler$.onServerError(HttpErrorHandler.scala:286)
at play.core.server.Server.logExceptionAndGetResult$1(Server.scala:53)
at play.core.server.Server.getHandlerFor(Server.scala:83)
at play.core.server.Server.getHandlerFor$(Server.scala:49)
at play.core.server.AkkaHttpServer.getHandlerFor(AkkaHttpServer.scala:42)
at play.core.server.AkkaHttpServer.getHandler(AkkaHttpServer.scala:215)
at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:195)
at play.core.server.AkkaHttpServer.$anonfun$createServerBinding$3(AkkaHttpServer.scala:107)
Caused by: java.lang.IllegalArgumentException: null
at controllers.AssetsCustomController.at(AssetsCustomController.java:20)
at router.Routes$$anonfun$routes$1.$anonfun$applyOrElse$10(Routes.scala:183)
at play.core.routing.HandlerInvokerFactory$$anon$6$$anon$7.call(HandlerInvoker.scala:61)
at play.core.routing.GeneratedRouter$$anon$2.call(GeneratedRouter.scala:251)
at router.Routes$$anonfun$routes$1.$anonfun$applyOrElse$9(Routes.scala:183)
at play.core.routing.GeneratedRouter.$anonfun$call$5(GeneratedRouter.scala:99)
at scala.util.Either.fold(Either.scala:118)
at play.core.routing.GeneratedRouter.call(GeneratedRouter.scala:99)
at router.Routes$$anonfun$routes$1.applyOrElse(Routes.scala:182)
at router.Routes$$anonfun$routes$1.applyOrElse(Routes.scala:154)
[SOLUTION]
Thanks to the answer Flo354, I was able to solve the problem and the AssetsCustomController is like this:
package controllers;
import akka.stream.Materializer;
import org.springframework.util.StringUtils;
import play.mvc.Controller;
import play.mvc.Result;
import javax.inject.Inject;
import java.util.concurrent.CompletionStage;
public class AssetsCustomController extends Controller {
private final Assets assets;
private final Materializer materializer;
#Inject
public AssetsCustomController(Materializer materializer, Assets assets) {
this.materializer = materializer;
this.assets = assets;
}
public CompletionStage<Result> at(String path, String file) {
if (StringUtils.isEmpty(path)) {
throw new IllegalArgumentException();
}
return assets.at(path, file, false).asJava().apply(request()).run(materializer);
}
}

Your Action<AnyContent> is part of the Scala API, which means it does not use the Java part.
Your Custom error handler apply only to Java sources. Look at your stack trace
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[IllegalArgumentException: null]]
at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:255)
at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:182)
at play.api.http.DefaultHttpErrorHandler$.onServerError(HttpErrorHandler.scala:286)
....
You can see that it's scala when there is play.api.something
You can workaround this by sending back a CompletionStage<Result>
public CompletionStage<Result> test(String path, String file) {
if (StringUtils.isEmpty(path)) {
throw new IllegalArgumentException();
}
return assets.at(path, file, false).asJava().apply(request()).run(materializer);
}
And in your controller, don't forget to inject an instance of akka.stream.Materializer
Hope it will help!

Related

Illegal argument exception for Optaplanner project, solver config does not exist as a classpath resource in the class loader

I am getting an error when running my SpringBoot application in my solver class. I am trying to create the solver factory from an xml resource contained in the resources folder exactly like the employee rostering example for OptaPlanner but I am getting an Illegal argument exception that I can't figure out why. I tried moving the xml file into the same folder as the solver class but the same error appears. I don't know why the exception is being thrown when I am following the example code that OptaPlanner has for employee rostering ?
Below is my code for my solver class:
package com.schedule.demo.solver;
import com.schedule.demo.domain.Roster;
import com.schedule.demo.service.RosterService;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.impl.score.director.ScoreDirector;
import org.optaplanner.core.impl.score.director.ScoreDirectorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.ApplicationScope;
#ApplicationScope
#Component
public class ScheduleSolverManager implements ApplicationRunner {
public static final String SOLVER_CONFIG = "com/schedule/demo/service/solver/scheduleSolverConfig.xml";
protected final transient Logger logger = LoggerFactory.getLogger(getClass());
private SolverFactory<Roster> rosterSolverFactory;
private ScoreDirectorFactory<Roster> rosterScoreDirectorFactory;
private ThreadPoolTaskExecutor taskExecutor;
private ConcurrentMap<Integer, Solver<Roster>> deptIdToSolverMap = new ConcurrentHashMap<>();
private RosterService rosterService;
public ScheduleSolverManager(ThreadPoolTaskExecutor taskExecutor, RosterService rosterService){
this.taskExecutor = taskExecutor;
this.rosterService = rosterService;
}
#Override
public void run(ApplicationArguments args) throws Exception {
setupRosterSolverFactory();
}
private void setupRosterSolverFactory() {
rosterSolverFactory = SolverFactory.createFromXmlResource("com/schedule/demo/service/solver/scheduleSolverConfig.xml", ScheduleSolverManager.class.getClassLoader());
rosterScoreDirectorFactory = rosterSolverFactory.buildSolver().getScoreDirectorFactory();
}
public CountDownLatch solve(Integer deptID){
final CountDownLatch solvingEndedLatch = new CountDownLatch(1);
try{
taskExecutor.execute(()->{
Solver<Roster> rosterSolver = rosterSolverFactory.buildSolver();
deptIdToSolverMap.put(deptID, rosterSolver);
rosterSolver.addEventListener(event -> {
if(event.isEveryProblemFactChangeProcessed()){
logger.info("New best solution found for deptId ({}).", deptID);
Roster newRoster = event.getNewBestSolution();
rosterService.updateShiftsOfRoster(newRoster);
}
});
Roster roster = rosterService.buildRoster(deptID);
try {
rosterSolver.solve(roster);
solvingEndedLatch.countDown();
} finally {
deptIdToSolverMap.remove(deptID);
}
});
} catch (Throwable e) {
logger.error("Error solving for deptId (" + deptID + ").", e);
}
return solvingEndedLatch;
}
public Roster getRoster(final Integer deptId){
Solver<Roster> rosterSolver = deptIdToSolverMap.get(deptId);
return rosterSolver == null ? null : rosterSolver.getBestSolution();
}
public ScoreDirector<Roster> getScoreDirector() {
return rosterScoreDirectorFactory.buildScoreDirector();
}
}
The error when run is as follows:
Caused by: java.lang.IllegalArgumentException: The solverConfigResource (com/schedule/demo/service/solver/scheduleSolverConfig.xml) does not exist as a classpath resource in the classLoader (org.springframework.boot.devtools.restart.classloader.RestartClassLoader#2302b8ed).
Use the optaplanner-spring-boot-starter. It simplifies Spring integration greatly.
Guide: https://github.com/spring-guides/getting-started-guides/pull/126
Video: https://www.youtube.com/watch?v=U2N02ReT9CI
If you don't want to use the starter, this code might fix it: SolverFactory.createFromXmlResource("com/schedule/demo/service/solver/scheduleSolverConfig.xml", Roster.class.getClassLoader()); (uses the classloader of Roster).
You have to place the solver config XML under src/main/resources in order to be able to load it as a classpath resource.
So, if you load it using the resource name com/schedule/demo/service/solver/scheduleSolverConfig.xml, then the file has to be located at
src/main/resources/com/schedule/demo/service/solver/scheduleSolverConfig.xml

Receive events from AMQP with Axon 4

I am trying to send messages via rabbitmq to an axon4 spring boot based system. The message is received but no events are triggered. I am very sure I am missing an essential part, but up to now I wasn't able to figure it out.
Here the relevant part of my application.yml
axon:
amqp:
exchange: axon.fanout
transaction-mode: publisher_ack
# adding the following lines changed nothing
eventhandling:
processors:
amqpEvents:
source: in.queue
mode: subscribing
spring:
rabbitmq:
username: rabbit
password: rabbit
From the docs I found that I am supposed to create a SpringAMQPMessageSource bean:
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.axonframework.extensions.amqp.eventhandling.AMQPMessageConverter;
import org.axonframework.extensions.amqp.eventhandling.spring.SpringAMQPMessageSource;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Slf4j
#Configuration
public class AxonConfig {
#Bean
SpringAMQPMessageSource inputMessageSource(final AMQPMessageConverter messageConverter) {
return new SpringAMQPMessageSource(messageConverter) {
#RabbitListener(queues = "in.queue")
#Override
public void onMessage(final Message message, final Channel channel) {
log.debug("received external message: {}, channel: {}", message, channel);
super.onMessage(message, channel);
}
};
}
}
If I send a message to the queue from the rabbitmq admin panel I see the log:
AxonConfig : received external message: (Body:'[B#13f7aeef(byte[167])' MessageProperties [headers={}, contentLength=0, receivedDeliveryMode=NON_PERSISTENT, redelivered=false, receivedExchange=, receivedRoutingKey=in.queue, deliveryTag=2, consumerTag=amq.ctag-xi34jwHHA__xjENSteX5Dw, consumerQueue=in.queue]), channel: Cached Rabbit Channel: AMQChannel(amqp://rabbit#127.0.0.1:5672/,1), conn: Proxy#11703cc8 Shared Rabbit Connection: SimpleConnection#581cb879 [delegate=amqp://rabbit#127.0.0.1:5672/, localPort= 58614]
Here the Aggregate that should receive the events:
import lombok.extern.slf4j.Slf4j;
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.config.ProcessingGroup;
import org.axonframework.eventsourcing.EventSourcingHandler;
import org.axonframework.modelling.command.AggregateIdentifier;
import org.axonframework.spring.stereotype.Aggregate;
import pm.mbo.easyway.api.app.order.commands.ConfirmOrderCommand;
import pm.mbo.easyway.api.app.order.commands.PlaceOrderCommand;
import pm.mbo.easyway.api.app.order.commands.ShipOrderCommand;
import pm.mbo.easyway.api.app.order.events.OrderConfirmedEvent;
import pm.mbo.easyway.api.app.order.events.OrderPlacedEvent;
import pm.mbo.easyway.api.app.order.events.OrderShippedEvent;
import static org.axonframework.modelling.command.AggregateLifecycle.apply;
#ProcessingGroup("amqpEvents")
#Slf4j
#Aggregate
public class OrderAggregate {
#AggregateIdentifier
private String orderId;
private boolean orderConfirmed;
#CommandHandler
public OrderAggregate(final PlaceOrderCommand command) {
log.debug("command: {}", command);
apply(new OrderPlacedEvent(command.getOrderId(), command.getProduct()));
}
#CommandHandler
public void handle(final ConfirmOrderCommand command) {
log.debug("command: {}", command);
apply(new OrderConfirmedEvent(orderId));
}
#CommandHandler
public void handle(final ShipOrderCommand command) {
log.debug("command: {}", command);
if (!orderConfirmed) {
throw new IllegalStateException("Cannot ship an order which has not been confirmed yet.");
}
apply(new OrderShippedEvent(orderId));
}
#EventSourcingHandler
public void on(final OrderPlacedEvent event) {
log.debug("event: {}", event);
this.orderId = event.getOrderId();
orderConfirmed = false;
}
#EventSourcingHandler
public void on(final OrderConfirmedEvent event) {
log.debug("event: {}", event);
orderConfirmed = true;
}
#EventSourcingHandler
public void on(final OrderShippedEvent event) {
log.debug("event: {}", event);
orderConfirmed = true;
}
protected OrderAggregate() {
}
}
So the problem is that the messages are received by the system but no events are triggered. The content of the messages seem to be irrelevant. Whatever I send to the queue I only get a log message from my onMessage method.
JavaDoc of SpringAMQPMessageSource says this:
/**
* MessageListener implementation that deserializes incoming messages and forwards them to one or more event processors.
* <p>
* The SpringAMQPMessageSource must be registered with a Spring MessageListenerContainer and forwards each message
* to all subscribed processors.
* <p>
* Note that the Processors must be subscribed before the MessageListenerContainer is started. Otherwise, messages will
* be consumed from the AMQP Queue without any processor processing them.
*
* #author Allard Buijze
* #since 3.0
*/
But up to now I couldn't find out where or how to register it.
The axon.eventhandling entries in my config and #ProcessingGroup("amqpEvents") in my Aggregate are already from testing. But having those entries in or not made no difference at all. Also tried without the mode=subscribing.
Exact versions: Spring Boot 2.1.4, Axon 4.1.1, axon-amqp-spring-boot-autoconfigure 4.1
Any help or hints highly appreciated.
Update 23.04.19:
I tried to write my own class like this:
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.axonframework.common.Registration;
import org.axonframework.eventhandling.EventMessage;
import org.axonframework.extensions.amqp.eventhandling.AMQPMessageConverter;
import org.axonframework.messaging.SubscribableMessageSource;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
#Slf4j
#Component
public class RabbitMQSpringAMQPMessageSource implements ChannelAwareMessageListener, SubscribableMessageSource<EventMessage<?>> {
private final List<Consumer<List<? extends EventMessage<?>>>> eventProcessors = new CopyOnWriteArrayList<>();
private final AMQPMessageConverter messageConverter;
#Autowired
public RabbitMQSpringAMQPMessageSource(final AMQPMessageConverter messageConverter) {
this.messageConverter = messageConverter;
}
#Override
public Registration subscribe(final Consumer<List<? extends EventMessage<?>>> messageProcessor) {
eventProcessors.add(messageProcessor);
log.debug("subscribe to: {}", messageProcessor);
return () -> eventProcessors.remove(messageProcessor);
}
#RabbitListener(queues = "${application.queues.in}")
#Override
public void onMessage(final Message message, final Channel channel) {
log.debug("received external message: {}, channel: {}", message, channel);
log.debug("eventProcessors: {}", eventProcessors);
if (!eventProcessors.isEmpty()) {
messageConverter.readAMQPMessage(message.getBody(), message.getMessageProperties().getHeaders())
.ifPresent(event -> eventProcessors.forEach(
ep -> ep.accept(Collections.singletonList(event))
));
}
}
}
The result is the same and the log now proofs that the eventProcessors are just empty.
eventProcessors: []
So the question is, how to register the event processors correctly. Is there a way how to do that properly with spring?
Update2:
Also no luck with this:
#Slf4j
#Component("rabbitMQSpringAMQPMessageSource")
public class RabbitMQSpringAMQPMessageSource extends SpringAMQPMessageSource {
#Autowired
public RabbitMQSpringAMQPMessageSource(final AMQPMessageConverter messageConverter) {
super(messageConverter);
}
#RabbitListener(queues = "${application.queues.in}")
#Override
public void onMessage(final Message message, final Channel channel) {
try {
final var eventProcessorsField = this.getClass().getSuperclass().getDeclaredField("eventProcessors");
eventProcessorsField.setAccessible(true);
final var eventProcessors = (List<Consumer<List<? extends EventMessage<?>>>>) eventProcessorsField.get(this);
log.debug("eventProcessors: {}", eventProcessors);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
log.debug("received message: message={}, channel={}", message, channel);
super.onMessage(message, channel);
}
}
axon:
eventhandling:
processors:
amqpEvents:
source: rabbitMQSpringAMQPMessageSource
mode: SUBSCRIBING
Registering it programmatically in addition to above also didn't help:
#Autowired
void configure(EventProcessingModule epm,
RabbitMQSpringAMQPMessageSource rabbitMessageSource) {
epm.registerSubscribingEventProcessor("rabbitMQSpringAMQPMessageSource", c -> rabbitMessageSource);
epm.assignProcessingGroup("amqpEvents", "rabbitMQSpringAMQPMessageSource");// this line also made no difference
}
Of course #ProcessingGroup("amqpEvents") is in place in my class that contains the #EventSourcingHandler annotated methods.
Update 25.4.19:
see accepted answer from Allard. Thanks a lot pointing me at the mistake I made: I missed that EventSourcingHandler don't receive messages from outside. This is for projections. Not for distributing Aggregates! ups
Here the config/classes that are receiving events from rabbitmq now:
axon:
eventhandling:
processors:
amqpEvents:
source: rabbitMQSpringAMQPMessageSource
mode: SUBSCRIBING
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.axonframework.extensions.amqp.eventhandling.AMQPMessageConverter;
import org.axonframework.extensions.amqp.eventhandling.spring.SpringAMQPMessageSource;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Slf4j
#Component("rabbitMQSpringAMQPMessageSource")
public class RabbitMQSpringAMQPMessageSource extends SpringAMQPMessageSource {
#Autowired
public RabbitMQSpringAMQPMessageSource(final AMQPMessageConverter messageConverter) {
super(messageConverter);
}
#RabbitListener(queues = "${application.queues.in}")
#Override
public void onMessage(final Message message, final Channel channel) {
log.debug("received message: message={}, channel={}", message, channel);
super.onMessage(message, channel);
}
}
import lombok.extern.slf4j.Slf4j;
import org.axonframework.config.ProcessingGroup;
import org.axonframework.eventhandling.EventHandler;
import org.axonframework.queryhandling.QueryHandler;
import org.springframework.stereotype.Service;
import pm.mbo.easyway.api.app.order.events.OrderConfirmedEvent;
import pm.mbo.easyway.api.app.order.events.OrderPlacedEvent;
import pm.mbo.easyway.api.app.order.events.OrderShippedEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#Slf4j
#ProcessingGroup("amqpEvents")
#Service
public class OrderedProductsEventHandler {
private final Map<String, OrderedProduct> orderedProducts = new HashMap<>();
#EventHandler
public void on(OrderPlacedEvent event) {
log.debug("event: {}", event);
String orderId = event.getOrderId();
orderedProducts.put(orderId, new OrderedProduct(orderId, event.getProduct()));
}
#EventHandler
public void on(OrderConfirmedEvent event) {
log.debug("event: {}", event);
orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> {
orderedProduct.setOrderConfirmed();
return orderedProduct;
});
}
#EventHandler
public void on(OrderShippedEvent event) {
log.debug("event: {}", event);
orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> {
orderedProduct.setOrderShipped();
return orderedProduct;
});
}
#QueryHandler
public List<OrderedProduct> handle(FindAllOrderedProductsQuery query) {
log.debug("query: {}", query);
return new ArrayList<>(orderedProducts.values());
}
}
I removed the #ProcessingGroup from my Aggregate of course.
My logs:
RabbitMQSpringAMQPMessageSource : received message: ...
OrderedProductsEventHandler : event: OrderShippedEvent...
In Axon, Aggregates do not receive events from "outside". The Event Handlers inside Aggregates (more specifically, they are EventSourcingHandlers) only handle events that have been published by that same aggregate instance, so that it can reconstruct its prior state.
It is only external event handlers, for example the ones that update projections, that will receive events from external sources.
For that to work, your application.yml should mention the bean name as a processors' source instead of the queue name. So in your first example:
eventhandling:
processors:
amqpEvents:
source: in.queue
mode: subscribing
Should become:
eventhandling:
processors:
amqpEvents:
source: inputMessageSource
mode: subscribing
But again, this only works for event handlers defined on components, not on Aggregates.

How to write a proper unit test for Elasticsearch in Java

Overview:
I'm totally new to Elastic search testing and I'm gonna add proper unit tests. The project compatibilities are as follow:
Java 8
Elasticsearch 6.2.4
Project uses low level rest client for fetching data from ES
More info about ES configurations is as follow:
import static java.net.InetAddress.getByName;
import static java.util.Arrays.stream;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import au.com.api.util.RestClientUtil;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Configuration
public class ElasticConfiguration implements InitializingBean{
#Value(value = "${elasticsearch.hosts}")
private String[] hosts;
#Value(value = "${elasticsearch.httpPort}")
private int httpPort;
#Value(value = "${elasticsearch.tcpPort}")
private int tcpPort;
#Value(value = "${elasticsearch.clusterName}")
private String clusterName;
#Inject
private RestClientUtil client;
#Bean
public RestHighLevelClient restHighClient() {
return new RestHighLevelClient(RestClient.builder(httpHosts()));
}
#Bean
#Deprecated
public RestClient restClient() {
return RestClient.builder(httpHosts()).build();
}
/**
* #return TransportClient
* #throws UnknownHostException
*/
#SuppressWarnings("resource")
#Bean
public TransportClient transportClient() throws UnknownHostException{
Settings settings = Settings.builder()
.put("cluster.name", clusterName).build();
return new PreBuiltTransportClient(settings).addTransportAddresses(transportAddresses());
}
#Override
public void afterPropertiesSet() throws Exception {
log.debug("loading search templates...");
try {
for (Map.Entry<String, String> entry : Constants.SEARCH_TEMPLATE_MAP.entrySet()) {
client.putInlineSearchTemplateToElasticsearch(entry.getKey(), entry.getValue());
}
} catch (Exception e) {
log.error("Exception has occurred in putting search templates into ES.", e);
}
}
private HttpHost[] httpHosts() {
return stream(hosts).map(h -> new HttpHost(h, httpPort, "http")).toArray(HttpHost[]::new);
}
private TransportAddress[] transportAddresses() throws UnknownHostException {
TransportAddress[] transportAddresses = stream(hosts).map(h -> {
try {
return new TransportAddress(getByName(h), tcpPort);
} catch (UnknownHostException e) {
log.error("Exception has occurred in creating ES TransportAddress. host: '{}', tcpPort: '{}'", h, tcpPort, e);
}
return null;
}).filter(Objects::nonNull).toArray(TransportAddress[]::new);
if (transportAddresses.length == 0) {
throw new UnknownHostException();
}
return transportAddresses;
}
}
Issue:
I don't know how to Mock ES or how to test ES without running an standalone ES on my machine. Please use the following class as an example and let me know how could I write a testcase (unit test not integration) for getSearchResponse method:
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.NoNodeAvailableException;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.script.mustache.SearchTemplateRequestBuilder;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.aggregations.Aggregation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Repository;
#Slf4j
#Repository
#NoArgsConstructor
public abstract class NewBaseElasticsearchRepository {
#Autowired
protected NewIndexLocator newIndexLocator;
#Value(value = "${elasticsearch.client.timeout}")
private Long timeout;
#Autowired
protected TransportClient transportClient;
#Autowired
protected ThresholdService thresholdService;
#Autowired
protected MessageSource messageSource;
/**
* #param script the name of the script to be executed
* #param templateParams a map of the parameters to be sent to the script
* #param indexName the index to target (an empty indexName will search all indexes)
*
* #return a Search Response object containing details of the request results from Elasticsearch
*
* #throws NoNodeAvailableException thrown when the transport client cannot connect to any ES Nodes (or Coordinators)
* #throws Exception thrown for all other request errors such as parsing and non-connectivity related issues
*/
protected SearchResponse getSearchResponse(String script, Map<String, Object> templateParams, String... indexName) {
log.debug("transport client >> index name --> {}", Arrays.toString(indexName));
SearchResponse searchResponse;
try {
searchResponse = new SearchTemplateRequestBuilder(transportClient)
.setScript(script)
.setScriptType(ScriptType.STORED)
.setScriptParams(templateParams)
.setRequest(new SearchRequest(indexName))
.execute()
.actionGet(timeout)
.getResponse();
} catch (NoNodeAvailableException e) {
log.error(ELASTIC_SEARCH_EXCEPTION_NOT_FOUND, e.getMessage());
throw new ElasticSearchException(ELASTIC_SEARCH_EXCEPTION_NOT_FOUND);
} catch (Exception e) {
log.error(ELASTIC_SEARCH_EXCEPTION, e.getMessage());
throw new ElasticSearchException(ELASTIC_SEARCH_EXCEPTION);
}
log.debug("searchResponse ==> {}", searchResponse);
return searchResponse;
}
So, I would be grateful if you could have a look on the example class and share your genuine solutions with me here about how could I mock TransportClient and get a proper response from SearchResponse object.
Note:
I tried to use ESTestCase from org.elasticsearch.test:framework:6.2.4 but faced jar hell issue and could't resolve it. In the meantime, I could't find any proper docs related to that or Java ES unit testing, in general.

Spock testing for SpringBoot RestController and mocking of exceptions

I am new to groovy way of testing RestController for spring boot application. I have a Controller class :
#RestController
#RequestMapping(value = "/onboarding/v1")
public class OnboardingController {
private static final Logger LOG = LoggerFactory.getLogger(OnboardingServiceImpl.class);
#Autowired
private OnboardingService onboardingService;
#RequestMapping(
value = "/service-models",
method = RequestMethod.POST,
consumes = { "multipart/form-data" },
produces = { "application/json" }
)
public ResponseEntity createServiceModel(
#RequestParam("name") final String name,
#RequestParam("file") final MultipartFile file
){
try {
final ServiceModelRequestData serviceModelRequestData =
new ServiceModelRequestData(name, file);
final ServiceModelDetail createdServiceModel =
onboardingService.createServiceModel(serviceModelRequestData);
return new ResponseEntity<>(createdServiceModel, HttpStatus.OK);
}
catch (MalformedContentException ex) {
LOG.error("Malformed Content:", ex);
return new ResponseEntity<>(errorMessage(ex), HttpStatus.BAD_REQUEST);
}
catch (ServiceModelNameAlreadyExistsException ex) {
LOG.error("Service Model Name Already Exists:", ex);
return new ResponseEntity<>(errorMessage(ex), HttpStatus.CONFLICT);
}
catch (ServiceUnavailableException ex) {
LOG.error("Service Unavailable currently:" + ex);
return new ResponseEntity<>(errorMessage(ex), HttpStatus.SERVICE_UNAVAILABLE);
}
}
//...
}
I am unable to find how to spock test the above class along with writing the spock test cases for exceptions and checking the desired response?I want to create a test case which throws an error when the method is called and returns the responseentity which I want to check contains the given Jason data along with the desired Http Status. Sample spock test for the code snippet would be highly appreciated.
EDITED:
The test Class is as below(along with comments of what I was trying to do):
package com.service.onboarding.web.controller
import spock.lang.Specification
import com.service.onboarding.business.OnboardingServiceImpl
import com.service.onboarding.business.api.OnboardingService
import com.service.onboarding.domain.exception.MalformedContentException
import com.service.onboarding.domain.exception.ServiceModelNameAlreadyExistsException
import com.service.onboarding.domain.exception.ServiceUnavailableException
import com.service.onboarding.domain.resource.Greeting
import groovy.json.internal.Exceptions
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc
import org.springframework.web.multipart.MultipartFile
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.ResponseEntity
import spock.lang.Unroll
import static org.mockito.BDDMockito.given
/*#WebMvcTest
public class OnboardingControllerSpec extends Specification {
#MockBean
private OnboardingService onboardingService;
#Autowired
private MockMvc mockMvc;
def "controller should return expected JSON content and OK response"() {
given: 'hello world service responds with greeting'
def name = "Emily"
given(onboardingService.getPersonalGreeting("${name}")).willReturn(new Greeting(1, "Hi, ${name}"));
when: 'hello world service is called with name provided'
def response = mockMvc.perform(get("/onboarding/v1?name=${name}"))
then: 'expected JSON returned and response code is OK'
response
.andExpect(status().isOk())
.andExpect(content().json("{'id': 1, 'content': 'Hi, ${name}'}"))
}
}*/
public class OnboardingControllerSpec extends Specification{
OnboardingServiceImpl service =new OnboardingServiceImpl()
OnboardingController controller
ResponseEntity response
#Unroll
def "HTTP response #statusCode when creating service model"() {
given:
if (exception) {
service = Stub() {
createServiceModel(_) >> { throw exception }
}
}
controller= new OnboardingController(onboardingService: service)
when:
response=controller.createServiceModel("test", Mock(MultipartFile))
then:
response.statusCode == statusCode
where:
exception | statusCode
null | OK
new MalformedContentException() | BAD_REQUEST
new ServiceModelNameAlreadyExistsException() | CONFLICT
new ServiceUnavailableException() | SERVICE_UNAVAILABLE
}
}
My service class is as follows:
package com.service.onboarding.business;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.service.onboarding.business.api.OnboardingService;
import com.service.onboarding.business.servicemanagement.ServiceModelRepository;
import com.service.onboarding.domain.exception.MalformedContentException;
import com.service.onboarding.domain.exception.ServiceModelDoesNotExistException;
import com.service.onboarding.domain.exception.ServiceModelInUseException;
import com.service.onboarding.domain.exception.ServiceModelNameAlreadyExistsException;
import com.onboarding.domain.exception.ServiceUnavailableException;
import com.service.onboarding.domain.requestdata.ServiceModelPaginationFilter;
import com.service.onboarding.domain.requestdata.ServiceModelRequestData;
import com.service.onboarding.domain.resource.Greeting;
import com.service.onboarding.domain.resource.ServiceModel;
import com.service.onboarding.domain.resource.ServiceModelDetail;
import com.service.onboarding.domain.responsedata.PaginatedServiceResponseData;
/*
* Sample service to demonstrate what the API would use to get things done
*/
#Service
public class OnboardingServiceImpl implements OnboardingService {
private final AtomicLong counter = new AtomicLong();
private static final String TEMPLATE = "Hello, %s!";
private static final Logger LOG = LoggerFactory.getLogger(OnboardingServiceImpl.class);
#Autowired
private ServiceModelRepository serviceModelRepository;
public OnboardingServiceImpl() {
}
#Override
public Greeting getPersonalGreeting(final String name) {
return new Greeting(counter.incrementAndGet(),
String.format(TEMPLATE, name));
}
#Override
public ServiceModelDetail createServiceModel(final ServiceModelRequestData serviceModelRequestData) throws MalformedContentException, ServiceModelNameAlreadyExistsException, ServiceUnavailableException {
return serviceModelRepository.create(serviceModelRequestData);
}
Your test should look like this:
package de.scrum_master.stackoverflow
import org.springframework.http.ResponseEntity
import org.springframework.web.multipart.MultipartFile
import spock.lang.Specification
import spock.lang.Unroll
import static org.springframework.http.HttpStatus.*
class OnboardingControllerTest extends Specification {
OnboardingService service = new OnboardingService()
OnboardingController controller
ResponseEntity response
#Unroll
def "HTTP response #statusCode when creating service model"() {
given:
if (exception) {
service = Stub() {
createServiceModel(_) >> { throw exception }
}
}
controller = new OnboardingController(onboardingService: service)
when:
response = controller.createServiceModel("test", Mock(MultipartFile))
then:
response.statusCode == statusCode
where:
exception | statusCode
null | OK
new MalformedContentException() | BAD_REQUEST
new ServiceModelNameAlreadyExistsException() | CONFLICT
new ServiceUnavailableException() | SERVICE_UNAVAILABLE
}
}
The console log (I switched to Java logging when replicating your use case) would be:
Mär 10, 2018 12:51:50 PM de.scrum_master.stackoverflow.OnboardingController createServiceModel
INFORMATION: Malformed Content: de.scrum_master.stackoverflow.MalformedContentException
Mär 10, 2018 12:51:50 PM de.scrum_master.stackoverflow.OnboardingController createServiceModel
INFORMATION: Service Model Name Already Exists: de.scrum_master.stackoverflow.ServiceModelNameAlreadyExistsException
Mär 10, 2018 12:51:50 PM de.scrum_master.stackoverflow.OnboardingController createServiceModel
INFORMATION: Service Unavailable currently: de.scrum_master.stackoverflow.ServiceUnavailableException
In my IDE (IntelliJ IDEA) the test result looks like this:
P.S.:
I know you are new to SO. So this was your free shot. Next time please provide an MCVE, i.e. a minimal, compilable and executable example, not just a code snippet without package name, imports and test class. You want help with Spock, so please write a Spock specification (test class), show it to us and explain your problem with it. That way we do not need to recreate your situation from scratch but can just concentrate on fixing your test and solving your problem.
You were lucky I just felt a bit bored, waiting for a guest to arrive, otherwise I would not have done that. Saying "I have nothing, please do everything for me" only shows that you are lazy. If you want help, make it easy for others to help and show some respect towards their time budget. They are doing it for free!

AttributeNotFoundException through JMX in Startup bean on Wildfly

Hello everyone!
I'm trying to load wildfly server's system properties through JMX in Startup bean's #PostConstruct method. It works fine on the already started server instance when deployment starts, but fails while starting with server instance bootstrapping.
Wildfly 11.0.0.CR1
Startup bean code:
package ru.wildfly.test.ejb.wildflyconsulregistrar.startup;
import ru.wildfly.test.ejb.wildflyconsulregistrar.api.ConsulRegistrar;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.inject.Inject;
#Startup
#Singleton
public class WildflyConsulRegistrarStartupBean {
#Inject
private ConsulRegistrar consulRegistrar;
#PostConstruct
public void initialize() {
registerServices();
}
private void registerServices() {
consulRegistrar.registerService("WildflyTestCluster");
}
.............
}
ConsulRegistrar code:
package ru.wildfly.test.ejb.wildflyconsulregistrar.impl;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.agent.model.NewService;
import ru.test.ejb.wildflyconsulregistrar.api.ConsulRegistrar;
import ru.wildfly.test.ejb.wildflyconsulregistrar.serversettings.api.CurrentServerNodeSettings;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
#Dependent
public class ConsulRegistrarImpl implements ConsulRegistrar {
...............
#Inject
private CurrentServerNodeSettings currentServerNodeSettings;
.............
#Override
public void registerService(String serviceName) {
String currentNodeName = currentServerNodeSettings.getCurrentNodeName();
........................
}
.......................
}
CurrentServerNodeSettings code:
package ru.wildfly.test.ejb.wildflyconsulregistrar.serversettings.impl;
import ru.wildfly.test.ejb.wildflyconsulregistrar.serversettings.api.CurrentServerNodeSettings;
import javax.enterprise.context.Dependent;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
#Dependent
public class CurrentServerNodeSettingsWildflyImpl implements CurrentServerNodeSettings {
....................
#Override
public String getCurrentNodeName() {
String currentNodeName = getPlatformMBeanServerAttributeValue(String.class, "jboss.as:system-property=server.name", "value");
return currentNodeName;
}
private <T> T getPlatformMBeanServerAttributeValue(Class<T> valueType, String objectName, String attributeName) {
T attributeValue = null;
try {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
Object attributeObject = mBeanServer.getAttribute(new ObjectName(objectName), attributeName);
attributeValue = attributeObject != null ? (valueType.cast(attributeObject)) : null;
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return attributeValue;
}
}
Error message:
Caused by: java.lang.IllegalStateException:
javax.management.AttributeNotFoundException:
"WFLYCTL0216: Management resource '[(\"system-property\" => \"server.name\")]' not found"
at ru.wildfly.test.ejb.wildflyconsulregistrar.serversettings.impl.CurrentServerNodeSettingsWildflyImpl
.getPlatformMBeanServerAttributeValue(CurrentServerNodeSettingsWildflyImpl.java:41)
I have found the same issue on jboss forum https://developer.jboss.org/message/971717#971717 , but it was unanswered.
Any suggestions?
This is a dependency problem during startup, i.e. the server name is set after your #PostConstruct method gets executed. Try to load the server name lazy when it is accessed from the application for the first time.
In Wildfly there is no generic way to enforce the sequence of deployments from the application despite the definition of module dependencies. But this won't help in your case.

Categories

Resources