Spring-Retry: Create custom annotation similar to #Retryable - spring-boot

I have a number of microservices which needs a retry mechanism if connection with database fails.
This retry mechanism has to be triggered when SQLException and HibernateException occurs.
Passing a proper interceptor in #Retryable will work but this has to be incorporated in all the microservices.
Can we make a custom annotation similar to #Retryable like #DatabaseRetryable which will trigger retry on SQLException and HibernateException.
Usage of this annotation would be roughly as following
#DatabaseRetryable
void executeQuery()
{
//some code
}

There are several approaches for this:
Use the spring-retry project and integrate that into your application. But as you stated this is not what you want. This framework provides more than just simple retries on exceptions and is much more extensive than it seems at first glance.
Use an AOP (Aspect Oriented Programming) model and libraries like AspectJ
Create a custom annotation, introspect your classes before you run the methods and see if it annotated with the #CustomRetryable and then run the retry method or not. This however is not very simple and needs to be properly integrated with your classes. Which in term depends on how your application is designed etc.
If you want to keep it as simple as possible: create a helper class to perform retries for you.
My suggestion is look at your problem, is you desired solution needed for more than just these retries? Then go with a library. Is it simple one/two use case scenarios then go with a utility class/method approach.
A very crude example of this could be a util class:
import java.util.logging.Level;
import java.util.logging.Logger;
public class RetryOperation {
public static void main(String args[]) {
retryOnException(() -> {throw new Exception();} , Exception.class, 4);
}
interface CustomSupplier<T> {
T get() throws Exception;
}
static <E extends Exception, T> T retryOnException(CustomSupplier<T> method, Class<E> exceptionClass, int retries) {
if (method == null) {
throw new IllegalArgumentException("Method may not be null");
}
if (exceptionClass == null) {
throw new IllegalArgumentException("Exception type needs to be provided");
}
int retryCount = 0;
T result = null;
while (retryCount < retries) {
try {
result = method.get();
} catch (Exception exception) {
if (exceptionClass.isAssignableFrom(exception.getClass()) && retryCount < retries) {
// log the exception here
retryCount++;
Logger.getLogger(RetryOperation.class.getName()).log(Level.INFO, String.format("Failed %d time to execute method retrying", retryCount));
} else {
throw exception;
}
}
}
return result;
}
}
Note that this is a crude example and should only function to explain my thinking behind it. Look at what you exactly need and design from there.

You can solve this by creating a meta-annotation with your desired name:
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Retryable(
value = { SQLException.class, HibernateException.class }
)
public #interface DatabaseRetryable {
}
You can use this meta-annotation as drop-in replacement for #Retryable. The same constraints apply - it just allows to configure some common behavior in a single place. You might also use this to use the same backOff for all related services.

Related

Vert.x: how to process HttpRequest with a blocking operation

