I have maven project in Java in which I have a property file (quartz.properties) under this directory:
/src/main/resources
Now I can use this property file in two ways from this class as shown below:
/**
* Create a StdSchedulerFactory that has been initialized via
* <code>{#link #initialize(Properties)}</code>.
*
* #see #initialize(Properties)
*/
public StdSchedulerFactory(Properties props) throws SchedulerException {
initialize(props);
}
/**
* Create a StdSchedulerFactory that has been initialized via
* <code>{#link #initialize(String)}</code>.
*
* #see #initialize(String)
*/
public StdSchedulerFactory(String fileName) throws SchedulerException {
initialize(fileName);
}
I am not sure how can I use StdSchedulerFactory class to provide the path of my quartz.properties file.
As of now I am providing hardcoded path like this but this is not the right way to provide the path since if anyone else is running this code in their desktop or laptop then it will not work. I will be running this application from my desktop and also I will be making a runnable jar as well so I want that my program should load my properties file dynamically without any hardcoded path.
public class TestingQuartz {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory factory = new StdSchedulerFactory(
"C:\\workspace\\tester_quartz\\quartzmain\\src\\main\\resources\\quartz.properties");
Scheduler scheduler = factory.getScheduler();
scheduler.start();
}
}
As your configuration file is in src/main/resources of a mavenized project, it will be embedded in the resulting artifact (jar, war...) you build with maven. Thus you should load the file "from the classpath" like this :
StdSchedulerFactory factory = new StdSchedulerFactory();
factory.initialize(this.getClass().getClassLoader().getResourceAsStream("quartz.properties"));
Since it appears to be on your class path you could do this:
getClass().getClassLoader().getResource("quartz.properties").toExternalForm()
Related
I need to write a simple configuration file using apache-commons-configuration but no matter what I try, it's not writing anything to the file.
This is how the file should look like
<config>
<foo>bar</foo>
</config>
This is what I'm doing to write the foo configuration:
private static final String USER_CONFIGURATION_FILE_NAME = "config.xml";
private final Path configFilePath = Paths.get(System.getProperty("user.home"), ".myapp",
USER_CONFIGURATION_FILE_NAME);
private final FileBasedConfigurationBuilder<XMLConfiguration> configBuilder=
new FileBasedConfigurationBuilder<>(XMLConfiguration.class)
.configure(new Parameters().xml().setFile(configFilePath.toFile()));
/**
* Sets the foo configuration to the given {#link String}
*
* #param foo The configuration to be set up
* #throws ConfigurationException If any error occur while setting the property on the
* configuration file
*/
public void setfoo(final String bar) throws ConfigurationException {
checkNotNull(bar);
final Configuration config = configBuilder.getConfiguration();
config.setProperty("foo", bar);
configBuilder.save();
}
/**
* Retrieves the foo set up on the configuration file
*
* #return The foo set up on the configuration file
* #throws ConfigurationException If any error occur while setting the property on the
* configuration file
* #throws NoSuchElementException If there is no foo set up
*/
public String getFoo() throws ConfigurationException {
return configBuilder.getConfiguration().getString("foo");
}
Am I missing something? In Apache Commons Configuration - File-based Configurations I can't see any other information needed to set up the file, so I really don't know what I'm missing here.
For some reason the FileBasedConfiguration is not setting up the xml file by itself, so I had to create it manually and set up the root element, like this:
configFilePath.toFile().createNewFile();
final Writer writer = Files.newBufferedWriter(configFilePath);
writer.write("<config></config>"); //sets the root element of the configuration file
writer.flush();
Shouldn't FileBasedConfiguration take care of this for me, or this is a step that's not documented on apache-commons?
Let say I have the follow code.
private static String configFile = null;
File cf = new File(configFile);
Configuration c = new Configuration();
if (cf.exists() && cf.isFile()) {
c.configure(cf);
} else {
c.configure(configFile);
}
I am wondering what is the difference between c.configure(cf) and c.configure(configFile). In my code,configFile is repsented as resource and cf is the the configFile object.
I found these two from this (api).
public Configuration configure(String resource)
throws HibernateException
public Configuration configure(File configFile)
throws HibernateException
The documentation of the API isn't explicitly clear, is it?
I tracked it as far as this class before getting fed up:
https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/internal/ConfigLoader.java
But it looks like in case of configure(String resource), it is the name of a resource as would be passed to the Java class loader to get a resource as a stream, i.e.:
http://docs.oracle.com/javase/7/docs/api/java/lang/Class.html#getResourceAsStream(java.lang.String)
Whereas, configure(File configFile), it uses a FileInputStream.
In either case Hibernate is still expecting the same XML format for the configuration.
I've written a class that loads external properties for use in my program, it is modeled off of this link: Load Properties File in Singleton Class. I've read here (FileNotFoundException with properties file) and here (Java Properties File not loading) of how to load an external property file, however I continue to get a FileNotFoundException. My properties file is external and is located in the same directory as my executable jar.
public class ExternalProperties extends Properties{
private static ExternalProperties instance = null;
private Properties properties;
private static String location;
protected ExternalProperties() throws IOException {
properties = new Properties();
properties.load(getClass().getResourceAsStream("test.properties"));
}
public static ExternalProperties getInstance(){
if(instance == null){
try{
instance = new ExternalProperties();
} catch (IOException e) {
e.printStackTrace();
}
}
return instance;
}
public static void setLocation(String path){
location = path;
}
}
I am passing the location of the property file through the command line such as:
java -jar chef-deploy-tests-0.0.0009-SNAPSHOT-jar-with-dependencies.jar test.properties
Any suggestions on what I am doing wrong?
It depends on your location of the test.properties file.
* <li> If the name begins with a {#code '/'}
* then the absolute name of the resource is the
* portion of the name following the {#code '/'}.
*
* <li> Otherwise, the absolute name is of the following form:
*
* <blockquote>
* {#code modified_package_name/name}
* </blockquote>
*
* <p> Where the {#code modified_package_name} is the package name of this
* object with {#code '/'} substituted for {#code '.'}.
I think your problem is that test.properties is NOT on your classpath. The way you are running it now is simply passing the file as an argument to the application. I think what you mean to do is add it to the application's classpath:
java -jar chef-deploy-tests-0.0.0009-SNAPSHOT-jar-with-dependencies.jar -classpath .
This will include all the files in the the current directory in the java classpath. Another option may be to put that test.properties file into a separate directory and specify that one instead of the '.'
I'm trying to write a test for a Mule flow that will involve dropping a file in a location, waiting for it to be processed by my flow and compare the output to see if it has been transformed correctly. My flow looks as follows:
<flow name="mainFlow" processingStrategy="synchronous">
<file:inbound-endpoint name="fileIn" path="${inboundPath}">
<file:filename-regex-filter pattern="myFile.csv" caseSensitive="true"/>
</file:inbound-endpoint>
...
<file:outbound-endpoint path="${outboundPath}" outputPattern="out.csv"/>
</flow>
Is there a way I can access the inboundPath and outboundPath Mule properties inside of my test class so that I can drop files and wait for output in the correct places?
The test class I'm using is:
public class MappingTest extends BaseFileToFileFunctionalTest {
#Override
protected String getConfigResources() {
return "mappingtest.xml";
}
#Test
public void testMapping() throws Exception {
dropInputFileIntoPlace("myFile.csv");
waitForOutputFile("out.csv", 100);
assertEquals(getExpectedOutputFile("expected-out.csv"), getActualOutputFile("out.csv"));
}
}
Which extends this class:
public abstract class BaseFileToFileFunctionalTest extends FunctionalTestCase {
private static final File INPUT_DIR = new File("/tmp/muletest/input");
private static final File OUTPUT_DIR = new File("/tmp/muletest/output");
private static final Charset CHARSET = Charsets.UTF_8;
#Before
public void setup() {
new File("/tmp/muletest/input").mkdirs();
new File("/tmp/muletest/output").mkdirs();
empty(INPUT_DIR);
empty(OUTPUT_DIR);
}
private void empty(File inputDir) {
for (File file : inputDir.listFiles()) {
file.delete();
}
}
protected File waitForOutputFile(String expectedFileName, int retryAttempts) throws InterruptedException {
boolean polling = true;
int attemptsRemaining = retryAttempts;
File outputFile = new File(OUTPUT_DIR, expectedFileName);
while (polling) {
Thread.sleep(100L);
if (outputFile.exists()) {
polling = false;
}
if (attemptsRemaining == 0) {
VisibleAssertions.fail("Output file did not appear within expected time");
}
attemptsRemaining--;
}
outputFile.deleteOnExit();
return outputFile;
}
protected void dropInputFileIntoPlace(String inputFileResourceName) throws IOException {
File inputFile = new File(INPUT_DIR, inputFileResourceName);
Files.copy(Resources.newInputStreamSupplier(Resources.getResource(inputFileResourceName)), inputFile);
inputFile.deleteOnExit();
}
protected String getActualOutputFile(String outputFileName) throws IOException {
File outputFile = new File(OUTPUT_DIR, outputFileName);
return Files.toString(outputFile, CHARSET);
}
protected String getExpectedOutputFile(String resourceName) throws IOException {
return Resources.toString(Resources.getResource(resourceName), CHARSET);
}
}
As you can see I'm currently creating temporary input/output directories. I'd like to make this part read from the Mule properties if possible? Thanks in advance.
After observing your test classes and code I could see that you want to dynamically create temp folders place files in them. And the flow should read the files from Temp Directory and write output to another Temp directory. Point to be noted is that Mule's Endpoints are created when the configuration is loaded. So the ${inbound} and ${outbound} should be provided to the mule flow by the time they are provided.
So one option can be to create a dummy flow pointing to the temp folders for testing.
or
Create a test properties file pointing to the temp folders and load that to your flow config, so that your flow endpoints will get the temp folder paths.
In any way path cannot be provided to the flow inbound endpoints after they have been created(on config load).
Update1:
As per your comment the solution with option would be like the following.
Seperate the properties loading part of the config into another config.
Like "mapping-core-config.xml,mappingtest.xml" where the mapping-core-config will have the tags to load the properties file.
Now create a test config file for the mapping-core-config.xml file which loads the test properties file. This should be used in your test config. This way without modifying or disturbing your main code, you can test your flows pointing to temp folders.
"mapping-core-test-config.xml,mappingtest.xml"
Note: The test config can reside in the src/test/resources folders.
Hope this helps.
I'd like to allow users to add/refresh/update/remove modules in the main project without the need of restart or redeploy. Users will be able to code their own modules and add them in the main project.
Technicaly, a module will be a JAR which may be "hot-started" and may contain :
spring controllers
services, ejbs...
resources (jsps, css, images, javascripts...)
So, when the user adds a module, the application have to register controllers, services, ejbs and map resources as intend. When he removes, the application unloads them.
Easy to say. Actually seems a lot more difficult to do.
Currently, I did it using Servlet 3.0 and web-fragment.xml. The main issue is that I have to redeploy everytime I update a module. I need to avoid that.
I read some docs about OSGi but I don't understand how I can link it with my project neither how It can load/unload on demand.
Can someone lead me to a solution or an idea?
What I use :
Glassfish 3.1.2
Spring MVC 3.1.3
Spring Security 3.1.3
Thanks.
EDIT:
I can now say that it is possible. Here's the way I will do :
Add module :
Upload the module.jar
Handle the file, expand in a module folder
Close Spring application context
Load JAR in a custom classloader where parent is WebappClassLoader
Copy resources in the main project (maybe it will be possible to find alternative, I hope but currently, this should work)
Refresh Spring application context
Remove module :
Close Spring application context
Unbind custom classloader and let it go to GC
Remove resources
Remove files from the module folder + jar if kept
Refresh Spring application context
For each, Spring have to scan another folder than
domains/domain1/project/WEB-INF/classes
domains/domain1/project/WEB-INF/lib
domains/domain1/lib/classes
And that's actually my current issue.
Technicaly, I found PathMatchingResourcePatternResolver and ClassPathScanningCandidateComponentProvider was involved. Now I need to tell them to scan specific folder/classes.
For the rest, I already did some tests and it should work as intended.
One point which will not be possible : ejbs in the jar.
I'll post some sources when I'd have done something usable.
Ok, I did it but I have really too much sources to post it here. I will explain step by step how I did but won't post the classloading part which is simple for an average skilled developper.
One thing is currently not supported by my code is the context config scan.
First, the explanation below depends on your needs and also your application server. I use Glassfish 3.1.2 and I did not find how to configure a custom classpath :
classpath prefix/suffix not supported anymore
-classpath parameter on the domain's java-config did not work
CLASSPATH environment did not work either
So the only available paths in classpath for GF3 are : WEB-INF/classes, WEB-INF/lib... If you find a way to do it on your application server, you can skip the first 4 steps.
I know this is possible with Tomcat.
Step 1 : Create a custom namespace handler
Create a custom NamespaceHandlerSupport with its XSD, spring.handlers and spring.schemas. This namespace handler will contain a redefinition of <context:component-scan/>.
/**
* Redefine {#code component-scan} to scan the module folder in addition to classpath
* #author Ludovic Guillaume
*/
public class ModuleContextNamespaceHandler extends NamespaceHandlerSupport {
#Override
public void init() {
registerBeanDefinitionParser("component-scan", new ModuleComponentScanBeanDefinitionParser());
}
}
The XSD contains only component-scan element which is a perfect copy of the Spring's one.
spring.handlers
http\://www.yourwebsite.com/schema/context=com.yourpackage.module.spring.context.config.ModuleContextNamespaceHandler
spring.schemas
http\://www.yourwebsite.com/schema/context/module-context.xsd=com/yourpackage/module/xsd/module-context.xsd
N.B.: I didn't override the Spring default namespace handler due to some issues like the name of the project which need to have a letter greater than 'S'. I wanted to avoid that so I made my own namespace.
Step 2 : Create the parser
This will be initialized by the namespace handler created above.
/**
* Parser for the {#code <module-context:component-scan/>} element.
* #author Ludovic Guillaume
*/
public class ModuleComponentScanBeanDefinitionParser extends ComponentScanBeanDefinitionParser {
#Override
protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
return new ModuleBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters);
}
}
Step 3 : Create the scanner
Here's the custom scanner which uses the same code as ClassPathBeanDefinitionScanner. The only code changed is String packageSearchPath = "file:" + ModuleManager.getExpandedModulesFolder() + "/**/*.class";.
ModuleManager.getExpandedModulesFolder() contains an absolute url. e.g.: C:/<project>/modules/.
/**
* Custom scanner that detects bean candidates on the classpath (through {#link ClassPathBeanDefinitionScanner} and on the module folder.
* #author Ludovic Guillaume
*/
public class ModuleBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
private ResourcePatternResolver resourcePatternResolver;
private MetadataReaderFactory metadataReaderFactory;
/**
* #see {#link ClassPathBeanDefinitionScanner#ClassPathBeanDefinitionScanner(BeanDefinitionRegistry, boolean)}
* #param registry
* #param useDefaultFilters
*/
public ModuleBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
super(registry, useDefaultFilters);
try {
// get parent class variable
resourcePatternResolver = (ResourcePatternResolver)getResourceLoader();
// not defined as protected and no getter... so reflection to get it
Field field = ClassPathScanningCandidateComponentProvider.class.getDeclaredField("metadataReaderFactory");
field.setAccessible(true);
metadataReaderFactory = (MetadataReaderFactory)field.get(this);
field.setAccessible(false);
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Scan the class path for candidate components.<br/>
* Include the expanded modules folder {#link ModuleManager#getExpandedModulesFolder()}.
* #param basePackage the package to check for annotated classes
* #return a corresponding Set of autodetected bean definitions
*/
#Override
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>(super.findCandidateComponents(basePackage));
logger.debug("Scanning for candidates in module path");
try {
String packageSearchPath = "file:" + ModuleManager.getExpandedModulesFolder() + "/**/*.class";
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
}
Step 4 : Create a custom resource caching implementation
This will allow Spring to resolve your module classes out of the classpath.
public class ModuleCachingMetadataReaderFactory extends CachingMetadataReaderFactory {
private Log logger = LogFactory.getLog(ModuleCachingMetadataReaderFactory.class);
#Override
public MetadataReader getMetadataReader(String className) throws IOException {
List<Module> modules = ModuleManager.getStartedModules();
logger.debug("Checking if " + className + " is contained in loaded modules");
for (Module module : modules) {
if (className.startsWith(module.getPackageName())) {
String resourcePath = module.getExpandedJarFolder().getAbsolutePath() + "/" + ClassUtils.convertClassNameToResourcePath(className) + ".class";
File file = new File(resourcePath);
if (file.exists()) {
logger.debug("Yes it is, returning MetadataReader of this class");
return getMetadataReader(getResourceLoader().getResource("file:" + resourcePath));
}
}
}
return super.getMetadataReader(className);
}
}
And define it in the bean configuration :
<bean id="customCachingMetadataReaderFactory" class="com.yourpackage.module.spring.core.type.classreading.ModuleCachingMetadataReaderFactory"/>
<bean name="org.springframework.context.annotation.internalConfigurationAnnotationProcessor"
class="org.springframework.context.annotation.ConfigurationClassPostProcessor">
<property name="metadataReaderFactory" ref="customCachingMetadataReaderFactory"/>
</bean>
Step 5 : Create a custom root classloader, module classloader and module manager
This is the part I won't post classes. All classloaders extend URLClassLoader.
Root classloader
I did mine as singleton so it can :
initialize itself
destroy
loadClass (modules classes, parent classes, self classes)
The most important part is loadClass which will allow context to load your modules classes after using setCurrentClassLoader(XmlWebApplicationContext) (see bottom of the next step). Concretly, this method will scan the children classloader (which I personaly store in my module manager) and if not found, it will scan parent/self classes.
Module classloader
This classloader simply adds the module.jar and the .jar it contains as url.
Module manager
This class can load/start/stop/unload your modules. I did like this :
load : store a Module class which represent the module.jar (contains id, name, description, file...)
start : expand the jar, create module classloader and assign it to the Module class
stop : remove the expanded jar, dispose classloader
unload : dispose Module class
Step 6 : Define a class which will help to do context refreshs
I named this class WebApplicationUtils. It contains a reference to the dispatcher servlet (see step 7). As you will see, refreshContext call methods on AppClassLoader which is actually my root classloader.
/**
* Refresh {#link DispatcherServlet}
* #return true if refreshed, false if not
* #throws RuntimeException
*/
private static boolean refreshDispatcherServlet() throws RuntimeException {
if (dispatcherServlet != null) {
dispatcherServlet.refresh();
return true;
}
return false;
}
/**
* Refresh the given {#link XmlWebApplicationContext}.<br>
* Call {#link Module#onStarted()} after context refreshed.<br>
* Unload started modules on {#link RuntimeException}.
* #param context Application context
* #param startedModules Started modules
* #throws RuntimeException
*/
public static void refreshContext(XmlWebApplicationContext context, Module[] startedModules) throws RuntimeException {
try {
logger.debug("Closing web application context");
context.stop();
context.close();
AppClassLoader.destroyInstance();
setCurrentClassLoader(context);
logger.debug("Refreshing web application context");
context.refresh();
setCurrentClassLoader(context);
AppClassLoader.setThreadsToNewClassLoader();
refreshDispatcherServlet();
if (startedModules != null) {
for (Module module : startedModules) {
module.onStarted();
}
}
}
catch (RuntimeException e) {
for (Module module : startedModules) {
try {
ModuleManager.stopModule(module.getId());
}
catch (IOException e2) {
e.printStackTrace();
}
}
throw e;
}
}
/**
* Set the current classloader to the {#link XmlWebApplicationContext} and {#link Thread#currentThread()}.
* #param context ApplicationContext
*/
public static void setCurrentClassLoader(XmlWebApplicationContext context) {
context.setClassLoader(AppClassLoader.getInstance());
Thread.currentThread().setContextClassLoader(AppClassLoader.getInstance());
}
Step 7 : Define a custom context loader listener
/**
* Initialize/destroy ModuleManager on context init/destroy
* #see {#link ContextLoaderListener}
* #author Ludovic Guillaume
*/
public class ModuleContextLoaderListener extends ContextLoaderListener {
public ModuleContextLoaderListener() {
super();
}
#Override
public void contextInitialized(ServletContextEvent event) {
// initialize ModuleManager, which will scan the given folder
// TODO: param in web.xml
ModuleManager.init(event.getServletContext().getRealPath("WEB-INF"), "/dev/temp/modules");
super.contextInitialized(event);
}
#Override
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
XmlWebApplicationContext context = (XmlWebApplicationContext)super.createWebApplicationContext(sc);
// set the current classloader
WebApplicationUtils.setCurrentClassLoader(context);
return context;
}
#Override
public void contextDestroyed(ServletContextEvent event) {
super.contextDestroyed(event);
// destroy ModuleManager, dispose every module classloaders
ModuleManager.destroy();
}
}
web.xml
<listener>
<listener-class>com.yourpackage.module.spring.context.ModuleContextLoaderListener</listener-class>
</listener>
Step 8 : Define a custom dispatcher servlet
/**
* Only used to keep the {#link DispatcherServlet} easily accessible by {#link WebApplicationUtils}.
* #author Ludovic Guillaume
*/
public class ModuleDispatcherServlet extends DispatcherServlet {
private static final long serialVersionUID = 1L;
public ModuleDispatcherServlet() {
WebApplicationUtils.setDispatcherServlet(this);
}
}
web.xml
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>com.yourpackage.module.spring.web.servlet.ModuleDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Step 9 : Define a custom Jstl view
This part is 'optional' but it brings some clarity and cleanness in the controller implementation.
/**
* Used to handle module {#link ModelAndView}.<br/><br/>
* <b>Usage:</b><br/>{#code new ModuleAndView("module:MODULE_NAME.jar:LOCATION");}<br/><br/>
* <b>Example:</b><br/>{#code new ModuleAndView("module:test-module.jar:views/testModule");}
* #see JstlView
* #author Ludovic Guillaume
*/
public class ModuleJstlView extends JstlView {
#Override
protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response) throws Exception {
String beanName = getBeanName();
// checks if it starts
if (beanName.startsWith("module:")) {
String[] values = beanName.split(":");
String location = String.format("/%s%s/WEB-INF/%s", ModuleManager.CONTEXT_ROOT_MODULES_FOLDER, values[1], values[2]);
setUrl(getUrl().replaceAll(beanName, location));
}
return super.prepareForRendering(request, response);
}
}
Define it in the bean config :
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="com.yourpackage.module.spring.web.servlet.view.ModuleJstlView"
p:prefix="/WEB-INF/"
p:suffix=".jsp"/>
Final step
Now you just need to create a module, interface it with ModuleManager and add resources in the WEB-INF/ folder.
After that you can call load/start/stop/unload. I personaly refresh the context after each operation except for load.
The code is probably optimizable (ModuleManager as singleton e.g.) and there's maybe a better alternative (though I did not find it).
My next goal is to scan a module context config which shouldn't be so difficult.