I know this question was asked a lot of time but i'm trying multiple solutions and can't make it work.
I am really new to Apache Camel, and I'm using it with Spring Boot.
What I want to do is have a global route to one of my folders, and when a file arrive in this folder trigger a process depending on a part of the file name.
Currently I just setup a route and try to trigger the process only for one of my file:
#Override
public void configure() throws Exception {
from("file://{{data.input.dir}}?moveFailed=errors&delete=true").choice()
.when(header("CamelFileName").endsWith(".zip"))
.process(myprocessor)
.end();
}
And my test:
#EndpointInject("mock:result") protected MockEndpoint resultEndpoint;
#Test
public void test() throws Exception {
ModelCamelContext mcc = camelContext.adapt(ModelCamelContext.class);
// Build a test route by adding an mock endpoint to the route
RouteDefinition route = mcc.getRouteDefinition(ROUTE_NAME);
RouteDefinition testRoute = route.to(resultEndpoint);
But here I have a null pointer exeception in the last line.
EDIT: Here is my Route definition
#Component public class MyRoute extends RouteBuilder {
public static final String ROUTE_NAME = "myRoute";
private final Processor myProcessor;
#Autowired public MyRoute(#Qualifier("my.processor") Processor myProcessor) {
this.myProcessor= myProcessor;
}
#Override public void configure() throws Exception {
from("file://{{data.input.dir}}?moveFailed=errors&delete=true").routeId(ROUTE_NAME).choice()
.when(header("CamelFileName").startsWith("ACK")).process(myProcessor).end();
}
}
First of all: What is the ROUTE_NAME you pass to get the route definition in your test? Because you have not defined a route ID in your route, Camel automatically generates a route ID. Have you really defined the generated route name in a constant? This name can change when you add other routes.
You should set a static self-defined route ID
from("whatever").routeId("import-file-route").choice()...
So that you can get the Route definition with a robust reference
mcc.getRouteDefinition("import-file-route")
Second: as far as I know, you cannot just call .to on the route definition. If you want to add a mock endpoint to your route, you have to advice the route.
This can be done for example by adding this method to your test-class...
public void addMockToEndOfRoute() throws Exception {
context.getRouteDefinition(""import-file-route"").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() {
weaveAddLast().to("mock:result");
}
});
}
... and then call addMockToEndOfRoute() in the test method.
Added to answer
Currently you import all files and try to limit routing to files with a specific name. Problem is that all imported files are deleted. So when you later try to read the corresponding DATA file, it could already be deleted.
You can limit the file-consumer to files with a specific name pattern with the include option that takes a regex
from("file://{{data.input.dir}}?include=ACK.*&...")
Like this only files with name "ACK[whatever]" are imported, so you can remove your content based router.
Related
Say I have a #RestController like below:
#RestController
#RequestMapping("/1st")
public class MyController {
#GetMapping("/2nd/aaa")
public void getAaa() {
...
}
#GetMapping("/2nd/bbb")
public void getBbb() {
...
}
}
I want to add methods to catch all requests with the base path "/1st" and "/1st/2nd" in between and then continue to the correct endpoint. I tried adding:
#GetMapping("/**")
public void doThisFirst() {
...
}
But that didn't work, a request to "/1st/2nd/bbb" still only landed on the method getBbb() only. Please help, thank you.
I want to add methods to catch all requests with the base path "/1st"
and "/1st/2nd" in between and then continue to the correct endpoint. I
tried adding:
#GetMapping("/**") public void doThisFirst() {
... }
Probably you want to execute some extra code (code in doThisFirst) before you execute the respecting code that each endpoint have.
There are 2 solutions here.
A) You define an aspect with #Before that will be executed before the code that your final endpoint has. Here is some example code
B) You define an interceptor which will be executed only before those 2 endpoints. Check here some previous SO answer.
Either the interceptor or the aspect should contain the code that you have in doThisFirst() and you want to execute before you reach the actual endpoint.
In every case this starting code should not be inside a controller, so you can remove the #GetMapping("/**") from the controller.
I have written a book catalog in Spring.
It collects books (pdf, epub, mobi, ebook) from a directory, collects some metadata from them, stores them in a DB and then puts them in a List that is made available to my views:
#Slf4j
#Controller
public class BookCatalogController {
// == Fields ==
private final BookService bookService;
#Autowired
public BookCatalogController(BookService bookService){this.bookService = bookService; }
// == Model attributes ==
#ModelAttribute
public List<Book> bookData(){ return bookService.getBooksFromMemory(); }
public static final File bookDirectory= new File("D:\\edu_repo\\ebooks_test\\");
.
.
.
// Catalog Simple View
#GetMapping(Mappings.CATALOG_SIMPLE)
public String catalogSimple(Model model){
log.info("catalogSimple method called");
// This is adding the entire BookManager book list into the model.
model.addAttribute(AttributeNames.BOOK_DATA, bookData());
return ViewNames.CATALOG_SIMPLE;
}
// Catalog Detail View
#GetMapping(Mappings.CATALOG_DETAIL)
public String catalogDetail(Model model){
log.info("catalogDetail method called");
// This is adding the entire BookManager book list into the model.
model.addAttribute(AttributeNames.BOOK_DATA, bookData());
return ViewNames.CATALOG_DETAIL;
}
.
.
.
#GetMapping(Mappings.LOAD_BOOKS)
public void loadBooks(Model model) {
bookService.loadBooksFromDirectory(bookDirectory);
}
}
Obviously I'm not using #GetMapping(Mappings.LOAD_BOOKS) properly as you can see in the error below:
The error:
There was an unexpected error (type=Internal Server Error, status=500).
Error resolving template [load-books], template might not exist or might not be accessible by any of the configured Template Resolvers
org.thymeleaf.exceptions.TemplateInputException: Error resolving template [load-books], template might not exist or might not be accessible by any of the configured Template Resolvers
How does one invoke a method like I am doing but without Spring trying to redirect the user to another view?
I'm not expecting the page to update at all since I'm not returning a View!
When you click a link in your browser with a load-books anchor, your browser sends it to the server and waits for result, which causes your page to be reloaded. Once the request to a load-books endpoint reached to the server, Spring MVC handles this and starting to looking up an appropriate controller with its method. It founds public void loadBooks(Model model) in your case. When Spring MVC invokes the method, it expects to obtain a view name to resolve and return back to your browser.
Since you haven't provided a View or String as a return type, Spring MVC used the endpoint's path as a view name (I'm not seeing your Mappings.LOAD_BOOKS constant, but it supposed to be load-books).
If you're not going to return any view back to the browser, you can annotate the method like that:
#GetMapping(Mappings.LOAD_BOOKS)
#ResponseBody
public void loadBooks(Model model) {
which tells Spring to treat void as a response body.
But it's not preventing a page refreshing, you'll just see an empty page after clicking the link. In order to fix this you can redirect a user to another page by returning the following string (without ResponseBody annotation on the method)
return "redirect:/path-to-redirect";
When Spring MVC sees this prefix it redirects you to another controller, but user going to notice that too.
If you really don't want to see a blank page for a moment, you'll have to use some JavaScript to perform AJAX request to the server when button is clicked.
Actually, it seems that you want to preload some files in a service by a given path. If it's all you want to do, you can use Spring's runners like that:
#Component
class Preloader implements ApplicationRunner {
private final BookCatalogService bookService;
#Autowired
public Preloader(BookCatalogService service) {
this.bookService = service;
}
#Override
public void run(ApplicationArguments args) throws Exception {
bookService.loadBooksFromDirectory(BookCatalogController.bookDirectory);
}
}
Spring automatically calls all registered runners when application is ready, so your code will be executed without having a user to visit load-books endpoint.
This feels like it should be a simple thing, but I'm still pretty new to SpringBoot, and the whole Servlet ecosystem, so it's not readily apparent. I would love an interface similar to HandlerInterceptor that allows me to modify the request and response object once I'm done in a controller. Even better would be to decorate mapping annotation, so I can specify which controllers need the operation.
The problem I'm solving right now, though I anticipate expanding this in the future, is that I have an encrypted header coming into my application that I would like to decrypt for use in the controller and then encrypt again on the way out.
EDIT: For clarity.
I have a rest controller, something like:
#RestController
public class PojoService {
#GetMapping(value = "/path/to/resource")
public ResponseEntity<SomeClass> getLocationData(
#RequestHeader(value = "EncryptedHeader", required = false) String ecryptedHeaderValue) {
DecryptionObject decryptedHeader = new DecryptionObject(pageHeaderValue);
SomePojo result = getResult();
return decryptedHeader.decorateResponseWithEncryptedHeader(result);
}
}
I would love to not have the DecryptionObject on every mapping, but rather, before I even get to the mapping, I decrypt the header via some filter or hook and then re-encrypt the header on the way out. Then my code would look something like:
#RestController
public class PojoService {
#GetMapping(value = "/path/to/resource", decryptHeader="EncryptedHeader")
public ResponseEntity<SomeClass> getLocationData(
#RequestHeader(value = "EncryptedHeader", required = false) String decryptedHeaderValue) {
SomePojo result = getResult();
return result;
}
}
I found that the HandlerInterceptor doesn't work because I cannot modify the request or response in the interceptor. Hope that clarifies the issue.
You can still use HandlerInterceptor. Create your class implementing HandlerInterceptor, and then register it using another class which extends WebMvcConfigurer.
#EnableWebMvc
#Configuration
#ComponentScan
public class MyWebConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new [...]); //Apply to all controllers
registry.addInterceptor(new [...]).addPathPatterns("path1","path2"); //Apply to specific paths to restrict to some controllers.
}
}
You also could do it using a Filter - create your Filter class and register it by declaring a #Bean of type FilterRegistrationBean - this also allows you to restrict to some paths.
UPDATE: You could do this with request attributes which can be set by interceptors (request.setAttribute("decryptedHeaderValue",<decrypted>). Or if you're specific about using headers, a filter would be more suitable for your purpose. Create a new wrapped request type that wraps the incoming request and does whatever you want, and pass this wrapper to the next filter in chain.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
[...]
HttpServletRequestWrapper decryptedRequest = new HttpServletRequestWrapper((HttpServletRequest) request) {
public String getHeader(String name) {
if (name.equals("DecryptedHeader")) {
String encrypted = super.getHeader("EncryptedHeader");
String decrypted = decrypt(encrypted);
return decrypted;
}
return super.getHeader(name); //Default behavior
}
}
chain.doFilter(decryptedRequest, response); //Pass on the custom request down
}
Then any class down the line (other filters, controllers etc) can just call request.getHeader("DecryptedHeader") to retrieve the decrypted header. This is just one of many similar approaches. You can restrict the paths for which this filter executes when registering it.
For response, there is a similar class HttpServletResponseWrapper which you can use for customization.
We can do this via addingAttribute in the interceptor
httpServletRequest.setAttribute(,);
I want the spring application to be able to go into the localhost and load a given path into the resource loader so it can be read by the server. The code below is trying to access a file a valid .txt file on my computer stored on my desktop.
What follows is my resource-loader implementation.
#Component
#Scope("prototype")
public class CustomResourceLoader implements ResourceLoaderAware {
#Autowired
private ResourceLoader rsld;
#Override
public void setResourceLoader(ResourceLoader rsld) {
// TODO Auto-generated method stub
this.rs = rs;
}
public void showResource(String path) throws IOException{
Resource resour = rsld.getResource("file:"+path);
File fl = resour.getFile();
System.out.println(fl.exists());
System.out.println(fl.getAbsoluteFile());
System.out.println(fl.getName());
}
}
The output to calling the show resource method is as follows:
false
/Users/wes/Documents/workspace/rest-services-AM/{
"/Users/wes/Desktop/wes.txt"}
wes.txt
I understand that this means that my resource is not returning an actual file so i can not use it. Any help would be appreciated.
Thanks.
As nobody seems to be aware of, I found out that it's just necessairy to use double slashes like:
#Value("\\\\MY-MACHINE\\thefile.txt")
private Resource file;
In some cases you may required a file: statement before, like: file:\\MY-MACHINE\thefile.txt"
I would like to configure and use a Spring 4.1 AsyncUncaughtExceptionHandler. According to the Spring team (see relevant comment here) one will be able to configure an AsyncUncaughtExceptionHandler either by with the <task:annotation-driven> or by implementing AsyncConfigurer as shown here:
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler() ;
}
Now my question is as follows: Is there another web-layer annotation similar to #ExceptionHandler that would work like a AsyncUncaughtExceptionHandler?
As stated in the comment, here's an approach I've taken:
It's about async data imports so all classes are called Import...
What I did not do (yet) is the uncaught exception handling, but reading your post made me think about it and it should be straight forward with Spring-AOP wrapping the Importer.process() methods. This will not be global solution but it would be adaptable for a complete application by using a more generalized Result object.
The Controller uses the ImportRequests to get processing (or done) messages. The Importer itself is not removing the results from the map but this is delegated to the controller instead (A user is clicking delete). We also have a #Scheduled task which cleans up done results after 1 hour to ensure there are not left-overs.
So here's part of the code that the Controller is able to get import results during processing:
#Service
public class ImportRequests {
private final Map<User, ImportResult> importRequests = new ConcurrentHashMap<>();
/** Add, remove, get methods for current user omitted */
}
public class ImportResult {
/** The done. */
private Future<Boolean> done;
/** The error messages. */
private List<String> messages = Collections.synchronizedList(new ArrayList<String>());;
}
#Service
public class ImportService {
#Autowired
private ImportRequests importRequests;
#Autowired
private Importer importer;
public ImportResult doImport(final ImportForm importForm) {
ImportResult result = new ImportResult();
importRequests.addImportResultForCurrentUser(result);
/* This is the actual Async call (process) */
result.setDone(importer.process(result));
return result;
}
}
#Service
public class ImporterImpl implements Importer {
/**
* doProcess will import the *big* file and update the result object with the necessary messages
*/
#Async
public Future<Boolean> process(ImportResult result) {
Boolean done = doProcess(result);
return new AsyncResult<Boolean>(done);
}
}
Hope this helps.
Original Text:
One possibility that I have used is the "#ControllerAdvice" on a class scanned by the servletcontext.
You simply create a method with the exception as a parameter and annotate that method with "#ExceptionHandler". You can even have multiple handlers for specific exception types.
The result of these methods are again handled by the DispatcherServlet, so you can render a view the same way as with request mappings.