I've just started with Vert.x and would like to understand what is the right way of handling potentially long (blocking) operations as part of processing a REST HttpRequest. The application itself is a Spring app.
Here is a simplified REST service I have so far:
public class MainApp {
// instantiated by Spring
private AlertsRestService alertsRestService;
#PostConstruct
public void init() {
Vertx.vertx().deployVerticle(alertsRestService);
}
}
public class AlertsRestService extends AbstractVerticle {
// instantiated by Spring
private PostgresService pgService;
#Value("${rest.endpoint.port:8080}")
private int restEndpointPort;
#Override
public void start(Future<Void> futureStartResult) {
HttpServer server = vertx.createHttpServer();
Router router = Router.router(vertx);
//enable reading of the request body for all routes
router.route().handler(BodyHandler.create());
router.route(HttpMethod.GET, "/allDefinitions")
.handler(this::handleGetAllDefinitions);
server.requestHandler(router)
.listen(restEndpointPort,
result -> {
if (result.succeeded()) {
futureStartResult.complete();
} else {
futureStartResult.fail(result.cause());
}
}
);
}
private void handleGetAllDefinitions( RoutingContext routingContext) {
HttpServerResponse response = routingContext.response();
Collection<AlertDefinition> allDefinitions = null;
try {
allDefinitions = pgService.getAllDefinitions();
} catch (Exception e) {
response.setStatusCode(500).end(e.getMessage());
}
response.putHeader("content-type", "application/json")
.setStatusCode(200)
.end(Json.encodePrettily(allAlertDefinitions));
}
}
Spring config:
<bean id="alertsRestService" class="com.my.AlertsRestService"
p:pgService-ref="postgresService"
p:restEndpointPort="${rest.endpoint.port}"
/>
<bean id="mainApp" class="com.my.MainApp"
p:alertsRestService-ref="alertsRestService"
/>
Now the question is: how to properly handle the (blocking) call to my postgresService, which may take longer time if there are many items to get/return ?
After researching and looking at some examples, I see a few ways to do it, but I don't fully understand differences between them:
Option 1. convert my AlertsRestService into a Worker Verticle and use the worker thread pool:
public class MainApp {
private AlertsRestService alertsRestService;
#PostConstruct
public void init() {
DeploymentOptions options = new DeploymentOptions().setWorker(true);
Vertx.vertx().deployVerticle(alertsRestService, options);
}
}
What confuses me here is this statement from the Vert.x docs: "Worker verticle instances are never executed concurrently by Vert.x by more than one thread, but can [be] executed by different threads at different times"
Does it mean that all HTTP requests to my alertsRestService are going to be, effectively, throttled to be executed sequentially, by one thread at a time? That's not what I would like: this service is purely stateless and should be able to handle concurrent requests just fine ....
So, maybe I need to look at the next option:
Option 2. convert my service to be a multi-threaded Worker Verticle, by doing something similar to the example in the docs:
public class MainApp {
private AlertsRestService alertsRestService;
#PostConstruct
public void init() {
DeploymentOptions options = new DeploymentOptions()
.setWorker(true)
.setInstances(5) // matches the worker pool size below
.setWorkerPoolName("the-specific-pool")
.setWorkerPoolSize(5);
Vertx.vertx().deployVerticle(alertsRestService, options);
}
}
So, in this example - what exactly will be happening? As I understand, ".setInstances(5)" directive means that 5 instances of my 'alertsRestService' will be created. I configured this service as a Spring bean, with its dependencies wired in by the Spring framework. However, in this case, it seems to me the 5 instances are not going to be created by Spring, but rather by Vert.x - is that true? and how could I change that to use Spring instead?
Option 3. use the 'blockingHandler' for routing. The only change in the code would be in the AlertsRestService.start() method in how I define a handler for the router:
boolean ordered = false;
router.route(HttpMethod.GET, "/allDefinitions")
.blockingHandler(this::handleGetAllDefinitions, ordered);
As I understand, setting the 'ordered' parameter to TRUE means that the handler can be called concurrently. Does it mean this option is equivalent to the Option #2 with multi-threaded Worker Verticles?
What is the difference? that the async multi-threaded execution pertains to the one specific HTTP request only (the one for the /allDefinitions path) as opposed to the whole AlertsRestService Verticle?
Option 4. and the last option I found is to use the 'executeBlocking()' directive explicitly to run only the enclosed code in worker threads. I could not find many examples of how to do this with HTTP request handling, so below is my attempt - maybe incorrect. The difference here is only in the implementation of the handler method, handleGetAllAlertDefinitions() - but it is rather involved... :
private void handleGetAllAlertDefinitions(RoutingContext routingContext) {
vertx.executeBlocking(
fut -> { fut.complete( sendAsyncRequestToDB(routingContext)); },
false,
res -> { handleAsyncResponse(res, routingContext); }
);
}
public Collection<AlertDefinition> sendAsyncRequestToDB(RoutingContext routingContext) {
Collection<AlertDefinition> allAlertDefinitions = new LinkedList<>();
try {
alertDefinitionsDao.getAllAlertDefinitions();
} catch (Exception e) {
routingContext.response().setStatusCode(500)
.end(e.getMessage());
}
return allAlertDefinitions;
}
private void handleAsyncResponse(AsyncResult<Object> asyncResult, RoutingContext routingContext){
if(asyncResult.succeeded()){
try {
routingContext.response().putHeader("content-type", "application/json")
.setStatusCode(200)
.end(Json.encodePrettily(asyncResult.result()));
} catch(EncodeException e) {
routingContext.response().setStatusCode(500)
.end(e.getMessage());
}
} else {
routingContext.response().setStatusCode(500)
.end(asyncResult.cause());
}
}
How is this different form other options? And does Option 4 provide concurrent execution of the handler or single-threaded like in Option 1?
Finally, coming back to the original question: what is the most appropriate Option for handling longer-running operations when handling REST requests?
Sorry for such a long post.... :)
Thank you!
That's a big question, and I'm not sure I'll be able to address it fully. But let's try:
In Option #1 what it actually means is that you shouldn't use ThreadLocal in your worker verticles, if you use more than one worker of the same type. Using only one worker means that your requests will be serialised.
Option #2 is simply incorrect. You cannot use setInstances with instance of a class, only with it's name. You're correct, though, that if you choose to use name of the class, Vert.x will instantiate them.
Option #3 is less concurrent than using Workers, and shouldn't be used.
Option #4 executeBlocking is basically doing Option #3, and is also quite bad.

