Set a spring integration router attribute programmatically - java

We have a Spring Integration route using a Router with an expression. This expression is set in the yml file.
I would like to create unit tests setting manually the value for this router attribute, but it doesn't seem to work.
Test
#Autowired
EventDrivenConsumer myrouter;
.....
((ExpressionEvaluatingRouter) myrouter.getHandler()).setPrimaryExpression(new SpelExpressionParser().doParseExpression("true"));
Part of the XML context
<int:router input-channel="catchweightExcluderChannel" expression="${rip.config.exclude_catchweight}" default-output-channel="productTransformerChannel">
<int:mapping value="true" channel="catchWeightFilteringChannel" />
<int:mapping value="false" channel="productTransformerChannel" />
</int:router>
But it seems my value overrided is not affecting the behaviour...it seems as once Spring reads the context, changing attributes of components does not affect their behaviour.

You can't change the property that way because expression is a ctor arg for the ExpressionEvaluatingRouter:
public class ExpressionEvaluatingRouter extends AbstractMessageProcessingRouter {
public ExpressionEvaluatingRouter(Expression expression) {
super(new ExpressionEvaluatingMessageProcessor<Object>(expression));
setPrimaryExpression(expression);
}
}
I think for your test use-case the #TestPropertySource would be the best solution:
#RunWith(SpringRunner.class)
#TestPropertySource(properties = "rip.config.exclude_catchweight:true")
UPDATE
but then why is there a mutator called setPrimaryExpression
The setPrimaryExpression() and its sibling getExpression() for visualization tools and logs to make expression-based components much cleaner for analysis. Since one components may have several expressions, we decided to distinguish a prime one and therefore that name for a setter.
The real hard work for expression evaluation is done there in the ExpressionEvaluatingMessageProcessor, which is ctor-based as well, as we see.
For your per method requirements I only can suggests to create ApplicationContext manually and call its setEnvironment(new MockEnvironment(). And already there you can populate a desired value for that expression in particular case.
You can't override that ctor-based initialization when your application context is already started.

Related

Programmatically accessing camel property from camel context

The project I am working at the moment uses camel as the routing framework.
When configuring camel context in spring we pass a property file that contains a bunch of global properties needed when configuring camel routes or for controlling run time behavior:
<camel:camelContext xmlns="http://camel.apache.org/schema/spring" id="my-id">
<camel:propertyPlaceholder location="my-system.properties" id="global-properties"/>
...
</camel:camelContext>
and say my-system.properties has an entry like below:
my-system.properties
# Global properties that control my-system configuration and run time
...
foo={{bar}}
...
When configuring the routes I can access foo property using the {{foo}} notation. It is also available to other beans using #PropertyInject annotation. However there is one use case in my design when a plain POJO not created by spring (an enum instead but this is not relevant) needs to access my foo property. Because this POJO it is passed the CamelContext as a method argument I find it natural to think I should be able to get the value of foo from there. However I spent a bit of time and could not figure out by myself how.
I know I can load the properties file again or even get the system property System.getProperty("bar") and everything will work but it looks like cheating to me.
There is an api on CamelContext to resolve property placeholders - its the resolvePropertyPlaceholders method:
http://camel.apache.org/maven/current/camel-core/apidocs/org/apache/camel/CamelContext.html#resolvePropertyPlaceholders(java.lang.String)
If your POJO is not being managed by the SpringContext I don't see any way you can automatically inject the property. Although your approach may not seem the most fancy or elegant, it has the advantage of not giving you any overhead you could have by using another injection tool.

Spring Integration - Invoking Methods in Application Code

I have a outbound-channel-adapter, where the relevant configuration is shown below.
<int:outbound-channel-adapter channel="foo-fileChannel" ref="foo-handlerTarget" method="handleFeedFile">
<int:poller fixed-delay="5000" receive-timeout="1000" max-messages-per-poll="10" />
</int:outbound-channel-adapter>
<int:channel id="foo-fileChannel">
<int:queue />
</int:channel>
<bean id="foo-handlerTarget" class="com.abc.FooFeedHandlerImpl">
<property name="fooDescriptorFile" value="${feed.foo.fooDescriptorFile}" />
<property name="fileIdRegex" ref="foo-fileRegex" />
<property name="processId" value="${feed.processId}" />
<property name="workingLocation" value="${feed.foo.workingLocation}" />
<property name="remoteLocation" value="${feed.foo.remoteLocation}" />
<property name="stalenessThreshold" value="${feed.foo.stalenessThreshold}" />
</bean>
And in FooFeedHandlerImpl...
public void handleFeedFile(File retrievedFile) {
handleFeedFile(retrievedFile, null);
}
public void handleFeedFile(File retrievedFile, String processKey) {
if (isHandlerForFileName(retrievedFile.getName())) {
processFeed(retrievedFile, processKey);
}
}
Questions:
Which handleFeedFile method gets invoked by the channel adapter?
When I invoke a method in the application code using Spring integration, how are the method parameters determined?
Thanks for any help!
Edit:
I ran my process locally (downloaded a local SFTP server - http://www.coreftp.com/server/index.html) and determined that the handleFeedFile(File file) method was invoked.
You probably want to refer to F.6 Message Mapping rules and conventions.
Multiple parameters could create a lot of ambiguity with regards to determining the appropriate mappings. The general advice is to annotate your method parameters with #Payload and/or #Header/#Headers Below are some of the examples of ambiguous conditions which result in an Exception being raised.
and:
Multiple methods:
Message Handlers with multiple methods are mapped based on the same rules that are described above, however some scenarios might still look confusing.
If you're not in a position to annotate your target methods, then you might be able to use a SpEL expression to call your intended method:
3.3.2 Configuring An Outbound Channel Adapter
Like many other Spring Integration components, the and also provide support for SpEL expression evaluation. To use SpEL, provide the expression string via the 'expression' attribute instead of providing the 'ref' and 'method' attributes that are used for method-invocation on a bean. When an Expression is evaluated, it follows the same contract as method-invocation where: the expression for an will generate a message anytime the evaluation result is a non-null value, while the expression for an must be the equivalent of a void returning method invocation.
According to the documentation on Spring integration, the POJO (bean foo-handlerTarget) in your case will get called with a Message object containing the payload. Have you executed your code? I'd expect it generates a NoSuchMethodError.
You need a
public void handleFeedFile(Message<?> message);
method.

Spring prototypes inherit properties at runtime

What is the best approach for creating services that load a property set at runtime (bean is passed "xyz" and loads xyz.properties)? These properties files need to be able to be dropped into a folder outside the classpath before a command is entered to start the service (edit: this could happen at any time while the program is running).
I already have a system to do this that we've been using for over a year, but I'm migrating to spring to make the code more modular (customize services more easily through DI) and easier to maintain. My current method of creating an environment and then passing it with "this" to the dependencies just seems upside down from an IoC standpoint.
Is there a way to use a PropertyPlaceholderConfigurer without hardcoding the name of the property file? Maybe just a reference to a variable I pass into the constructor of the service that its dependencies can load? So far, it looks like I will have to create a service and inject its dependencies without any config and then call a separate load method for each to pass in the properties, but that just seems like I'm not really using spring.
USE CASE: The app will pool client connections to various servers and will forward requests from other applications to these servers. New profiles must be able to be added by non-programmers without taking down or restarting the app. The profiles will include basic things like host, port, and login info, but also more complex things like whether to use tcp/http, ssl/https (which will determine which client type to use), and timeouts and pool min/max/etc (which will need default values).
I tried with PropertyPlaceholderConfigurer and frankly, I couldn't wrap my head around it, somehow. It's easy enough to use when you use the existing options but I couldn't extend the framework.
So my approach was much more simple:
Create an annotation #InjectConfig which takes a config key as parameter.
In your beans/services, annotate fields or public setters with this annotation.
Write a BeanPostProcessor which takes options from a "config provider" and injects them into the fields / setters.
Now all you need is a config provider. Inject that into the post processor and you're done.
Note: I prefer annotating setters because that means you can easily configure your services from tests (just call the setters) without having to come up with smart names for 238576 config files.
EDIT If you have many configs, then a config factory might be a better choice:
Create a key to describe a config bundle (I usually use an enum or a new type here to prevent typos)
Put this key into the service when you create it (manually or via Spring)
Write a config factory that can return Properties or a Map for a config key.
Inject this factory into your service
In the init code of your service, use the key to lookup your config via the factory.
Using this approach, you can have a dummy factory that always returns the same thing in tests and a more complex factory for production.
The real factory can then be configured via spring so it knows where to look for configuration files. One approach is to register a java.io.File per config key. Now your concerns (configuring a service and loading configs) are completely separated.
PropertyPlaceholderConfigurer reads and initialize files on application context initialization and only once. So most probably you cannot configure it at runtime.
But you can have variables. For example, for my case I have default properties and user specific properties. So PropertyPlaceholderConfigurer loads properties from classpath first and after that is trying to find additional properties at defined location (user home folder). I user's property file exists so configurer loads it and override properties.
Here is my example:
<bean id="config" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true"/> <!-- do not throw exception if file not found -->
<property name="locations">
<list>
<value>classpath:server.properties</value>
<value>file:${user.home}/user.properties</value>
</list>
</property>
</bean>
I'm not sure that this answer is what you're exactly need. But I'm trying to guess what is your actual task. So if you need re-read properties runtime each time you access them you have to do it manually like you did before because spring application context helps you configure your application initial configuration.
It seems like the best approach may be to use a ServiceManager that contains the main ApplicationContext and then have each Service initialize its own FileSystemXmlApplicationContext with the main context as the parent like so:
public class ServiceManager {
ApplicationContext appContext;
String APP_HOME = System.getProperty("user.home") + File.separator;
public void init() {
//set main spring context
appContext = new AnnotationConfigApplicationContext(AppConfig.class);
}
public void start(String serviceName) throws Exception {
ApplicationContext serviceContext = new FileSystemXmlApplicationContext(
new String[]{APP_HOME + serviceName + ".xml"}, //path to child ctx
appContext); //reference to parent ctx to build hierarchy
Service service = (Service) serviceContext.getBean("service");
service.start();
}
}
The ApplicationContext is a bit heavy to be duplicating, but memory is pretty cheap these days and this provides total separation of concerns. I still have shared logging and an event system managed by the parent context, and each service is now simplified in its own config. I built a proof of concept using two services, and it seems to work fine so far. I'll add another comment once I finish the other services and finish testing.
reference:
http://techo-ecco.com/blog/spring-application-context-hierarchy-and-contextsingletonbeanfactorylocator/

#RequestMapping with "params" on same URL in different classes cause "IllegalStateException: Cannot map handler" in JUnit with SpringJUnit4ClassRunner

I perform refactoring and split controller into 2 controllers with:
#RequestMapping(value = "/graph.htm", method = RequestMethod.POST, params="first")
in first controller and:
#RequestMapping(value = "/graph.htm", method = RequestMethod.POST, params="second")
in second controller so these annotations lie in different files. When I build and use project all is fine (I put input HTML tag in my forms with different names: first and second).
But when I try to run JUnit controller test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:test-context.xml" })
I get trace:
Caused by: java.lang.IllegalStateException: Cannot map handler 'firstController'
to URL path [/graph.htm]: There is already handler
of type [class com.web.controller.SecondController] mapped.
at org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler(AbstractUrlHandlerMapping.java:294)
at org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler(AbstractUrlHandlerMapping.java:266)
at org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping.detectHandlers(AbstractDetectingUrlHandlerMapping.java:82)
at org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping.initApplicationContext(AbstractDetectingUrlHandlerMapping.java:58)
at org.springframework.context.support.ApplicationObjectSupport.initApplicationContext(ApplicationObjectSupport.java:119)
at org.springframework.web.context.support.WebApplicationObjectSupport.initApplicationContext(WebApplicationObjectSupport.java:72)
at org.springframework.context.support.ApplicationObjectSupport.setApplicationContext(ApplicationObjectSupport.java:73)
at org.springframework.context.support.ApplicationContextAwareProcessor.invokeAwareInterfaces(ApplicationContextAwareProcessor.java:117)
at org.springframework.context.support.ApplicationContextAwareProcessor.postProcessBeforeInitialization(ApplicationContextAwareProcessor.java:92)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:399)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1479)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:524)
When I comment out this:
#RequestMapping(value = "/graph.htm", method = RequestMethod.POST, params="second")
in second controller individual test for first controller successfully completed.
To resolve this issue I may use different URLs (value in #RequestMapping) but I don't understand why request mapping resolved for params in my production build of application and fail with SpringJUnit4ClassRunner.
Any help welcome!
PS. I use Spring 3.2.
PPS. I found mostly same issue Can I have the same mapping value with different param in a different Spring controller? but according to answers my production build also must fail?! But I run production build successfully!!
Also refer to:
#RequestMapping with 2 params in render method
Spring MVC 3: same #RequestMapping in different controllers, with centralised XML URL mapping (hybrid xml/annotations approach)
*PPS.
I check official docs for 3.2:
http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html#params%28%29
In a Servlet environment, parameter mappings are considered as restrictions
that are enforced at the type level. The primary path mapping (i.e. the
specified URI value) still has to uniquely identify the target handler, with
parameter mappings simply expressing preconditions for invoking the handler.
So seems I perform illegal coding practice...
This is what I understand when reading the official doc quoted in your question :
In a Servlet environment, parameter mappings are considered as restrictions
that are enforced at the type level. The primary path mapping (i.e. the
specified URI value) still has to uniquely identify the target handler within the class, with
parameter mappings simply expressing preconditions for invoking the handler.
I added the words "within the class".
And please note the enforced at type level. As I understand, it means that : in a servlet env. declaring the params at method level is quite the same as declaring the params at type level (at least if you only have only one method in your class).
Finally, if you take care to this sentence (same source):
When used at the type level, all method-level mappings inherit this parameter restriction (i.e. the type-level restriction gets checked before the handler method is even resolved).
I think all of this summarize why your are not doing illegal coding.
Regarding unit tests:
What is also important here are the words "In a Servlet environment. Obviously, when running unit tests : your not in a Servlet environment and that's probably why it is failing.
My neighbour colleague help me with debugging issue.
We compare production and test environment and found difference in context XML configuration.
Previous test configuration which fail:
<bean name="handlerMapping"
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean name="handlerAdapter"
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
New and working test context configuration:
<bean name="handlerMapping"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean name="handlerAdapter"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
Different spring classes use different mapping schema. Old uses per classes, newer uses per methods!!

Set System Properties or Environment Variables Before Property Placeholder with SpringJunit4ClassRunner

I have a main app-context.xml that defines a property placeholder with two locations: default properties file and an optional override file:
<context:property-placeholder
location="classpath:config.properties,${configOverride}"
ignore-resource-not-found="true" />
The optional override location allows specifying another properties file (e.g. "-DconfigOverride=file:/home/app/config.properties") with only the properties that should be overridden.
For my unit tests, I'm using a test context that imports app-context.xml:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:test-context.xml"})
public class UserServiceTest {
...
}
How can I set system properties or environment variables within the application before the application context is loaded? I would like to achieve the same effect as setting "-DconfigOverride=classpath:testConfig.properties" across all test classes without having to specify a command line arg, if possible.
One other alternative is setting the environment property in a #BeforeClass annotated method, which will be invoked before the Context Configuration happens.
#BeforeClass
public static void setSystemProps() {
System.setProperty("configOverride", "yourVal");
}
Thinking of ,
Extending SpringJUnit4ClassRunner and setting the system property
configOverride in its constructor/initialization block
Then passing ExtendedSpringJUnit4ClassRunner to #RunWith
Here's what I ended up doing - I didn't have to change any unit test classes. Unfortunately, I didn't set the "configOverride" property (see AhamedMustafaM's answer for one way to do that) and instead went with overriding the original property placeholder definition (I tried again after my initial failed attempts and got it to work).
I added the following line to my testContext.xml:
<!-- import the main app context -->
<import resource="classpath:appContext.xml" />
<!-- this is the line i added -->
<context:property-placeholder order="-999"
location="classpath:testConfig.properties"
ignore-unresolvable="true" />
Note the order="-999" attribute, which is used to ensure priority over the original property-placeholder definition(s). Also I set "ignore-unresolvable" to "true" to delegate any unresolvable properties to the original placeholder configurer.
My issue was similar but I wanted to set the spring.profiles.active environment variable and it turned out that I just needed to throw #ActiveProfiles() with the values I wanted on to the test itself.

Categories