SpringBoot and use / load a dynamic "applicationContext.xml" instead of hard coded one - spring-boot

I've seen a hundred examples of this:
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
#SpringBootApplication
#ImportResource("classpath:applicationContext.xml")
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
}
And I have been on a rabbit trail for many hours now.
I am building a framework....and I need load (a handful of dependencies, not all of them...) from the xml dependency injection file (aka, "beans") :
applicationContext.xml
and I need to name to be dynamic, not hard coded.
String myValue = "DefaultEnvVarValue";
String envValue = System.getenv("MYENVVARIABLENAME");
if (null != envValue )
{
myValue=envValue;
}
String topLevelAppContextFileName = "applicationContext." + myValue + ".xml";
Without springboot, I would do this:
ApplicationContext context = new ClassPathXmlApplicationContext(topLevelAppContextFileName);
Is there a way to pull this off with SpringBoot?
I found PropertySourcesPlaceholderConfigurer for property files, but cannot find anything for the dependency injection.
Sidenote:
Before I get a "xml bad" comment, most of my dependencies are annotation based. But I'm making a framework for others to use, and therefore I need a handful of them to be xml-driven.....aka, I have a legit reason to have some of the DI be xml driven.

This could work -
Config
public class DemoApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ac) {
ac = new ClassPathXmlApplicationContext(topLevelAppContextFileName);
}
}
Main
public static void main(String args[]) {
new SpringApplicationBuilder(Application.class)
.initializers(new DemoApplicationContextInitializer())
.run(
}

For future readers, I ended up doing this:
#SpringBootApplication
#ImportResource({"classpath*:applicationContext.xml"})
public class MySpringBootApplication {
public static void main(String[] args) {
try {
URL resource = MySpringBootApplication.class.getResource("/applicationContext.xml");
if (null == resource || StringUtils.isBlank(resource.getPath())) {
throw new FileNotFoundException("applicationContext.xml not found. The entry dependency injection file must be applicationContext.xml");
}
org.springframework.context.ConfigurableApplicationContext applicationContext = SpringApplication.run(MySpringBootApplication.class, args);
And then I put the "dynamic" part in the inside applicationContext.xml file.
Note the ":" delimiter that will allow a default value if the environment variable does not exist.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<import resource="projectContext.${MYENVVARIABLENAME:DefaultEnvVarValue}.xml"/>
That was simpler to implement, even though I technically have 2 files, instead of one.
So if the environment variable does not exist, it will default to importing the second file called:
projectContext.DefaultEnvVarValue.xml

Related

MockMvc tests always returns status code 200 for get requests

I I am very new to spring boot and only have been working with it for a couple of days, so I am also very confused about this project (which is not my own). I am supposed to write tests with MockMvc for the rest controller, however each test with MockMvc only returns the status code 200 although it should be a 404.
Here is one of the tests:
#WebMvcTest(ObjectController.class)
#SpringJUnitConfig(TestConfig.class)
public class MvcTest {
#Autowired
MockMvc mockMvc;
#Test
public void shouldReturn404() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/obj/123"))
.andExpect(MockMvcResultMatchers.status().is(HttpStatus.NOT_FOUND.value()));
}
}
This is my rest controller I would like to test.
#RestController
#RequestMapping("obj")
public class ObjectController {
#Autowired
MyClass myClass;
#GetMapping()
public List<MyObject> findAll() {
List<MyObject> objList;
objList = myClass.getObjects();
return objList;
}
#GetMapping("/{id}")
public MyObject findById(#PathVariable String id) {
for (MyObject obj : myClass.getObjects()) {
if (obj.getId().equals(id))
return obj;
}
return null;
}
}
Then there is this class:
public class MyClass {
private List<MyObject> objList = new ArrayList<MyObject>();
public MyObject addObject(MyObject obj) {
objList.add(obj);
return obj;
}
public void setObjects(List<MyObject> objList) {
this.objList = objList;
}
public synchronized List<MyObject> getObjects() {
return objList;
}
}
There is an xml file that belongs to the class looking like this which can be found in the resource folder:
<?xml version='1.0' encoding='ISO-8859-1'?>
<beans xmlns='http://www.springframework.org/schema/beans'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:context='http://www.springframework.org/schema/context'
xsi:schemaLocation='http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd'>
<bean id='MyClass' class='it.is.some.path.MyClass'>
<property name='objList'>
<list>
<ref bean='[R01] Object1' />
</list>
</property>
</bean>
</beans>
The referenced beans can be found in separate files under resources too, in a sub folder called 'objList' and look like this:
<?xml version='1.0' encoding='ISO-8859-1'?>
<beans xmlns='http://www.springframework.org/schema/beans'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:context='http://www.springframework.org/schema/context'
xsi:schemaLocation='http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd'>
<bean id='[R01] Object1' class='this.is.some.path.MyObject'>
<property name='id' value='123' />
</bean>
</beans>
The myclass.xml as well as the folder with all xmls of the objects are imported via #ImportResource in the Application class.
And then there is
public class MyObject {
public String id = RandomStringUtils.randomAlphanumeric(5);
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
am sorry for the lengthy explanation but I have been spending two days now trying to figure out how to get MockMvc running and I feel like I am completely stuck. I would be very, very grateful if anybody could help out.
I assume 200 is the correct response as you have a mapping for /obj/{id} where {id} is a placeholder for any path variable like 123 in /obj/123.
Even though your controller returns null from a code perspective, Spring won't map this automatically to a 404 not found if this is what you would expect.
So you can literally try it with every path variable, you'll always get HTTP 200 because there wasn't any exception and the DisptacherServlet could properly route the HTTP request to a controller mapping. HTTP 200 is the default response code if you don't specify anything.
If you want your controller endpoint to return 404 when your MyObject is null, you have to be more explicit about this, e.g.:
#GetMapping("/{id}")
public ResponseEntity<MyObject> findById(#PathVariable String id) {
for (MyObject obj : myClass.getObjects()) {
if (obj.getId().equals(id))
return ResponseEntity.ok(obj);
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}

Cucumber-spring is not finding Step Definitions

I need help!
so the following code works for me (Pure JUnit code)
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("classpath:/importMasterConfig.xml")
public class FeatureWrittenInJavaUsingSteps {
#Before()
public void setup(){
/*do something*/
}
#After
public void tearDown()
{
/*Do something*/
}
#Autowired
ItemServiceController service;
#Test
public void callingStepFunctionsExample(){
ItemServiceControllerTestsSteps steps = new ItemServiceControllerTestsSteps(service);
steps.I_prepare_a_X_item_for_the_X_dealer("only images and pricing", "furniture");
steps.I_perform_the_X_inventory_service_call("createItem");
steps.I_should_get_the_X_response_code("200");
steps.the_inventory_service_response_result_should_be_a_X_object("Vertical Item");
}
}
However, when I try to run this code using the Cucumber Feature, it can't seem to build correctly. I am assuming I am setting up the project wrong.
Here is my Step code:
#ContextConfiguration("classpath:cucumber.xml")
public class ItemServiceControllerTestsSteps {
//Common variables across steps - currently only local.
private VerticalItem itemToCreate;
private ServiceResponse response;
//Step specific variables.
#Autowired
private ItemServiceController itemService;
public ItemServiceControllerTestsSteps(ItemServiceController service){
itemService = service;
}
#Before()
public void setup(){/*Do something*/}
#After()
public void tearDown(){/*Do Something*/}
#Given("^I prepare a \"(.*)\" item for the \"(.*)\" dealer$")
public VerticalItem I_prepare_a_X_item_for_the_X_dealer(String itemType, String dealerType){ //Step function and factory in one.
/*Do stuff*/}
#When("^I perform the \"(.*)\" inventory service call$")
public void I_perform_the_X_inventory_service_call(String actionType){
/*Do Stuff*/}
#Then("^I should get the \"(.*)\" response code$")
public void I_should_get_the_X_response_code(String codeType){/*Do stuff*/}
#Then("^the inventory service response result should be a \"(.*)\" object$")
public void the_inventory_service_response_result_should_be_a_X_object(String expectedClassType){ /*Do Stuff*/}
}
Here is my cucumber.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="cucumber.runtime.java.spring stepDefinitions"/>
<context:annotation-config/>
<import resource="classpath:importMasterConfig.xml"/>
</beans>
Finally here is my Runner Class:
#RunWith(Cucumber.class)
#CucumberOptions(plugin = {"pretty", "rerun:rerun.txt", "html:target/local-html-report/"},
glue = "stepDefinitions.ItemServiceControllerTestsSteps")
public class CucumberRunner {}
If someone can please enlighten me why the JUnit runner works and the cucumber one doesn't I would be a very happy camper!
In the above code, I did a few things wrong but lets cover the big ones.
1) My Glue code String was incorrect, I need to pass in the package name, not the file name (should have been just stepDefinitions)
2) I was using Spring 3 instead of Spring 4 with Cucumber 1.2.2 - The latest Cucumber requires Spring 4.
The other stuff wasn't actually related to Spring and Cucumber.
Step definitions should be instance methods on the step definitions classes, and not class (static) methods.
Step definitions classes are instantiated (on demand) for each scenario, so no state should be leaked between scenarios.

What are the other ways to specify code for Insight to analyze?

Spring Insight documentation states:
A trace represents a thread of execution. It is usually started by an HTTP request but can also be started by a background job
My application architecture style is one of queues running in the background that I'd like to instrument as well. However, I can't figure out how to get Spring Insight to instrument these calls initiated by queued message. I.e. I'd like to instrument the trace after a message is read off of the queue.
How can I ensure Insight instruments these background jobs?
I ended up creating an aspect that targets all of the Command Handlers. It extends the AbstractOperationCollectionAspect, implements the collectionPoint aspect passing in the Handler as an argument to use when it implements the createOperation method.
I.e.
public aspect CommandHandlerOperationCollectionAspect extends AbstractOperationCollectionAspect
{
public pointcut collectionPoint():
execution(* com.xtrac.common.core.handler.ThreadedHandler.HandlerRunnable.executeActorHandler(com.xtrac.common.core.handler.Handler,java.lang.Object));
protected Operation createOperation(JoinPoint jp)
{
Object[] args = jp.getArgs();
com.xtrac.common.core.handler.Handler handler = (Handler) args[0];
Operation operation = new Operation()
.type(XTRACOperationType.COMMAND_HANDLER)
.label(handler.getClass().getSimpleName())
.sourceCodeLocation(getSourceCodeLocation(jp));
return operation;
}
#Override
public String getPluginName()
{
return HandlerPluginRuntimeDescriptor.PLUGIN_NAME;
}
#Override
public boolean isMetricsGenerator()
{
return true;
}
}
I also implemented an AbstractSingleTypeEndpointAnalyzer to fill out the analyzer:
public class HandlerEndPointAnalyzer extends AbstractSingleTypeEndpointAnalyzer
{
private static final HandlerEndPointAnalyzer INSTANCE=new HandlerEndPointAnalyzer();
private HandlerEndPointAnalyzer() {
super(XTRACOperationType.COMMAND_HANDLER);
}
public static final HandlerEndPointAnalyzer getInstance() {
return INSTANCE;
}
#Override
protected EndPointAnalysis makeEndPoint(Frame handlerFrame, int depth) {
Operation operation = handlerFrame.getOperation();
String resourceLabel = operation.getLabel();
String exampleRequest = EndPointAnalysis.getHttpExampleRequest(handlerFrame);
return new EndPointAnalysis(EndPointName.valueOf(resourceLabel),
resourceLabel,
exampleRequest,
getOperationScore(operation, depth),
operation);
}
being sure to add it as a descriptor:
public class HandlerPluginRuntimeDescriptor extends PluginRuntimeDescriptor {
public static final String PLUGIN_NAME = "handler";
private static final HandlerPluginRuntimeDescriptor INSTANCE=new HandlerPluginRuntimeDescriptor();
private static final List<? extends EndPointAnalyzer> epAnalyzers=
ArrayUtil.asUnmodifiableList(HandlerEndPointAnalyzer.getInstance());
private HandlerPluginRuntimeDescriptor() {
super();
}
public static final HandlerPluginRuntimeDescriptor getInstance() {
return INSTANCE;
}
#Override
public Collection<? extends EndPointAnalyzer> getEndPointAnalyzers() {
return epAnalyzers;
}
#Override
public String getPluginName() {
return PLUGIN_NAME;
}
}
All noted in the spring xml file:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:insight="http://www.springframework.org/schema/insight-idk"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/insight-idk http://www.springframework.org/schema/insight-idk/insight-idk-1.0.xsd">
<insight:plugin name="handler" version="${project.version}" publisher="XTRAC Solutions LLC" />
<insight:operation-group group="XTRAC Handlers" operation="command_handler_operation" />
<insight:operation-group group="XTRAC Handlers" operation="event_handler_operation" />
<insight:operation-group group="XTRAC Classic" operation="xtrac_workflow_operation" />
<insight:operation-view operation="command_handler_operation"
template="com/xtrac/insight/command_handler_operation.ftl" />
<insight:operation-view operation="event_handler_operation"
template="com/xtrac/insight/event_handler_operation.ftl" />
<insight:operation-view operation="xtrac_workflow_operation"
template="com/xtrac/insight/xtrac_workflow_operation.ftl" />
<bean id="handlerPluginEndPointAnalyzer"
class="com.xtrac.insight.HandlerEndPointAnalyzer"
factory-method="getInstance"
lazy-init="true"
/>
<bean id="handlerPluginRuntimeDescriptor"
class="com.xtrac.insight.HandlerPluginRuntimeDescriptor"
factory-method="getInstance"
lazy-init="true"
/>
</beans>
along with some ftls.
I also created a MethodOperationCollectionAspect to collect some of the web service calls that occur in these handers. This sets it up for a nice display that tells me a lot about what is going on during the hander operation, and how much time it takes. E.g.
This set up a framework for maintaining a monitor on the health of the application if I set up the base line Thresholds for the named handlers
This is very useful because I can then tell if the application is healthy. Otherwise, the endpoints default to <200 ms for healthy.

Spring TypeConverter fails on Camel RouteBuilder subclass

In an attempt to install my component on karaf I get the following error:
Caused by: org.apache.camel.CamelException: Cannot find any routes with this RouteBuilder reference: RouteBuilderRef[logparserRouteBean]
I've narrowed it down to a conversion error in AbstractBeanFactory using the SimpleTypeConverter as returned by the getTypeConverter().
Given that PerformanceLogRoute extends org.apache.camel.builder.RouteBuilder, how can the convertion fail??
Suggestions, and ideas to any solution is greatly appreciated.
UPDATE
package no.osl.cdms.profile.routes;
import no.osl.cdms.profile.api.TimeMeasurement;
import no.osl.cdms.profile.factories.EntityFactory;
import no.osl.cdms.profile.log.TimeMeasurementEntity;
import no.osl.cdms.profile.parser.LogLineRegexParser;
import org.apache.camel.builder.RouteBuilder;
import java.util.Map;
public class PerformanceLogRoute extends RouteBuilder {
public static final String PERFORMANCE_LOG_ROUTE_ID = "PerformanceLogRoute";
private static final String LOG_DIRECTORY = "C:/data";
private static final String LOG_FILE = "performance.log";
private static final int DELAY = 0;
private LogLineRegexParser logLineRegexParser = new LogLineRegexParser();
private EntityFactory entityFactory = EntityFactory.getInstance();
private static final String LOG_FILE_ENDPOINT = "stream:file? fileName="+LOG_DIRECTORY +"/"+LOG_FILE+"&scanStream=true&scanStreamDelay=" + DELAY;
private static final String DATABASE_ENDPOINT = "jpa:";
#Override
public void configure() throws Exception{
fromF(LOG_FILE_ENDPOINT, LOG_DIRECTORY, LOG_FILE, DELAY)
.convertBodyTo(String.class) // Converts input to String
.choice().when(body().isGreaterThan("")) // Ignores empty lines
.bean(logLineRegexParser, "parse") // Parses log entry into String map
.bean(entityFactory, "createTimemeasurement") // Parses log entry into database format
.split(body())
.choice().when(body().isNotNull())
.toF(DATABASE_ENDPOINT, body().getClass().toString())
.routeId(PERFORMANCE_LOG_ROUTE_ID);
}
public String toString() {
return PERFORMANCE_LOG_ROUTE_ID;
}
}
The xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camel="http://camel.apache.org/schema/spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<bean id="logparserRouteBean" class="no.osl.cdms.profile.routes.PerformanceLogRoute" />
<camelContext id="cdms-core-camel-context" xmlns="http://camel.apache.org/schema/spring">
<routeBuilder ref="logparserRouteBean" />
</camelContext>
</beans>
This is what I found at this moment. From what I remember it is identical to what caused the error, but I'll double check in the morning.

Maven plugin to validate Spring configuration?

Does anyone know of a Maven plugin that can be used to validate Spring configuration files? By validation, I mean:
Verify all beans reference a class on the build path
Verify all bean references refer to a valid bean definition
Verify no orphaned beans exist
Other configuration mistakes I'm sure I'm missing.
I searched around and didn't come up with anything.
A Maven plugin would be ideal for my purposes, but any other tools (Eclipse plugin, etc.) would be appreciated.
What we do on our project is simply write a JUnit test which loads the Spring configuration. This does a few of the things you described like:
Validate the XML
Ensures beans can be loaded with classes on the classpath (at least beans which aren't lazy-loaded)
It does not check that there are no orphan beans. There is no reliable way of doing this anyway considering from anywhere in your code, you can lookup beans directly given their ID. Just because a bean is not referenced by any other beans does not mean it is not used. In fact all Spring configs will have at least one bean which is not referenced by other beans because there always has to be a root to the hierarchy.
If you have beans which rely on real services like databases or something and you don't want to connect to these services in a JUnit test, you simply need to abstract the configuration to allow for test values. This can be easily accomplished with something like the PropertyPlaceholderConfigurer which allows you to have different properties specified in separate config files for each environment and then referenced by one beans definition file.
EDIT (to include sample code):
The way we do this is have at least 3 different spring files...
src/main/resources/applicationContext.xml
src/main/resources/beanDefinitions.xml
src/test/resources/testContext.xml
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<import resource="classpath:beanDefinitions.xml"/>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="file:path/environment.properties" />
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driver}" />
...
</bean>
... <!-- more beans which shouldn't be loaded in a test go here -->
</beans>
beanDefinitions.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="myBean" class="com.example.MyClass">
...
</bean>
<bean id="myRepo" class="com.example.MyRepository">
<property name="dataSource" ref="dataSource"/>
...
</bean>
... <!-- more beans which should be loaded in a test -->
</beans>
testContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<import resource="classpath:beanDefinitions.xml"/>
<bean id="dataSource" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.jdbc.datasource.DriverManagerDataSource"/>
</bean>
</beans>
There are many things going on here, let me explain...
The applicationContext.xml file is the main spring file for your whole application. It contains an PropertyPlaceHolder bean to allow certain property values to be configurable between different environments we deploy to (test vs. prod). It imports all of the main beans that the app needs to run. Any beans which should not be used in a test, like DB beans, or other classes which communicate with external services/resources should be definied in this file.
The beanDefinitions.xml file has all of your normal beans in it which don't rely on external things. These beans can and will reference beans defined in the appContext.xml file.
The testContext.xml file is the test version of the appContext. It needs versions of all beans defined in the appContext.xml file but we used a mocking library to instantiate these beans. This way the real classes aren't used and there is no risk of access external resources. This file also doesn't need the property placeholder bean.
Now that we have a test context which we aren't afraid to load from a test, here is the code to do it...
SpringContextTest.java
package com.example;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class SpringContextTest {
#Test
public void springContextCanLoad() {
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("testContext.xml"));
for (String beanName : factory.getBeanDefinitionNames()) {
Object bean = factory.getBean(beanName);
// assert anything you want
}
}
}
This may not be the optimal way of doing it; the ApplicationContext class is the recommended way of loading spring contexts. The above might be able to be replaced by:
#Test
public void springContextCanLoad() {
ApplicationContext context = new FileSystemXmlApplicationContext("classpath:testContext.xml");
}
I believe that one line will accomplish everything you need to verify your spring context is wired correctly. From there, you can load beans and assert like before.
Hope this helps!
Here's the URL of Spring IDE update site (Eclipse plugin). It does what you described above. Their site seems to be unavailable.
I came across this question when googling - I had exactly the same question.
I've written a (very much untested) Maven plugin to do this this. It currently only supports WARs but could easily be extended. In addition, I don't bother actually loading the beans since I don't want the hassle of having to maintain a large set of properties just to satisfy this plugin.
Here it is if it's ever any use:
package myplugins;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.ClassUtils;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Validates Spring configuration resource and class references
* using a classloader that looks at the specified WAR's lib and classes
* directory.
* <p/>
* It doesn't attempt to load the application context as to avoid the
* need to supply property files
* <br/>
* TODO: maybe one day supplying properties will become an optional part of the validation.
*
* #goal validate
* #aggregator
* #phase install
*/
public class WarSpringValidationMojo extends AbstractMojo
{
private final static String FILE_SEPARATOR = System.getProperty("file.separator");
/**
* Project.
* #parameter expression="${project}"
* #readonly
*/
private MavenProject project;
/**
* The WAR's root Spring configuration file name.
*
* #parameter expression="${applicationContext}" default-value="webAppConfig.xml"
*/
private String applicationContext;
/**
* The WAR's directory.
*
* #parameter expression="${warSourceDirectory}" default-value="${basedir}/target/${project.build.finalName}"
*/
private File warSourceDirectory;
#SuppressWarnings("unchecked")
public void execute() throws MojoExecutionException
{
try
{
if ("war".equals(project.getArtifact().getType()))
{
File applicationContextFile = new File(warSourceDirectory, "WEB-INF" + FILE_SEPARATOR + applicationContext);
File classesDir = new File(warSourceDirectory, "WEB-INF" + FILE_SEPARATOR + "classes");
File libDir = new File(warSourceDirectory, "WEB-INF" + FILE_SEPARATOR + "lib");
Set<URL> classUrls = new HashSet<URL>();
if (classesDir.exists())
{
classUrls.addAll(getUrlsForExtension(classesDir, "class", "properties"));
}
if (libDir.exists())
{
classUrls.addAll(getUrlsForExtension(libDir, "jar", "zip"));
}
ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader classLoader = new URLClassLoader(classUrls.toArray(new URL[classUrls.size()]), parentClassLoader);
ClassUtils.overrideThreadContextClassLoader(classLoader);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.setBeanClassLoader(classLoader);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.setValidating(true);
reader.loadBeanDefinitions(new FileSystemResource(applicationContextFile));
for (String beanName : factory.getBeanDefinitionNames())
{
validateBeanDefinition(classLoader, factory.getBeanDefinition(beanName), beanName);
}
getLog().info("Successfully validated Spring configuration (NOTE: validation only checks classes, " +
"property setter methods and resource references)");
}
else
{
getLog().info("Skipping validation since project artifact is not a WAR");
}
}
catch (Exception e)
{
getLog().error("Loading Spring beans threw an exception", e);
throw new MojoExecutionException("Failed to validate Spring configuration");
}
}
private void validateBeanDefinition(ClassLoader beanClassloader, BeanDefinition beanDefinition, String beanName) throws Exception
{
Class<?> beanClass = validateBeanClass(beanClassloader, beanDefinition, beanName);
validateBeanConstructor(beanDefinition, beanName, beanClass);
validateBeanSetters(beanDefinition, beanName, beanClass);
}
private Class<?> validateBeanClass(ClassLoader beanClassloader, BeanDefinition beanDefinition, String beanName) throws Exception
{
Class<?> beanClass;
try
{
beanClass = beanClassloader.loadClass(beanDefinition.getBeanClassName());
}
catch (ClassNotFoundException e)
{
throw new ClassNotFoundException("Cannot find " + beanDefinition.getBeanClassName() +
" for bean '" + beanName + "' in " + beanDefinition.getResourceDescription(), e);
}
return beanClass;
}
private void validateBeanConstructor(BeanDefinition beanDefinition, String beanName,
Class<?> beanClass) throws Exception
{
boolean foundConstructor = false;
ConstructorArgumentValues constructorArgs = beanDefinition.getConstructorArgumentValues();
Class<?>[] argTypes = null;
if (constructorArgs != null)
{
Constructor<?>[] constructors = beanClass.getDeclaredConstructors();
int suppliedArgCount = constructorArgs.getArgumentCount();
boolean isGenericArgs = !constructorArgs.getGenericArgumentValues().isEmpty();
for (int k = 0; k < constructors.length && !foundConstructor; k++)
{
Constructor<?> c = constructors[k];
knownConstructorLoop:
{
Class<?>[] knownConstructorsArgTypes = c.getParameterTypes();
if (knownConstructorsArgTypes.length == suppliedArgCount)
{
if (isGenericArgs)
{
foundConstructor = true; // TODO - support generic arg checking
}
else
{
for (int i = 0; i < knownConstructorsArgTypes.length; i++)
{
Class<?> argType = knownConstructorsArgTypes[i];
ConstructorArgumentValues.ValueHolder valHolder = constructorArgs.getArgumentValue(i,
argType);
if (valHolder == null)
{
break knownConstructorLoop;
}
}
foundConstructor = true;
}
}
}
}
}
else
{
try
{
Constructor c = beanClass.getConstructor(argTypes);
foundConstructor = true;
}
catch (Exception ignored) { }
}
if (!foundConstructor)
{
throw new NoSuchMethodException("No matching constructor could be found for bean '" +
beanName + "' for " + beanClass.toString() + " in " + beanDefinition.getResourceDescription());
}
}
private void validateBeanSetters(BeanDefinition beanDefinition, String beanName, Class<?> beanClass) throws Exception
{
MutablePropertyValues properties = beanDefinition.getPropertyValues();
List<PropertyValue> propList = properties.getPropertyValueList();
try
{
Method[] methods = beanClass.getMethods();
for (PropertyValue p : propList)
{
boolean foundMethod = false;
String propName = p.getName();
String setterMethodName = "set" + propName.substring(0, 1).toUpperCase();
if (propName.length() > 1)
{
setterMethodName += propName.substring(1);
}
for (int i = 0; i < methods.length && !foundMethod; i++)
{
Method m = methods[i];
foundMethod = m.getName().equals(setterMethodName);
}
if (!foundMethod)
{
throw new NoSuchMethodException("No matching setter method " + setterMethodName
+ " could be found for bean '" + beanName + "' for " + beanClass.toString() +
" in " + beanDefinition.getResourceDescription());
}
}
}
catch (NoClassDefFoundError e)
{
getLog().warn("Could not validate setter methods for bean " + beanName +
" since getting the methods of " + beanClass + " threw a NoClassDefFoundError: "
+ e.getLocalizedMessage());
}
}
private Collection<? extends URL> getUrlsForExtension(File file, String... extensions) throws Exception
{
Set<URL> ret = new HashSet<URL>();
if (file.isDirectory())
{
for (File childFile : file.listFiles())
{
ret.addAll(getUrlsForExtension(childFile, extensions));
}
}
else
{
for (String ex : extensions)
{
if (file.getName().endsWith("." + ex))
{
ret.add(file.toURI().toURL());
break;
}
}
}
return ret;
}
}
And the plugin's pom.xml:
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
... <my project's parent> ...
</parent>
<groupId>myplugins</groupId>
<artifactId>maven-spring-validation-plugin</artifactId>
<version>1.0</version>
<packaging>maven-plugin</packaging>
<name>Maven Spring Validation Plugin</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-project</artifactId>
<version>2.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.0.7.RELEASE</version>
</dependency>
</dependencies>
</project>
Once installed, run like so at the root level of your WAR module:
mvn myplugins:maven-spring-validation-plugin:validate

Resources