Osgi ConfigurationAdmin delay in activating Component

I have a service that requires a configuration
#Component(service=InstrumenterService.class ,configurationPid = "InstrumenterService", configurationPolicy = ConfigurationPolicy.REQUIRE, scope = ServiceScope.PROTOTYPE)
public class InstrumenterService
This service is referenced inside another service :
#Component(service = SampleService.class, scope = ServiceScope.PROTOTYPE)
public class SampleService {
#Reference(cardinality = ReferenceCardinality.OPTIONAL, scope = ReferenceScope.PROTOTYPE_REQUIRED, policyOption = ReferencePolicyOption.GREEDY)
InstrumenterService coverageInstrumenter;
public boolean hasInstrumenter() {
if(coverageInstrumenter == null)
return false;
return true;
}
}
This SampleService is used inside a Main class hooked to the main osgi thread.
I'm using ComponentServiceObjects as I want to create on demand SampleServices.
#Component(immediate = true, property = "main.thread=true")
public class Main implements Runnable {
#Reference
ConfigurationAdmin cfgAdm;
#Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
private ComponentServiceObjects<SampleService> sampleServices;
public void run() {
if (cfgAdm != null) {
Configuration configuration;
try {
configuration = cfgAdm.getConfiguration("InstrumenterService", "?");
Hashtable<String, Object> props = new Hashtable<>();
props.put("some_prop", "some_value");
configuration.update(props);
} catch (IOException e1) {
e1.printStackTrace();
}
}
SampleService servicess = sampleServices.getService();
System.out.println(servicess.hasInstrumenter());
}
}
The problem I have is that the configuration set by the ConfigurationAdmin is not visible in the InstrumenterService unless I put a Thread.sleep(500); command after calling the configuration.update.
I'm not really confortable using a Thread.sleep command to ensure the configuration update is visible.
Is there an API to check that the configuration has been updated and is available to use ?
Thanks to Neil I was able to find a workable solution.
I used a ServiceTracker after the configuration was set to wait for the service:
BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext();
ServiceTracker serviceTracker = new ServiceTracker(bundleContext, InstrumenterService.class.getName(), null);
serviceTracker.open();
try {
serviceTracker.waitForService(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
serviceTracker.close();
The reason I needed ConfigurationAdmin in the first place is because there is an interface IInstrumenter which can be implemented by many different classes.
The name of this instrumenter is set in the ConfigurationAdmin and then further on in other services the required instrumeter service is fetch "automagically".
This way any number of instrumenter could be added to the application and only the name of the instrumeter needs to be known in order for it to be used.
I want to mention also that with OSGI we managed to split our monolith legacy application in more modules (~15) and they do not depend directly on each other but use an API layer.
Thanks again for the good job you are doing with OSGI.
As clarified in the comments, this code is not exactly realistic. In production code there is not normally a requirement to update a configuration record and then immediately obtain a service published by a component. This is because any such code makes too many assumptions about the effect of the configuration update.
A call to getServiceReference and getService returns only a snapshot of the service registry state at a particular instant. It is inherently unreliable to call getService expecting it to return a value.
In reality, we always use a pattern where we react to being notified of the existence of the service. This can be done in various ways, including ServiceListener and ServiceTracker, but the simplest is to write a component with a reference, e.g.:
#Component
public class MyComponent {
#Reference
SampleService service;
public void doSomething() {
println(service.hasInstrumenter());
}
}
This component has a mandatory reference to SampleService and will only be activated only when an instance of SampleService is available.

Handle Hibernate optimistic locking with Spring

I am using Hibernate and Spring Data, it will perform optimistic locking when insert or update an entity, and if the version in database doesn't match with the one to persist, it will throw exception StaleObjectStateException, in Spring, you need to catch it with ObjectOptimisticLockingFailureException.
What I want to do is catch the exception and ask the user to refresh the page in order to get the latest data from database like below:
public void cancelRequest()
{
try
{
request.setStatus(StatusEnum.CANCELLED);
this.request = topUpRequestService.insertOrUpdate(request);
loadRequests();
//perform other tasks...
} catch (ObjectOptimisticLockingFailureException ex)
{
FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
}
}
I assume it will also work with the code below but I have not tested it yet.
public void cancelRequest()
{
RequestModel latestModel = requestService.findOne(request.getId());
if(latestModel.getVersion() != request.getVersion())
{
FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
}
else
{
request.setStatus(StatusEnum.CANCELLED);
this.request = requestService.insertOrUpdate(request);
loadRequests();
//perform other tasks...
}
}
I need to apply this checking on everywhere I call requestService.insertOrUpdate(request); and I don't want to apply them one by one. Therefore, I decide to place the checking code inside the function insertOrUpdate(entity) itself.
#Transactional
public abstract class BaseServiceImpl<M extends Serializable, ID extends Serializable, R extends JpaRepository<M, ID>>
implements BaseService<M, ID, R>
{
protected R repository;
protected ID id;
#Override
public synchronized M insertOrUpdate(M entity)
{
try
{
return repository.save(entity);
} catch (ObjectOptimisticLockingFailureException ex)
{
FacesUtils.showErrorMessage(null, FacesUtils.getMessage("message.actionFailed"),
FacesUtils.getMessage("message.pleaseReload"));
return entity;
}
}
}
My main question is, there will be one problem with this approach. The caller side will not know whether the entity persisted successfully or not since the exception will be caught and handled inside the function, so the caller side will always assume the persist was success, and continue do the other tasks, which is I don't want. I want it to stop performing tasks if fail to persist:
public void cancelRequest()
{
try
{
request.setStatus(StatusEnum.CANCELLED);
this.request = topUpRequestService.insertOrUpdate(request);
//I want it to stop here if fail to persist, don't load the requests and perform other tasks.
loadRequests();
//perform other tasks...
} catch (ObjectOptimisticLockingFailureException ex)
{
FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
}
}
I know when calling the insertOrUpdate , I can catch the returned entiry by declaring new model variable, and compare it's version to the original one, if version is same, means the persistance was failed. But if I doing it this way, I have to write the version checking code on everywhere I call insertOrUpdate. Any better approach then this?
The closest way to being able to do this and not having to necessarily make significant code changes at all the invocation points would be to look into some type of Spring AOP advice that works similar to Spring's #Transactional annotation.
#FacesReloadOnException( ObjectOptimisticLockingFailureException.class )
public void theRequestHandlerMethod() {
// call your service here
}
The idea is that the #FacesReloadOnException annotation triggers an around advice that catches any exception provided in the annotation value and does basically handles the call the FacesUtils should any of those exception classes be thrown.
The other options you have available aren't going to be nearly as straight forward and will require that you touch all your usage points in some fashion, its just inevitable.
But I certainly would not consider putting the try/catch block in the service tier if you don't want to alter your service tier's method return types because the controllers are going to need more context as you've pointed out. The only way to push that try/catch block downstream would be if you returned some type of Result object that your controller could then inspect like
public void someControllerRequestMethod() {
InsertOrUpdateResult result = yourService.insertOrUpdate( theObject );
if ( result.isSuccess() ) {
loadRequests();
}
else {
FacesUtils.showErrorMessage( ... );
}
}
Otherwise you'd need to get creative if you want to somehow centralize this in your web tier. Perhaps a web tier utility class that mimics your BaseService interface like the following:
public <T extends BaseService, U> U insertOrUpdate(T service, U object, Consumer<U> f) {
try {
U result = service.insertOrUpdate( object );
f.accept( result );
return result;
}
catch ( ObjectOptimisticLockingFailureException e ) {
FacesUtils.showErrorMessage( ... );
}
}
But being frank, unless you have a lot of call sites that are similar enough to where such a generalization with a consumer like this makes sense, you may find its more effort and work to generalize it than it would to just place the try/catch block in the controller itself.

Does CompletableFuture have a corresponding Local context?

In the olden days, we had ThreadLocal for programs to carry data along with the request path since all request processing was done on that thread and stuff like Logback used this with MDC.put("requestId", getNewRequestId());
Then Scala and functional programming came along and Futures came along and with them came Local.scala (at least I know the twitter Futures have this class). Future.scala knows about Local.scala and transfers the context through all the map/flatMap, etc. etc. functionality such that I can still do Local.set("requestId", getNewRequestId()); and then downstream after it has travelled over many threads, I can still access it with Local.get(...)
Soooo, my question is in Java, can I do the same thing with the new CompletableFuture somewhere with LocalContext or some object (not sure of the name) and in this way, I can modify Logback MDC context to store it in that context instead of a ThreadLocal such that I don't lose the request id and all my logs across the thenApply, thenAccept, etc. etc. still work just fine with logging and the -XrequestId flag in Logback configuration.
EDIT:
As an example. If you have a request come in and you are using Log4j or Logback, in a filter, you will set MDC.put("requestId", requestId) and then in your app, you will log many log statements line this:
log.info("request came in for url="+url);
log.info("request is complete");
Now, in the log output it will show this:
INFO {time}: requestId425 request came in for url=/mypath
INFO {time}: requestId425 request is complete
This is using a trick of ThreadLocal to achieve this. At Twitter, we use Scala and Twitter Futures in Scala along with a Local.scala class. Local.scala and Future.scala are tied together in that we can achieve the above scenario still which is very nice and all our log statements can log the request id so the developer never has to remember to log the request id and you can trace through a single customers request response cycle with that id.
I don't see this in Java :( which is very unfortunate as there are many use cases for that. Perhaps there is something I am not seeing though?
If you come across this, just poke the thread here
http://mail.openjdk.java.net/pipermail/core-libs-dev/2017-May/047867.html
to implement something like twitter Futures which transfer Locals (Much like ThreadLocal but transfers state).
See the def respond() method in here and how it calls Locals.save() and Locals.restort()
https://github.com/simonratner/twitter-util/blob/master/util-core/src/main/scala/com/twitter/util/Future.scala
If Java Authors would fix this, then the MDC in logback would work across all 3rd party libraries. Until then, IT WILL NOT WORK unless you can change the 3rd party library(doubtful you can do that).
My solution theme would be to (It would work with JDK 9+ as a couple of overridable methods are exposed since that version)
Make the complete ecosystem aware of MDC
And for that, we need to address the following scenarios:
When all do we get new instances of CompletableFuture from within this class? → We need to return a MDC aware version of the same rather.
When all do we get new instances of CompletableFuture from outside this class? → We need to return a MDC aware version of the same rather.
Which executor is used when in CompletableFuture class? → In all circumstances, we need to make sure that all executors are MDC aware
For that, let's create a MDC aware version class of CompletableFuture by extending it. My version of that would look like below
import org.slf4j.MDC;
import java.util.Map;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.function.Supplier;
public class MDCAwareCompletableFuture<T> extends CompletableFuture<T> {
public static final ExecutorService MDC_AWARE_ASYNC_POOL = new MDCAwareForkJoinPool();
#Override
public CompletableFuture newIncompleteFuture() {
return new MDCAwareCompletableFuture();
}
#Override
public Executor defaultExecutor() {
return MDC_AWARE_ASYNC_POOL;
}
public static <T> CompletionStage<T> getMDCAwareCompletionStage(CompletableFuture<T> future) {
return new MDCAwareCompletableFuture<>()
.completeAsync(() -> null)
.thenCombineAsync(future, (aVoid, value) -> value);
}
public static <T> CompletionStage<T> getMDCHandledCompletionStage(CompletableFuture<T> future,
Function<Throwable, T> throwableFunction) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return getMDCAwareCompletionStage(future)
.handle((value, throwable) -> {
setMDCContext(contextMap);
if (throwable != null) {
return throwableFunction.apply(throwable);
}
return value;
});
}
}
The MDCAwareForkJoinPool class would look like (have skipped the methods with ForkJoinTask parameters for simplicity)
public class MDCAwareForkJoinPool extends ForkJoinPool {
//Override constructors which you need
#Override
public <T> ForkJoinTask<T> submit(Callable<T> task) {
return super.submit(MDCUtility.wrapWithMdcContext(task));
}
#Override
public <T> ForkJoinTask<T> submit(Runnable task, T result) {
return super.submit(wrapWithMdcContext(task), result);
}
#Override
public ForkJoinTask<?> submit(Runnable task) {
return super.submit(wrapWithMdcContext(task));
}
#Override
public void execute(Runnable task) {
super.execute(wrapWithMdcContext(task));
}
}
The utility methods to wrap would be such as
public static <T> Callable<T> wrapWithMdcContext(Callable<T> task) {
//save the current MDC context
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
setMDCContext(contextMap);
try {
return task.call();
} finally {
// once the task is complete, clear MDC
MDC.clear();
}
};
}
public static Runnable wrapWithMdcContext(Runnable task) {
//save the current MDC context
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
setMDCContext(contextMap);
try {
return task.run();
} finally {
// once the task is complete, clear MDC
MDC.clear();
}
};
}
public static void setMDCContext(Map<String, String> contextMap) {
MDC.clear();
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
}
Below are some guidelines for usage:
Use the class MDCAwareCompletableFuture rather than the class CompletableFuture.
A couple of methods in the class CompletableFuture instantiates the self version such as new CompletableFuture.... For such methods (most of the public static methods), use an alternative method to get an instance of MDCAwareCompletableFuture. An example of using an alternative could be rather than using CompletableFuture.supplyAsync(...), you can choose new MDCAwareCompletableFuture<>().completeAsync(...)
Convert the instance of CompletableFuture to MDCAwareCompletableFuture by using the method getMDCAwareCompletionStage when you get stuck with one because of say some external library which returns you an instance of CompletableFuture. Obviously, you can't retain the context within that library but this method would still retain the context after your code hits the application code.
While supplying an executor as a parameter, make sure that it is MDC Aware such as MDCAwareForkJoinPool. You could create MDCAwareThreadPoolExecutor by overriding execute method as well to serve your use case. You get the idea!
You can find a detailed explanation of all of the above here in a post about the same.

