Using Velocity's WebappResourceLoader with Spring
I'm trying to use Velocity to create an email template that is mailed by Spring's JavaMailSender class. The resource loader that I decided to use to find the Velocity template in my web app is WebappResourceLoader which is located in the Velocity tool jar.
However, depending on how I use the WebappResourceLoader, I get either a NPE when the web app starts up or the template can't be found.
If I specify the init properties for the Velocity engine in my Spring application context I get the NPE. My configuration is as follows:
<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
<property name="velocityProperties">
<props>
<prop key="resource.loader">webapp</prop>
<prop key="webapp.resource.loader.class">org.apache.velocity.tools.view.WebappResourceLoader</prop>
<prop key="webapp.resource.loader.path">/WEB-INF/velocity/</prop>
</props>
</property>
</bean>
The stack trace I get when the app is starting up is:
java.lang.NullPointerException
at org.apache.velocity.tools.view.WebappResourceLoader.getResourceStream(WebappResourceLoader.java:145)
at org.apache.velocity.runtime.resource.loader.ResourceLoader.resourceExists(ResourceLoader.java:224)
at org.apache.velocity.runtime.resource.ResourceManagerImpl.getLoaderForResource(ResourceManagerImpl.java:641)
at org.apache.velocity.runtime.resource.ResourceManagerImpl.getLoaderNameForResource(ResourceManagerImpl.java:624)
at org.apache.velocity.runtime.RuntimeInstance.getLoa开发者_如何学CderNameForResource(RuntimeInstance.java:1464)
at org.apache.velocity.runtime.VelocimacroFactory.initVelocimacro(VelocimacroFactory.java:159)
at org.apache.velocity.runtime.RuntimeInstance.init(RuntimeInstance.java:261)
at org.apache.velocity.app.VelocityEngine.init(VelocityEngine.java:107)
at org.springframework.ui.velocity.VelocityEngineFactory.createVelocityEngine(VelocityEngineFactory.java:251)
at org.springframework.ui.velocity.VelocityEngineFactoryBean.afterPropertiesSet(VelocityEngineFactoryBean.java:57)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1460)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1398)
To fix this I changed my application context to as below:
<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean" />
And then in the service class, right before the code to merge application data with the template (which is at /WEB-INF/velocity and named regemail.vm) I added the following code:
velocityEngine.addProperty("resource.loader", "webapp");
velocityEngine.addProperty("webapp.resource.loader.class", "org.apache.velocity.tools.view.WebappResourceLoader");
velocityEngine.addProperty("webapp.resource.loader.path", "/WEB-INF/velocity/");
velocityEngine.setApplicationAttribute("javax.servlet.ServletContext", "localhost:8080");
The app starts fine but when the email is going to be sent, I get the following error:
SEVERE: Servlet.service() for servlet default threw exception
org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource 'regmail.vm'
at org.apache.velocity.runtime.resource.ResourceManagerImpl.loadResource(ResourceManagerImpl.java:483)
at org.apache.velocity.runtime.resource.ResourceManagerImpl.getResource(ResourceManagerImpl.java:354)
at org.apache.velocity.runtime.RuntimeInstance.getTemplate(RuntimeInstance.java:1400)
at org.apache.velocity.app.VelocityEngine.mergeTemplate(VelocityEngine.java:370)
at org.apache.velocity.app.VelocityEngine.mergeTemplate(VelocityEngine.java:345)
at org.springframework.ui.velocity.VelocityEngineUtils.mergeTemplate(VelocityEngineUtils.java:58)
at org.springframework.ui.velocity.VelocityEngineUtils.mergeTemplateIntoString(VelocityEngineUtils.java:122)
at com.mywebapp.web.service.RegistrationServiceImpl$1.prepare(RegistrationServiceImpl.java:60)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:353)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:345)
at com.mywebapp.web.service.RegistrationServiceImpl.sendRegEmail(RegistrationServiceImpl.java:65)
at com.mywebapp.web.controller.SignUpController.onSubmit(SignUpController.java:97)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
I'm using Velocity version 1.6.4 and Velocity tools 2.0. Any help much appreciated. Thanks!
You need to pass ServletContext object:
velocityEngine.setApplicationAttribute("javax.servlet.ServletContext", request.getSession().getServletContext());
Another option would be using ClasspathResourceLoader
instead and putting your templates within classpath.
If you look at the documentation for VelocityEngineFactoryBean, you'll notice that there's a 'resourceLoaderPath' property that you can set. Given your configuration above, it looks like you've placed your Velocity templates under /WEB-INF/velocity/
, so use that as the value for 'resourceLoaderPath' and the factory bean should load your templates just fine.
Three years later, but here's another solution that does not require to adapt your Java code to pass the ServletContext to Velocity Engine.
Simply use VelocityConfigurer instead of VelocityEngineFactoryBean in your Spring Context file like this:
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="velocityPropertiesMap">
<map>
<entry key="runtime.log.invalid.reference"><value>true</value></entry>
<entry key="runtime.log.logsystem.class"><value>org.apache.velocity.runtime.log.Log4JLogChute</value></entry>
<entry key="runtime.log.logsystem.log4j.logger"><value>velocity</value></entry>
<entry key="input.encoding"><value>UTF-8</value></entry>
<entry key="output.encoding"><value>UTF-8</value></entry>
<entry key="directive.include.output.errormsg.start"><value></value></entry>
<entry key="directive.parse.max.depth"><value>10</value></entry>
<entry key="directive.set.null.allowed"><value>true</value></entry>
<entry key="velocimacro.library.autoreload"><value>true</value></entry>
<entry key="velocimacro.permissions.allow.inline"><value>true</value></entry>
<entry key="velocimacro.permissions.allow.inline.to.replace.global"><value>false</value></entry>
<entry key="velocimacro.permissions.allow.inline.local.scope"><value>false</value></entry>
<entry key="velocimacro.context.localscope"><value>false</value></entry>
<entry key="runtime.interpolate.string.literals"><value>true</value></entry>
<entry key="resource.manager.class"><value>org.apache.velocity.runtime.resource.ResourceManagerImpl</value></entry>
<entry key="resource.manager.cache.class"><value>org.apache.velocity.runtime.resource.ResourceCacheImpl</value></entry>
<entry key="resource.loader"><value>webapp, class, ds</value></entry>
<entry key="class.resource.loader.description"><value>Velocity Classpath Resource Loader</value></entry>
<entry key="class.resource.loader.class"><value>org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader</value></entry>
<entry key="webapp.resource.loader.class"><value>org.apache.velocity.tools.view.WebappResourceLoader</value></entry>
<entry key="webapp.resource.loader.path"><value>/WEB-INF/views/</value></entry>
<entry key="webapp.resource.loader.cache"><value>false</value></entry>
<entry key="webapp.resource.loader.modificationCheckInterval"><value>2</value></entry>
<entry key="ds.resource.loader.instance"><ref bean="templateLoader"/></entry>
<entry key="ds.resource.loader.resource.table"><value>templates</value></entry>
<entry key="ds.resource.loader.resource.keycolumn"><value>code</value></entry>
<entry key="ds.resource.loader.resource.templatecolumn"><value>content</value></entry>
<entry key="ds.resource.loader.resource.timestampcolumn"><value>updated</value></entry>
<entry key="ds.resource.loader.cache"><value>false</value></entry>
</map>
</property>
</bean>
<bean id="templateLoader"
class="org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader">
<property name="dataSource" ref="yourDataSource"></property>
</bean>
精彩评论