Spring Beans constructed multiple times?

I am teaching myself how to use REST Assured and Spring right now. I am running into a few problems with the Spring Beans. Below is the System.println output from the REST Assured gets every time a bean is created. As of now, it should actually only be created once per use of the beans (I would imagine or at least that's what I want).
Top of Cucumber Steps
private BasicApi guideBox;
private ApplicationContext context = new AnnotationConfigApplicationContext(BaseApiConfig.class);
It is important to note here that the Application Context is used in each scenario to give guideBox what it needs for each scenario.
The output below is currently generated for each cucumber scenario indicating that the 3 beans I am currently using are created and initialized before each scenario is run. This output occurs three times meaning the beans are created three times; once before each of the three scenarios. As you can see from the first result giving an error for too many API requests, this is all happening too fast and too often. Is there a way to stop bean creation until that specific bean is needed, or is every bean going to be created every time? Is there a way to create all the beans just one time instead of before every scenario?
Output During Each Scenario
{"error":"You are sending API requests too quickly. You are limited to 1 API request per second. Please refer to the API docs for more information."}
{"results":[{"id":2098,"title":"Arrested Development","alternate_titles":[],"container_show":0,"first_aired":"2003-11-02","imdb_id":"tt0367279","tvdb":72173,"themoviedb":4589,"freebase":"\/m\/02hct1","wikipedia_id":496020,"tvrage":{"tvrage_id":2649,"link":"http:\/\/www.tvrage.com\/shows\/id-2649"},"artwork_208x117":"http:\/\/static-api.guidebox.com\/091414\/thumbnails_small\/2098-4213483650-208x117-show-thumbnail.jpg","artwork_304x171":"http:\/\/static-api.guidebox.com\/091414\/thumbnails_medium\/2098-6882581105-304x171-show-thumbnail.jpg","artwork_448x252":"http:\/\/static-api.guidebox.com\/091414\/thumbnails_large\/2098-7307781619-448x252-show-thumbnail.jpg","artwork_608x342":"http:\/\/static-api.guidebox.com\/091414\/thumbnails_xlarge\/2098-677562375-608x342-show-thumbnail.jpg"}],"total_results":1}
{"results":[{"id":1,"region":"US","name":"United States","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/us.png"},{"id":12,"region":"AI","name":"Anguilla","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ai.png"},{"id":11,"region":"AG","name":"Antigua and Barbuda","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ag.png"},{"id":16,"region":"AR","name":"Argentina","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ar.png"},{"id":14,"region":"AM","name":"Armenia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/am.png"},{"id":3,"region":"AU","name":"Australia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/au.png"},{"id":17,"region":"AT","name":"Austria","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/at.png"},{"id":18,"region":"AZ","name":"Azerbaijan","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/az.png"},{"id":28,"region":"BS","name":"Bahamas","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/bs.png"},{"id":23,"region":"BH","name":"Bahrain","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/bh.png"},{"id":31,"region":"BY","name":"Belarus","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/by.png"},{"id":20,"region":"BE","name":"Belgium","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/be.png"},{"id":32,"region":"BZ","name":"Belize","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/bz.png"},{"id":25,"region":"BM","name":"Bermuda","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/bm.png"},{"id":27,"region":"BO","name":"Bolivia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/bo.png"},{"id":30,"region":"BW","name":"Botswana","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/bw.png"},{"id":8,"region":"BR","name":"Brazil","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/br.png"},{"id":108,"region":"VG","name":"British Virgin Islands","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/vg.png"},{"id":26,"region":"BN","name":"Brunei","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/bn.png"},{"id":22,"region":"BG","name":"Bulgaria","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/bg.png"},{"id":75,"region":"KH","name":"Cambodia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/kh.png"},{"id":4,"region":"CA","name":"Canada","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ca.png"},{"id":39,"region":"CV","name":"Cape Verde","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/cv.png"},{"id":79,"region":"KY","name":"Cayman Islands","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ky.png"},{"id":35,"region":"CL","name":"Chile","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/cl.png"},{"id":37,"region":"CO","name":"Colombia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/co.png"},{"id":38,"region":"CR","name":"Costa Rica","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/cr.png"},{"id":40,"region":"CY","name":"Cyprus","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/cy.png"},{"id":41,"region":"CZ","name":"Czech Republic","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/cz.png"},{"id":42,"region":"DK","name":"Denmark","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/dk.png"},{"id":43,"region":"DM","name":"Dominica","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/dm.png"},{"id":44,"region":"DO","name":"Dominican Republic","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/do.png"},{"id":46,"region":"EC","name":"Ecuador","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ec.png"},{"id":48,"region":"EG","name":"Egypt","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/eg.png"},{"id":114,"region":"SV","name":"El Salvador","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/sv.png"},{"id":47,"region":"EE","name":"Estonia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ee.png"},{"id":52,"region":"FM","name":"Federated States Of Micronesia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/fm.png"},{"id":51,"region":"FJ","name":"Fiji","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/fj.png"},{"id":50,"region":"FI","name":"Finland","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/fi.png"},{"id":9,"region":"FR","name":"France","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/fr.png"},{"id":55,"region":"GM","name":"Gambia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/gm.png"},{"id":7,"region":"DE","name":"Germany","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/de.png"},{"id":54,"region":"GH","name":"Ghana","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/gh.png"},{"id":56,"region":"GR","name":"Greece","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/gr.png"},{"id":53,"region":"GD","name":"Grenada","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/gd.png"},{"id":57,"region":"GT","name":"Guatemala","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/gt.png"},{"id":58,"region":"GW","name":"Guinea-Bissau","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/gw.png"},{"id":61,"region":"HN","name":"Honduras","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/hn.png"},{"id":60,"region":"HK","name":"Hong Kong","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/hk.png"},{"id":63,"region":"HU","name":"Hungary","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/hu.png"},{"id":67,"region":"IN","name":"India","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/in.png"},{"id":64,"region":"ID","name":"Indonesia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/id.png"},{"id":65,"region":"IE","name":"Ireland","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ie.png"},{"id":66,"region":"IL","name":"Israel","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/il.png"},{"id":69,"region":"IT","name":"Italy","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/it.png"},{"id":72,"region":"JP","name":"Japan","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/jp.png"},{"id":71,"region":"JO","name":"Jordan","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/jo.png"},{"id":81,"region":"LA","name":"Lao People\u2019s Democratic Republic","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/la.png"},{"id":88,"region":"LV","name":"Latvia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/lv.png"},{"id":82,"region":"LB","name":"Lebanon","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/lb.png"},{"id":86,"region":"LT","name":"Lithuania","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/lt.png"},{"id":87,"region":"LU","name":"Luxembourg","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/lu.png"},{"id":94,"region":"MO","name":"Macau","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/mo.png"},{"id":140,"region":"MY","name":"Malaysia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/my.png"},{"id":97,"region":"MT","name":"Malta","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/mt.png"},{"id":98,"region":"MU","name":"Mauritius","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/mu.png"},{"id":5,"region":"MX","name":"Mexico","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/mx.png"},{"id":93,"region":"MN","name":"Mongolia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/mn.png"},{"id":141,"region":"MZ","name":"Mozambique","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/mz.png"},{"id":142,"region":"NA","name":"Namibia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/na.png"},{"id":146,"region":"NL","name":"Netherlands","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/nl.png"},{"id":6,"region":"NZ","name":"New Zealand","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/nz.png"},{"id":145,"region":"NI","name":"Nicaragua","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ni.png"},{"id":143,"region":"NE","name":"Niger","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ne.png"},{"id":147,"region":"NO","name":"Norway","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/no.png"},{"id":149,"region":"OM","name":"Oman","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/om.png"},{"id":150,"region":"PA","name":"Panama","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/pa.png"},{"id":126,"region":"PY","name":"Paraguay","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/py.png"},{"id":151,"region":"PE","name":"Peru","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/pe.png"},{"id":153,"region":"PH","name":"Philippines","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ph.png"},{"id":155,"region":"PL","name":"Poland","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/pl.png"},{"id":125,"region":"PT","name":"Portugal","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/pt.png"},{"id":127,"region":"QA","name":"Qatar","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/qa.png"},{"id":89,"region":"MD","name":"Republic Of Moldova","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/md.png"},{"id":128,"region":"RO","name":"Romania","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ro.png"},{"id":129,"region":"RU","name":"Russia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ru.png"},{"id":130,"region":"SA","name":"Saudi Arabia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/sa.png"},{"id":134,"region":"SG","name":"Singapore","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/sg.png"},{"id":136,"region":"SK","name":"Slovakia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/sk.png"},{"id":135,"region":"SI","name":"Slovenia","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/si.png"},{"id":111,"region":"ZA","name":"South Africa","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/za.png"},{"id":49,"region":"ES","name":"Spain","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/es.png"},{"id":84,"region":"LK","name":"Sri Lanka","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/lk.png"},{"id":76,"region":"KN","name":"St. Kitts and Nevis","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/kn.png"},{"id":115,"region":"SZ","name":"Swaziland","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/sz.png"},{"id":133,"region":"SE","name":"Sweden","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/se.png"},{"id":34,"region":"CH","name":"Switzerland","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ch.png"},{"id":124,"region":"TW","name":"Taiwan","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/tw.png"},{"id":119,"region":"TJ","name":"Tajikistan","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/tj.png"},{"id":118,"region":"TH","name":"Thailand","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/th.png"},{"id":123,"region":"TT","name":"Trinidad and Tobago","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/tt.png"},{"id":122,"region":"TR","name":"Turkey","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/tr.png"},{"id":120,"region":"TM","name":"Turkmenistan","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/tm.png"},{"id":104,"region":"UG","name":"Uganda","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ug.png"},{"id":103,"region":"UA","name":"Ukraine","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ua.png"},{"id":10,"region":"AE","name":"United Arab Emirates","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ae.png"},{"id":2,"region":"GB","name":"United Kingdom","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/gb.png"},{"id":107,"region":"VE","name":"Venezuela","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/ve.png"},{"id":109,"region":"VN","name":"Vietnam","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/vn.png"},{"id":112,"region":"ZW","name":"Zimbabwe","map_img":"http:\/\/static-api.guidebox.com\/misc\/flags_iso\/48\/zw.png"}]}
Java Config File
Just in case there is something wrong with how I am configuring my config file, here it is.
#Configuration
#PropertySources(
#PropertySource(value = {"classpath:/properties/guideBox.properties"}))
public class BaseApiConfig
{
#Bean
public GuideBoxProperties properties()
{
return new GuideBoxProperties();
}
#Bean
public BasicApi provideBasicApi()
{
return new BasicApi();
}
#Bean
public BasicApi provideHome() throws Exception
{
return new BasicApi(properties().getApiFull());
}
#Bean
public BasicApi provideArrestedDevleopmentSearch() throws Exception
{
return new BasicApi(properties().getArrestedDevelopmentSearch());
}
#Bean
public BasicApi provideAllRegions() throws Exception
{
return new BasicApi(properties().getAllRegions());
}
}
A Cucumber Scenario
#Given("^that the database contains \"([^\"]*)\"$")
public void that_the_database_contains(String title)
{
showTitle = title;
try
{
guideBox = (BasicApi) context.getBean("provideArrestedDevleopmentSearch");
}
catch (Exception e)
{
guideBox.basicHandle("Init Error: ", e);
}
}
#When("^the client requests a database search for Arrested Development$")
public void the_client_requests_a_database_search_for_Arrested_Development()
{
try {
guideBox.searchJson("results.title");
}
catch (Exception e)
{
guideBox.basicHandle("Json Search Error: ", e);
}
}
#Then("^the title should be visible in the search results$")
public void the_title_should_be_visible_in_the_search_results()
{
try
{
Assert.assertThat(guideBox.getJsonResults(), CoreMatchers.containsString(showTitle));
}
catch(Exception e)
{
guideBox.basicHandle("Assertion Error: ", e);
}
}
UPDATE 1
I managed to fix the problem in a general sense. I made the BasicApi parameterized constructor less busy, resulting in a bit more code in the step defs to manually control some requests. This got rid of the too many API request error.
That being said, although the program now works as expected, my question does still stand, as I think (or hope) that there is probably a better way to do this than using the ApplicationContext the way I am using it.

Resources