Initial import of context support module

This commit is contained in:
Arjen Poutsma 2008-10-26 17:08:38 +00:00
parent da208c282a
commit 4df7d71c1e
66 changed files with 9477 additions and 0 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="org.springframework.context.support">
<property file="${basedir}/../build.properties"/>
<import file="${basedir}/../build-spring-framework/package-bundle.xml"/>
<import file="${basedir}/../spring-build/standard/default.xml"/>
</project>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://ivyrep.jayasoft.org/ivy-doc.xsl"?>
<ivy-module
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://incubator.apache.org/ivy/schemas/ivy.xsd"
version="1.3">
<info organisation="org.springframework" module="${ant.project.name}">
<license name="Apache 2.0" url="http://www.apache.org/licenses/LICENSE-2.0"/>
</info>
<configurations>
<include file="${spring.build.dir}/common/default-ivy-configurations.xml"/>
</configurations>
<publications>
<artifact name="${ant.project.name}"/>
<artifact name="${ant.project.name}-sources" type="src" ext="jar"/>
</publications>
<dependencies>
<!-- compile dependencies -->
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.logging" rev="1.1.1" conf="compile->runtime" />
<dependency org="org.springframework" name="org.springframework.core" rev="latest.integration" conf="compile->compile" />
<dependency org="org.springframework" name="org.springframework.context" rev="latest.integration" conf="compile->compile" />
<!-- optional dependencies -->
<dependency org="org.springframework" name="org.springframework.jdbc" rev="latest.integration" conf="optional->compile" />
<dependency org="org.springframework" name="org.springframework.transaction" rev="latest.integration" conf="optional->compile" />
<dependency org="javax.mail" name="com.springsource.javax.mail" rev="1.4.1" conf="optional->compile" />
<dependency org="org.apache.velocity" name="com.springsource.org.apache.velocity" rev="1.5.0" conf="optional->compile" />
<dependency org="org.freemarker" name="com.springsource.freemarker" rev="2.3.12" conf="optional->compile" />
<dependency org="net.sourceforge.jasperreports" name="com.springsource.net.sf.jasperreports" rev="2.0.5" conf="optional->compile" />
<dependency org="com.opensymphony.quartz" name="com.springsource.org.quartz" rev="1.6.0" conf="optional->compile" />
<dependency org="com.bea.commonj" name="com.springsource.commonj" rev="1.1.0" conf="optional->compile" />
<dependency org="net.sourceforge.ehcache" name="com.springsource.net.sf.ehcache" rev="1.4.1" conf="optional->compile" />
<!-- test dependencies -->
<dependency org="org.apache.log4j" name="com.springsource.org.apache.log4j" rev="1.2.15" conf="test->runtime"/>
<dependency org="org.junit" name="com.springsource.org.junit" rev="4.4.0" conf="test->runtime" />
</dependencies>
</ivy-module>

View File

@ -0,0 +1,141 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.parent</artifactId>
<version>3.0-M1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.springframework.context.support</artifactId>
<packaging>jar</packaging>
<name>Spring Framework: Context Support</name>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.transaction</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>com.springsource.javax.mail</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>com.springsource.org.apache.velocity</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>com.springsource.freemarker</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.sourceforge.jasperreports</groupId>
<artifactId>com.springsource.net.sf.jasperreports</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.bea.commonj</groupId>
<artifactId>com.springsource.commonj</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.opensymphony.quartz</groupId>
<artifactId>com.springsource.org.quartz</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.sourceforge.ehcache</groupId>
<artifactId>com.springsource.net.sf.ehcache</artifactId>
<optional>true</optional>
</dependency>
<!--
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.agent</artifactId>
</dependency>
<dependency>
<groupId>javax.ejb</groupId>
<artifactId>com.springsource.javax.ejb</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.jms</groupId>
<artifactId>com.springsource.javax.jms</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>com.springsource.javax.persistence</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.sourceforge.cglib</groupId>
<artifactId>com.springsource.net.sf.cglib</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>com.springsource.org.aspectj.weaver</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>com.springsource.org.jruby</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>com.springsource.org.codehaus.groovy</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.beanshell</groupId>
<artifactId>com.springsource.bsh</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>edu.emory.mathcs.backport</groupId>
<artifactId>com.springsource.edu.emory.mathcs.backport</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.oracle.oc4j</groupId>
<artifactId>com.springsource.oracle.classloader</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.sun.enterprise</groupId>
<artifactId>com.springsource.com.sun.enterprise.loader</artifactId>
<optional>true</optional>
</dependency>
-->
</dependencies>
</project>

View File

@ -0,0 +1,313 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.ehcache;
import java.io.IOException;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.constructs.blocking.BlockingCache;
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory;
import net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* FactoryBean that creates a named EHCache {@link net.sf.ehcache.Cache} instance
* (or a decorator that implements the {@link net.sf.ehcache.Ehcache} interface),
* representing a cache region within an EHCache {@link net.sf.ehcache.CacheManager}.
*
* <p>If the specified named cache is not configured in the cache configuration descriptor,
* this FactoryBean will construct an instance of a Cache with the provided name and the
* specified cache properties and add it to the CacheManager for later retrieval. If some
* or all properties are not set at configuration time, this FactoryBean will use defaults.
*
* <p>Note: If the named Cache instance is found, the properties will be ignored and the
* Cache instance will be retrieved from the CacheManager.
*
* @author Dmitriy Kopylenko
* @author Juergen Hoeller
* @since 1.1.1
* @see #setCacheManager
* @see EhCacheManagerFactoryBean
* @see net.sf.ehcache.Cache
*/
public class EhCacheFactoryBean implements FactoryBean, BeanNameAware, InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
private CacheManager cacheManager;
private String cacheName;
private int maxElementsInMemory = 10000;
private int maxElementsOnDisk = 10000000;
private MemoryStoreEvictionPolicy memoryStoreEvictionPolicy = MemoryStoreEvictionPolicy.LRU;
private boolean overflowToDisk = true;
private String diskStorePath;
private boolean eternal = false;
private int timeToLive = 120;
private int timeToIdle = 120;
private boolean diskPersistent = false;
private int diskExpiryThreadIntervalSeconds = 120;
private boolean blocking = false;
private CacheEntryFactory cacheEntryFactory;
private String beanName;
private Ehcache cache;
/**
* Set a CacheManager from which to retrieve a named Cache instance.
* By default, <code>CacheManager.getInstance()</code> will be called.
* <p>Note that in particular for persistent caches, it is advisable to
* properly handle the shutdown of the CacheManager: Set up a separate
* EhCacheManagerFactoryBean and pass a reference to this bean property.
* <p>A separate EhCacheManagerFactoryBean is also necessary for loading
* EHCache configuration from a non-default config location.
* @see EhCacheManagerFactoryBean
* @see net.sf.ehcache.CacheManager#getInstance
*/
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
/**
* Set a name for which to retrieve or create a cache instance.
* Default is the bean name of this EhCacheFactoryBean.
*/
public void setCacheName(String cacheName) {
this.cacheName = cacheName;
}
/**
* Specify the maximum number of cached objects in memory.
* Default is 10000 elements.
*/
public void setMaxElementsInMemory(int maxElementsInMemory) {
this.maxElementsInMemory = maxElementsInMemory;
}
/**
* Specify the maximum number of cached objects on disk.
* Default is 10000000 elements.
*/
public void setMaxElementsOnDisk(int maxElementsOnDisk) {
this.maxElementsOnDisk = maxElementsOnDisk;
}
/**
* Set the memory style eviction policy for this cache.
* Supported values are "LRU", "LFU" and "FIFO", according to the
* constants defined in EHCache's MemoryStoreEvictionPolicy class.
* Default is "LRU".
*/
public void setMemoryStoreEvictionPolicy(MemoryStoreEvictionPolicy memoryStoreEvictionPolicy) {
Assert.notNull(memoryStoreEvictionPolicy, "memoryStoreEvictionPolicy must not be null");
this.memoryStoreEvictionPolicy = memoryStoreEvictionPolicy;
}
/**
* Set whether elements can overflow to disk when the in-memory cache
* has reached the maximum size limit. Default is "true".
*/
public void setOverflowToDisk(boolean overflowToDisk) {
this.overflowToDisk = overflowToDisk;
}
/**
* Set whether elements are considered as eternal. If "true", timeouts
* are ignored and the element is never expired. Default is "false".
*/
public void setEternal(boolean eternal) {
this.eternal = eternal;
}
/**
* Set t he time in seconds to live for an element before it expires,
* i.e. the maximum time between creation time and when an element expires.
* It is only used if the element is not eternal. Default is 120 seconds.
*/
public void setTimeToLive(int timeToLive) {
this.timeToLive = timeToLive;
}
/**
* Set the time in seconds to idle for an element before it expires, that is,
* the maximum amount of time between accesses before an element expires.
* This is only used if the element is not eternal. Default is 120 seconds.
*/
public void setTimeToIdle(int timeToIdle) {
this.timeToIdle = timeToIdle;
}
/**
* Set whether the disk store persists between restarts of the Virtual Machine.
* The default is "false".
*/
public void setDiskPersistent(boolean diskPersistent) {
this.diskPersistent = diskPersistent;
}
/**
* Set the number of seconds between runs of the disk expiry thread.
* The default is 120 seconds.
*/
public void setDiskExpiryThreadIntervalSeconds(int diskExpiryThreadIntervalSeconds) {
this.diskExpiryThreadIntervalSeconds = diskExpiryThreadIntervalSeconds;
}
/**
* Set whether to use a blocking cache that lets read attempts block
* until the requested element is created.
* <p>If you intend to build a self-populating blocking cache,
* consider specifying a {@link #setCacheEntryFactory CacheEntryFactory}.
* @see net.sf.ehcache.constructs.blocking.BlockingCache
* @see #setCacheEntryFactory
*/
public void setBlocking(boolean blocking) {
this.blocking = blocking;
}
/**
* Set an EHCache {@link net.sf.ehcache.constructs.blocking.CacheEntryFactory}
* to use for a self-populating cache. If such a factory is specified,
* the cache will be decorated with EHCache's
* {@link net.sf.ehcache.constructs.blocking.SelfPopulatingCache}.
* <p>The specified factory can be of type
* {@link net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory},
* which will lead to the use of an
* {@link net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache}.
* <p>Note: Any such self-populating cache is automatically a blocking cache.
* @see net.sf.ehcache.constructs.blocking.SelfPopulatingCache
* @see net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache
* @see net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory
*/
public void setCacheEntryFactory(CacheEntryFactory cacheEntryFactory) {
this.cacheEntryFactory = cacheEntryFactory;
}
public void setBeanName(String name) {
this.beanName = name;
}
public void afterPropertiesSet() throws CacheException, IOException {
// If no CacheManager given, fetch the default.
if (this.cacheManager == null) {
if (logger.isDebugEnabled()) {
logger.debug("Using default EHCache CacheManager for cache region '" + this.cacheName + "'");
}
this.cacheManager = CacheManager.getInstance();
}
// If no cache name given, use bean name as cache name.
if (this.cacheName == null) {
this.cacheName = this.beanName;
}
// Fetch cache region: If none with the given name exists,
// create one on the fly.
Ehcache rawCache = null;
if (this.cacheManager.cacheExists(this.cacheName)) {
if (logger.isDebugEnabled()) {
logger.debug("Using existing EHCache cache region '" + this.cacheName + "'");
}
rawCache = this.cacheManager.getEhcache(this.cacheName);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Creating new EHCache cache region '" + this.cacheName + "'");
}
rawCache = createCache();
this.cacheManager.addCache(rawCache);
}
// Decorate cache if necessary.
Ehcache decoratedCache = decorateCache(rawCache);
if (decoratedCache != rawCache) {
this.cacheManager.replaceCacheWithDecoratedCache(rawCache, decoratedCache);
}
this.cache = decoratedCache;
}
/**
* Create a raw Cache object based on the configuration of this FactoryBean.
*/
private Cache createCache() {
return new Cache(
this.cacheName, this.maxElementsInMemory, this.memoryStoreEvictionPolicy,
this.overflowToDisk, null, this.eternal, this.timeToLive, this.timeToIdle,
this.diskPersistent, this.diskExpiryThreadIntervalSeconds, null, null, this.maxElementsOnDisk);
}
/**
* Decorate the given Cache, if necessary.
* @param cache the raw Cache object, based on the configuration of this FactoryBean
* @return the (potentially decorated) cache object to be registered with the CacheManager
*/
protected Ehcache decorateCache(Ehcache cache) {
if (this.cacheEntryFactory != null) {
if (this.cacheEntryFactory instanceof UpdatingCacheEntryFactory) {
return new UpdatingSelfPopulatingCache(cache, (UpdatingCacheEntryFactory) this.cacheEntryFactory);
}
else {
return new SelfPopulatingCache(cache, this.cacheEntryFactory);
}
}
if (this.blocking) {
return new BlockingCache(cache);
}
return cache;
}
public Object getObject() {
return this.cache;
}
public Class getObjectType() {
return (this.cache != null ? this.cache.getClass() : Ehcache.class);
}
public boolean isSingleton() {
return true;
}
}

View File

@ -0,0 +1,143 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.ehcache;
import java.io.IOException;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
/**
* FactoryBean that exposes an EHCache {@link net.sf.ehcache.CacheManager} instance
* (independent or shared), configured from a specified config location.
*
* <p>If no config location is specified, a CacheManager will be configured from
* "ehcache.xml" in the root of the class path (that is, default EHCache initialization
* - as defined in the EHCache docs - will apply).
*
* <p>Setting up a separate EhCacheManagerFactoryBean is also advisable when using
* EhCacheFactoryBean, as it provides a (by default) independent CacheManager instance
* and cares for proper shutdown of the CacheManager. EhCacheManagerFactoryBean is
* also necessary for loading EHCache configuration from a non-default config location.
*
* <p>Note: As of Spring 2.0, this FactoryBean will by default create an independent
* CacheManager instance, which requires EHCache 1.2 or higher.
*
* @author Dmitriy Kopylenko
* @author Juergen Hoeller
* @since 1.1.1
* @see #setConfigLocation
* @see #setShared
* @see EhCacheFactoryBean
* @see net.sf.ehcache.CacheManager
*/
public class EhCacheManagerFactoryBean implements FactoryBean, InitializingBean, DisposableBean {
protected final Log logger = LogFactory.getLog(getClass());
private Resource configLocation;
private boolean shared = false;
private String cacheManagerName;
private CacheManager cacheManager;
/**
* Set the location of the EHCache config file. A typical value is "/WEB-INF/ehcache.xml".
* <p>Default is "ehcache.xml" in the root of the class path, or if not found,
* "ehcache-failsafe.xml" in the EHCache jar (default EHCache initialization).
* @see net.sf.ehcache.CacheManager#create(java.io.InputStream)
* @see net.sf.ehcache.CacheManager#CacheManager(java.io.InputStream)
*/
public void setConfigLocation(Resource configLocation) {
this.configLocation = configLocation;
}
/**
* Set whether the EHCache CacheManager should be shared (as a singleton at the VM level)
* or independent (typically local within the application). Default is "false", creating
* an independent instance.
* @see net.sf.ehcache.CacheManager#create()
* @see net.sf.ehcache.CacheManager#CacheManager()
*/
public void setShared(boolean shared) {
this.shared = shared;
}
/**
* Set the name of the EHCache CacheManager (if a specific name is desired).
* @see net.sf.ehcache.CacheManager#setName(String)
*/
public void setCacheManagerName(String cacheManagerName) {
this.cacheManagerName = cacheManagerName;
}
public void afterPropertiesSet() throws IOException, CacheException {
logger.info("Initializing EHCache CacheManager");
if (this.shared) {
// Shared CacheManager singleton at the VM level.
if (this.configLocation != null) {
this.cacheManager = CacheManager.create(this.configLocation.getInputStream());
}
else {
this.cacheManager = CacheManager.create();
}
}
else {
// Independent CacheManager instance (the default).
if (this.configLocation != null) {
this.cacheManager = new CacheManager(this.configLocation.getInputStream());
}
else {
this.cacheManager = new CacheManager();
}
}
if (this.cacheManagerName != null) {
this.cacheManager.setName(this.cacheManagerName);
}
}
public Object getObject() {
return this.cacheManager;
}
public Class getObjectType() {
return (this.cacheManager != null ? this.cacheManager.getClass() : CacheManager.class);
}
public boolean isSingleton() {
return true;
}
public void destroy() {
logger.info("Shutting down EHCache CacheManager");
this.cacheManager.shutdown();
}
}

View File

@ -0,0 +1,10 @@
<html>
<body>
Support classes for the open source cache
<a href="http://ehcache.sourceforge.net">EHCache</a>,
allowing to set up an EHCache CacheManager and Caches
as beans in a Spring context.
</body>
</html>

View File

@ -0,0 +1,52 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail;
/**
* Exception thrown on failed authentication.
*
* @author Dmitriy Kopylenko
* @author Juergen Hoeller
*/
public class MailAuthenticationException extends MailException {
/**
* Constructor for MailAuthenticationException.
* @param msg message
*/
public MailAuthenticationException(String msg) {
super(msg);
}
/**
* Constructor for MailAuthenticationException.
* @param msg the detail message
* @param cause the root cause from the mail API in use
*/
public MailAuthenticationException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Constructor for MailAuthenticationException.
* @param cause the root cause from the mail API in use
*/
public MailAuthenticationException(Throwable cause) {
super("Authentication failed", cause);
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail;
import org.springframework.core.NestedRuntimeException;
/**
* Base class for all mail exceptions.
*
* @author Dmitriy Kopylenko
*/
public abstract class MailException extends NestedRuntimeException {
/**
* Constructor for MailException.
* @param msg the detail message
*/
public MailException(String msg) {
super(msg);
}
/**
* Constructor for MailException.
* @param msg the detail message
* @param cause the root cause from the mail API in use
*/
public MailException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail;
import java.util.Date;
/**
* This is a common interface for mail messages, allowing a user to set key
* values required in assembling a mail message, without needing to know if
* the underlying message is a simple text message or a more sophisticated
* MIME message.
*
* <p>Implemented by both SimpleMailMessage and MimeMessageHelper,
* to let message population code interact with a simple message or a
* MIME message through a common interface.
*
* @author Juergen Hoeller
* @since 1.1.5
* @see SimpleMailMessage
* @see org.springframework.mail.javamail.MimeMessageHelper
*/
public interface MailMessage {
public void setFrom(String from) throws MailParseException;
public void setReplyTo(String replyTo) throws MailParseException;
public void setTo(String to) throws MailParseException;
public void setTo(String[] to) throws MailParseException;
public void setCc(String cc) throws MailParseException;
public void setCc(String[] cc) throws MailParseException;
public void setBcc(String bcc) throws MailParseException;
public void setBcc(String[] bcc) throws MailParseException;
public void setSentDate(Date sentDate) throws MailParseException;
public void setSubject(String subject) throws MailParseException;
public void setText(String text) throws MailParseException;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail;
/**
* Exception thrown if illegal message properties are encountered.
*
* @author Dmitriy Kopylenko
* @author Juergen Hoeller
*/
public class MailParseException extends MailException {
/**
* Constructor for MailParseException.
* @param msg the detail message
*/
public MailParseException(String msg) {
super(msg);
}
/**
* Constructor for MailParseException.
* @param msg the detail message
* @param cause the root cause from the mail API in use
*/
public MailParseException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Constructor for MailParseException.
* @param cause the root cause from the mail API in use
*/
public MailParseException(Throwable cause) {
super("Could not parse mail", cause);
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail;
/**
* Exception to be thrown by user code if a mail cannot be prepared properly,
* for example when a Velocity template cannot be rendered for the mail text.
*
* @author Juergen Hoeller
* @since 1.1
* @see org.springframework.ui.velocity.VelocityEngineUtils#mergeTemplateIntoString
* @see org.springframework.ui.freemarker.FreeMarkerTemplateUtils#processTemplateIntoString
*/
public class MailPreparationException extends MailException {
/**
* Constructor for MailPreparationException.
* @param msg the detail message
*/
public MailPreparationException(String msg) {
super(msg);
}
/**
* Constructor for MailPreparationException.
* @param msg the detail message
* @param cause the root cause from the mail API in use
*/
public MailPreparationException(String msg, Throwable cause) {
super(msg, cause);
}
public MailPreparationException(Throwable cause) {
super("Could not prepare mail", cause);
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.util.ObjectUtils;
/**
* Exception thrown when a mail sending error is encountered.
* Can register failed messages with their exceptions.
*
* @author Dmitriy Kopylenko
* @author Juergen Hoeller
*/
public class MailSendException extends MailException {
private transient Map failedMessages;
private Exception[] messageExceptions;
/**
* Constructor for MailSendException.
* @param msg the detail message
*/
public MailSendException(String msg) {
super(msg);
}
/**
* Constructor for MailSendException.
* @param msg the detail message
* @param cause the root cause from the mail API in use
*/
public MailSendException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Constructor for registration of failed messages, with the
* messages that failed as keys, and the thrown exceptions as values.
* <p>The messages should be the same that were originally passed
* to the invoked send method.
* @param failedMessages Map of failed messages as keys and thrown
* exceptions as values
*/
public MailSendException(Map failedMessages) {
super(null);
this.failedMessages = new LinkedHashMap(failedMessages);
this.messageExceptions = (Exception[]) failedMessages.values().toArray(new Exception[failedMessages.size()]);
}
/**
* Return a Map with the failed messages as keys, and the thrown exceptions
* as values.
* <p>Note that a general mail server connection failure will not result
* in failed messages being returned here: A message will only be
* contained here if actually sending it was attempted but failed.
* <p>The messages will be the same that were originally passed to the
* invoked send method, that is, SimpleMailMessages in case of using
* the generic MailSender interface.
* <p>In case of sending MimeMessage instances via JavaMailSender,
* the messages will be of type MimeMessage.
* <p><b>NOTE:</b> This Map will not be available after serialization.
* Use {@link #getMessageExceptions()} in such a scenario, which will
* be available after serialization as well.
* @return the Map of failed messages as keys and thrown exceptions as
* values, or an empty Map if no failed messages
* @see SimpleMailMessage
* @see javax.mail.internet.MimeMessage
*/
public final Map getFailedMessages() {
return (this.failedMessages != null ? this.failedMessages : Collections.EMPTY_MAP);
}
/**
* Return an array with thrown message exceptions.
* <p>Note that a general mail server connection failure will not result
* in failed messages being returned here: A message will only be
* contained here if actually sending it was attempted but failed.
* @return the array of thrown message exceptions,
* or an empty array if no failed messages
*/
public final Exception[] getMessageExceptions() {
return (this.messageExceptions != null ? this.messageExceptions : new Exception[0]);
}
public String getMessage() {
if (ObjectUtils.isEmpty(this.messageExceptions)) {
return super.getMessage();
}
else {
StringBuffer sb = new StringBuffer("Failed messages: ");
for (int i = 0; i < this.messageExceptions.length; i++) {
Exception subEx = this.messageExceptions[i];
sb.append(subEx.toString());
if (i < this.messageExceptions.length - 1) {
sb.append("; ");
}
}
return sb.toString();
}
}
public String toString() {
if (ObjectUtils.isEmpty(this.messageExceptions)) {
return super.toString();
}
else {
StringBuffer sb = new StringBuffer(getClass().getName());
sb.append("; nested exceptions (").append(this.messageExceptions.length).append(") are:");
for (int i = 0; i < this.messageExceptions.length; i++) {
Exception subEx = this.messageExceptions[i];
sb.append('\n').append("Failed message ").append(i + 1).append(": ");
sb.append(subEx);
}
return sb.toString();
}
}
public void printStackTrace(PrintStream ps) {
if (ObjectUtils.isEmpty(this.messageExceptions)) {
super.printStackTrace(ps);
}
else {
ps.println(getClass().getName() + "; nested exception details (" +
this.messageExceptions.length + ") are:");
for (int i = 0; i < this.messageExceptions.length; i++) {
Exception subEx = this.messageExceptions[i];
ps.println("Failed message " + (i + 1) + ":");
subEx.printStackTrace(ps);
}
}
}
public void printStackTrace(PrintWriter pw) {
if (ObjectUtils.isEmpty(this.messageExceptions)) {
super.printStackTrace(pw);
}
else {
pw.println(getClass().getName() + "; nested exception details (" +
this.messageExceptions.length + ") are:");
for (int i = 0; i < this.messageExceptions.length; i++) {
Exception subEx = this.messageExceptions[i];
pw.println("Failed message " + (i + 1) + ":");
subEx.printStackTrace(pw);
}
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail;
/**
* This interface defines a strategy for sending simple mails. Can be
* implemented for a variety of mailing systems due to the simple requirements.
* For richer functionality like MIME messages, consider JavaMailSender.
*
* <p>Allows for easy testing of clients, as it does not depend on JavaMail's
* infrastructure classes: no mocking of JavaMail Session or Transport necessary.
*
* @author Dmitriy Kopylenko
* @author Juergen Hoeller
* @since 10.09.2003
* @see org.springframework.mail.javamail.JavaMailSender
*/
public interface MailSender {
/**
* Send the given simple mail message.
* @param simpleMessage the message to send
* @throws org.springframework.mail.MailParseException
* in case of failure when parsing the message
* @throws org.springframework.mail.MailAuthenticationException
* in case of authentication failure
* @throws org.springframework.mail.MailSendException
* in case of failure when sending the message
*/
void send(SimpleMailMessage simpleMessage) throws MailException;
/**
* Send the given array of simple mail messages in batch.
* @param simpleMessages the messages to send
* @throws org.springframework.mail.MailParseException
* in case of failure when parsing a message
* @throws org.springframework.mail.MailAuthenticationException
* in case of authentication failure
* @throws org.springframework.mail.MailSendException
* in case of failure when sending a message
*/
void send(SimpleMailMessage[] simpleMessages) throws MailException;
}

View File

@ -0,0 +1,258 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail;
import java.io.Serializable;
import java.util.Date;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.Assert;
/**
* Models a simple mail message, including data such as the from, to, cc, subject, and text fields.
*
* <p>Consider <code>JavaMailSender</code> and JavaMail <code>MimeMessages</code> for creating
* more sophisticated messages, for example messages with attachments, special
* character encodings, or personal names that accompany mail addresses.
*
* @author Dmitriy Kopylenko
* @author Juergen Hoeller
* @since 10.09.2003
* @see MailSender
* @see org.springframework.mail.javamail.JavaMailSender
* @see org.springframework.mail.javamail.MimeMessagePreparator
* @see org.springframework.mail.javamail.MimeMessageHelper
* @see org.springframework.mail.javamail.MimeMailMessage
*/
public class SimpleMailMessage implements MailMessage, Serializable {
private String from;
private String replyTo;
private String[] to;
private String[] cc;
private String[] bcc;
private Date sentDate;
private String subject;
private String text;
/**
* Create a new <code>SimpleMailMessage</code>.
*/
public SimpleMailMessage() {
}
/**
* Copy constructor for creating a new <code>SimpleMailMessage</code> from the state
* of an existing <code>SimpleMailMessage</code> instance.
* @throws IllegalArgumentException if the supplied message is <code>null</code>
*/
public SimpleMailMessage(SimpleMailMessage original) {
Assert.notNull(original, "The 'original' message argument cannot be null");
this.from = original.getFrom();
this.replyTo = original.getReplyTo();
if (original.getTo() != null) {
this.to = copy(original.getTo());
}
if (original.getCc() != null) {
this.cc = copy(original.getCc());
}
if (original.getBcc() != null) {
this.bcc = copy(original.getBcc());
}
this.sentDate = original.getSentDate();
this.subject = original.getSubject();
this.text = original.getText();
}
public void setFrom(String from) {
this.from = from;
}
public String getFrom() {
return this.from;
}
public void setReplyTo(String replyTo) {
this.replyTo = replyTo;
}
public String getReplyTo() {
return replyTo;
}
public void setTo(String to) {
this.to = new String[] {to};
}
public void setTo(String[] to) {
this.to = to;
}
public String[] getTo() {
return this.to;
}
public void setCc(String cc) {
this.cc = new String[] {cc};
}
public void setCc(String[] cc) {
this.cc = cc;
}
public String[] getCc() {
return cc;
}
public void setBcc(String bcc) {
this.bcc = new String[] {bcc};
}
public void setBcc(String[] bcc) {
this.bcc = bcc;
}
public String[] getBcc() {
return bcc;
}
public void setSentDate(Date sentDate) {
this.sentDate = sentDate;
}
public Date getSentDate() {
return sentDate;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getSubject() {
return this.subject;
}
public void setText(String text) {
this.text = text;
}
public String getText() {
return this.text;
}
/**
* Copy the contents of this message to the given target message.
* @param target the <code>MailMessage</code> to copy to
* @throws IllegalArgumentException if the supplied <code>target</code> is <code>null</code>
*/
public void copyTo(MailMessage target) {
Assert.notNull(target, "The 'target' message argument cannot be null");
if (getFrom() != null) {
target.setFrom(getFrom());
}
if (getReplyTo() != null) {
target.setReplyTo(getReplyTo());
}
if (getTo() != null) {
target.setTo(getTo());
}
if (getCc() != null) {
target.setCc(getCc());
}
if (getBcc() != null) {
target.setBcc(getBcc());
}
if (getSentDate() != null) {
target.setSentDate(getSentDate());
}
if (getSubject() != null) {
target.setSubject(getSubject());
}
if (getText() != null) {
target.setText(getText());
}
}
public String toString() {
StringBuffer sb = new StringBuffer("SimpleMailMessage: ");
sb.append("from=").append(this.from).append("; ");
sb.append("replyTo=").append(this.replyTo).append("; ");
sb.append("to=").append(StringUtils.arrayToCommaDelimitedString(this.to)).append("; ");
sb.append("cc=").append(StringUtils.arrayToCommaDelimitedString(this.cc)).append("; ");
sb.append("bcc=").append(StringUtils.arrayToCommaDelimitedString(this.bcc)).append("; ");
sb.append("sentDate=").append(this.sentDate).append("; ");
sb.append("subject=").append(this.subject).append("; ");
sb.append("text=").append(this.text);
return sb.toString();
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof SimpleMailMessage)) {
return false;
}
SimpleMailMessage otherMessage = (SimpleMailMessage) other;
return (ObjectUtils.nullSafeEquals(this.from, otherMessage.from) &&
ObjectUtils.nullSafeEquals(this.replyTo, otherMessage.replyTo) &&
java.util.Arrays.equals(this.to, otherMessage.to) &&
java.util.Arrays.equals(this.cc, otherMessage.cc) &&
java.util.Arrays.equals(this.bcc, otherMessage.bcc) &&
ObjectUtils.nullSafeEquals(this.sentDate, otherMessage.sentDate) &&
ObjectUtils.nullSafeEquals(this.subject, otherMessage.subject) &&
ObjectUtils.nullSafeEquals(this.text, otherMessage.text));
}
public int hashCode() {
int hashCode = (this.from == null ? 0 : this.from.hashCode());
hashCode = 29 * hashCode + (this.replyTo == null ? 0 : this.replyTo.hashCode());
for (int i = 0; this.to != null && i < this.to.length; i++) {
hashCode = 29 * hashCode + (this.to == null ? 0 : this.to[i].hashCode());
}
for (int i = 0; this.cc != null && i < this.cc.length; i++) {
hashCode = 29 * hashCode + (this.cc == null ? 0 : this.cc[i].hashCode());
}
for (int i = 0; this.bcc != null && i < this.bcc.length; i++) {
hashCode = 29 * hashCode + (this.bcc == null ? 0 : this.bcc[i].hashCode());
}
hashCode = 29 * hashCode + (this.sentDate == null ? 0 : this.sentDate.hashCode());
hashCode = 29 * hashCode + (this.subject == null ? 0 : this.subject.hashCode());
hashCode = 29 * hashCode + (this.text == null ? 0 : this.text.hashCode());
return hashCode;
}
private static String[] copy(String[] state) {
String[] copy = new String[state.length];
System.arraycopy(state, 0, copy, 0, state.length);
return copy;
}
}

View File

@ -0,0 +1,183 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail.javamail;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
/**
* Spring-configurable <code>FileTypeMap</code> implementation that will read
* MIME type to file extension mappings from a standard JavaMail MIME type
* mapping file, using a standard <code>MimetypesFileTypeMap</code> underneath.
*
* <p>The mapping file should be in the following format, as specified by the
* Java Activation Framework:
*
* <pre>
* # map text/html to .htm and .html files
* text/html html htm HTML HTM</pre>
*
* Lines starting with <code>#</code> are treated as comments and are ignored. All
* other lines are treated as mappings. Each mapping line should contain the MIME
* type as the first entry and then each file extension to map to that MIME type
* as subsequent entries. Each entry is separated by spaces or tabs.
*
* <p>By default, the mappings in the <code>mime.types</code> file located in the
* same package as this class are used, which cover many common file extensions
* (in contrast to the out-of-the-box mappings in <code>activation.jar</code>).
* This can be overridden using the <code>mappingLocation</code> property.
*
* <p>Additional mappings can be added via the <code>mappings</code> bean property,
* as lines that follow the <code>mime.types<code> file format.
*
* @author Rob Harrop
* @author Juergen Hoeller
* @since 1.2
* @see #setMappingLocation
* @see #setMappings
* @see javax.activation.MimetypesFileTypeMap
*/
public class ConfigurableMimeFileTypeMap extends FileTypeMap implements InitializingBean {
/**
* The <code>Resource</code> to load the mapping file from.
*/
private Resource mappingLocation = new ClassPathResource("mime.types", getClass());
/**
* Used to configure additional mappings.
*/
private String[] mappings;
/**
* The delegate FileTypeMap, compiled from the mappings in the mapping file
* and the entries in the <code>mappings</code> property.
*/
private FileTypeMap fileTypeMap;
/**
* Specify the <code>Resource</code> from which mappings are loaded.
* <p>Needs to follow the <code>mime.types<code> file format, as specified
* by the Java Activation Framework, containing lines such as:<br>
* <code>text/html html htm HTML HTM</code>
*/
public void setMappingLocation(Resource mappingLocation) {
this.mappingLocation = mappingLocation;
}
/**
* Specify additional MIME type mappings as lines that follow the
* <code>mime.types<code> file format, as specified by the
* Java Activation Framework, for example:<br>
* <code>text/html html htm HTML HTM</code>
*/
public void setMappings(String[] mappings) {
this.mappings = mappings;
}
/**
* Creates the final merged mapping set.
*/
public void afterPropertiesSet() {
getFileTypeMap();
}
/**
* Return the delegate FileTypeMap, compiled from the mappings in the mapping file
* and the entries in the <code>mappings</code> property.
* @see #setMappingLocation
* @see #setMappings
* @see #createFileTypeMap
*/
protected final FileTypeMap getFileTypeMap() {
if (this.fileTypeMap == null) {
try {
this.fileTypeMap = createFileTypeMap(this.mappingLocation, this.mappings);
}
catch (IOException ex) {
IllegalStateException ise = new IllegalStateException(
"Could not load specified MIME type mapping file: " + this.mappingLocation);
ise.initCause(ex);
throw ise;
}
}
return this.fileTypeMap;
}
/**
* Compile a {@link FileTypeMap} from the mappings in the given mapping file
* and the given mapping entries.
* <p>The default implementation creates an Activation Framework {@link MimetypesFileTypeMap},
* passing in an InputStream from the mapping resource (if any) and registering
* the mapping lines programmatically.
* @param mappingLocation a <code>mime.types</code> mapping resource (can be <code>null</code>)
* @param mappings MIME type mapping lines (can be <code>null</code>)
* @return the compiled FileTypeMap
* @throws IOException if resource access failed
* @see javax.activation.MimetypesFileTypeMap#MimetypesFileTypeMap(java.io.InputStream)
* @see javax.activation.MimetypesFileTypeMap#addMimeTypes(String)
*/
protected FileTypeMap createFileTypeMap(Resource mappingLocation, String[] mappings) throws IOException {
MimetypesFileTypeMap fileTypeMap = null;
if (mappingLocation != null) {
InputStream is = mappingLocation.getInputStream();
try {
fileTypeMap = new MimetypesFileTypeMap(is);
}
finally {
is.close();
}
}
else {
fileTypeMap = new MimetypesFileTypeMap();
}
if (mappings != null) {
for (int i = 0; i < mappings.length; i++) {
fileTypeMap.addMimeTypes(mappings[i]);
}
}
return fileTypeMap;
}
/**
* Delegates to the underlying FileTypeMap.
* @see #getFileTypeMap()
*/
public String getContentType(File file) {
return getFileTypeMap().getContentType(file);
}
/**
* Delegates to the underlying FileTypeMap.
* @see #getFileTypeMap()
*/
public String getContentType(String fileName) {
return getFileTypeMap().getContentType(fileName);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail.javamail;
import java.beans.PropertyEditorSupport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.springframework.util.StringUtils;
/**
* Editor for <code>java.mail.internet.InternetAddress</code>,
* to directly populate an InternetAddress property.
*
* <p>Expects the same syntax as InternetAddress's constructor with
* a String argument. Converts empty Strings into null values.
*
* @author Juergen Hoeller
* @since 1.2.3
* @see javax.mail.internet.InternetAddress
*/
public class InternetAddressEditor extends PropertyEditorSupport {
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text)) {
try {
setValue(new InternetAddress(text));
}
catch (AddressException ex) {
throw new IllegalArgumentException("Could not parse mail address: " + ex.getMessage());
}
}
else {
setValue(null);
}
}
public String getAsText() {
InternetAddress value = (InternetAddress) getValue();
return (value != null ? value.toUnicodeString() : "");
}
}

View File

@ -0,0 +1,143 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail.javamail;
import java.io.InputStream;
import javax.mail.internet.MimeMessage;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
/**
* Extended {@link org.springframework.mail.MailSender} interface for JavaMail,
* supporting MIME messages both as direct arguments and through preparation
* callbacks. Typically used in conjunction with the {@link MimeMessageHelper}
* class for convenient creation of JavaMail {@link MimeMessage MimeMessages},
* including attachments etc.
*
* <p>Clients should talk to the mail sender through this interface if they need
* mail functionality beyond {@link org.springframework.mail.SimpleMailMessage}.
* The production implementation is {@link JavaMailSenderImpl}; for testing,
* mocks can be created based on this interface. Clients will typically receive
* the JavaMailSender reference through dependency injection.
*
* <p>The recommended way of using this interface is the {@link MimeMessagePreparator}
* mechanism, possibly using a {@link MimeMessageHelper} for populating the message.
* See {@link MimeMessageHelper MimeMessageHelper's javadoc} for an example.
*
* <p>The entire JavaMail {@link javax.mail.Session} management is abstracted
* by the JavaMailSender. Client code should not deal with a Session in any way,
* rather leave the entire JavaMail configuration and resource handling to the
* JavaMailSender implementation. This also increases testability.
*
* <p>A JavaMailSender client is not as easy to test as a plain
* {@link org.springframework.mail.MailSender} client, but still straightforward
* compared to traditional JavaMail code: Just let {@link #createMimeMessage()}
* return a plain {@link MimeMessage} created with a
* <code>Session.getInstance(new Properties())</code> call, and check the passed-in
* messages in your mock implementations of the various <code>send</code> methods.
*
* @author Juergen Hoeller
* @since 07.10.2003
* @see javax.mail.internet.MimeMessage
* @see javax.mail.Session
* @see JavaMailSenderImpl
* @see MimeMessagePreparator
* @see MimeMessageHelper
*/
public interface JavaMailSender extends MailSender {
/**
* Create a new JavaMail MimeMessage for the underlying JavaMail Session
* of this sender. Needs to be called to create MimeMessage instances
* that can be prepared by the client and passed to send(MimeMessage).
* @return the new MimeMessage instance
* @see #send(MimeMessage)
* @see #send(MimeMessage[])
*/
MimeMessage createMimeMessage();
/**
* Create a new JavaMail MimeMessage for the underlying JavaMail Session
* of this sender, using the given input stream as the message source.
* @param contentStream the raw MIME input stream for the message
* @return the new MimeMessage instance
* @throws org.springframework.mail.MailParseException
* in case of message creation failure
*/
MimeMessage createMimeMessage(InputStream contentStream) throws MailException;
/**
* Send the given JavaMail MIME message.
* The message needs to have been created with {@link #createMimeMessage()}.
* @param mimeMessage message to send
* @throws org.springframework.mail.MailAuthenticationException
* in case of authentication failure
* @throws org.springframework.mail.MailSendException
* in case of failure when sending the message
* @see #createMimeMessage
*/
void send(MimeMessage mimeMessage) throws MailException;
/**
* Send the given array of JavaMail MIME messages in batch.
* The messages need to have been created with {@link #createMimeMessage()}.
* @param mimeMessages messages to send
* @throws org.springframework.mail.MailAuthenticationException
* in case of authentication failure
* @throws org.springframework.mail.MailSendException
* in case of failure when sending a message
* @see #createMimeMessage
*/
void send(MimeMessage[] mimeMessages) throws MailException;
/**
* Send the JavaMail MIME message prepared by the given MimeMessagePreparator.
* <p>Alternative way to prepare MimeMessage instances, instead of
* {@link #createMimeMessage()} and {@link #send(MimeMessage)} calls.
* Takes care of proper exception conversion.
* @param mimeMessagePreparator the preparator to use
* @throws org.springframework.mail.MailPreparationException
* in case of failure when preparing the message
* @throws org.springframework.mail.MailParseException
* in case of failure when parsing the message
* @throws org.springframework.mail.MailAuthenticationException
* in case of authentication failure
* @throws org.springframework.mail.MailSendException
* in case of failure when sending the message
*/
void send(MimeMessagePreparator mimeMessagePreparator) throws MailException;
/**
* Send the JavaMail MIME messages prepared by the given MimeMessagePreparators.
* <p>Alternative way to prepare MimeMessage instances, instead of
* {@link #createMimeMessage()} and {@link #send(MimeMessage[])} calls.
* Takes care of proper exception conversion.
* @param mimeMessagePreparators the preparator to use
* @throws org.springframework.mail.MailPreparationException
* in case of failure when preparing a message
* @throws org.springframework.mail.MailParseException
* in case of failure when parsing a message
* @throws org.springframework.mail.MailAuthenticationException
* in case of authentication failure
* @throws org.springframework.mail.MailSendException
* in case of failure when sending a message
*/
void send(MimeMessagePreparator[] mimeMessagePreparators) throws MailException;
}

View File

@ -0,0 +1,437 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail.javamail;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.activation.FileTypeMap;
import javax.mail.AuthenticationFailedException;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;
import org.springframework.mail.MailAuthenticationException;
import org.springframework.mail.MailException;
import org.springframework.mail.MailParseException;
import org.springframework.mail.MailPreparationException;
import org.springframework.mail.MailSendException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.util.Assert;
/**
* Production implementation of the {@link JavaMailSender} interface,
* supporting both JavaMail {@link MimeMessage MimeMessages} and Spring
* {@link SimpleMailMessage SimpleMailMessages}. Can also be used as a
* plain {@link org.springframework.mail.MailSender} implementation.
*
* <p>Allows for defining all settings locally as bean properties.
* Alternatively, a pre-configured JavaMail {@link javax.mail.Session} can be
* specified, possibly pulled from an application server's JNDI environment.
*
* <p>Non-default properties in this object will always override the settings
* in the JavaMail <code>Session</code>. Note that if overriding all values locally,
* there is no added value in setting a pre-configured <code>Session</code>.
*
* @author Dmitriy Kopylenko
* @author Juergen Hoeller
* @since 10.09.2003
* @see javax.mail.internet.MimeMessage
* @see javax.mail.Session
* @see #setSession
* @see #setJavaMailProperties
* @see #setHost
* @see #setPort
* @see #setUsername
* @see #setPassword
*/
public class JavaMailSenderImpl implements JavaMailSender {
/** The default protocol: 'smtp' */
public static final String DEFAULT_PROTOCOL = "smtp";
/** The default port: -1 */
public static final int DEFAULT_PORT = -1;
private static final String HEADER_MESSAGE_ID = "Message-ID";
private Properties javaMailProperties = new Properties();
private Session session;
private String protocol = DEFAULT_PROTOCOL;
private String host;
private int port = DEFAULT_PORT;
private String username;
private String password;
private String defaultEncoding;
private FileTypeMap defaultFileTypeMap;
/**
* Create a new instance of the <code>JavaMailSenderImpl</code> class.
* <p>Initializes the {@link #setDefaultFileTypeMap "defaultFileTypeMap"}
* property with a default {@link ConfigurableMimeFileTypeMap}.
*/
public JavaMailSenderImpl() {
ConfigurableMimeFileTypeMap fileTypeMap = new ConfigurableMimeFileTypeMap();
fileTypeMap.afterPropertiesSet();
this.defaultFileTypeMap = fileTypeMap;
}
/**
* Set JavaMail properties for the <code>Session</code>.
* <p>A new <code>Session</code> will be created with those properties.
* Use either this method or {@link #setSession}, but not both.
* <p>Non-default properties in this instance will override given
* JavaMail properties.
*/
public void setJavaMailProperties(Properties javaMailProperties) {
this.javaMailProperties = javaMailProperties;
synchronized (this) {
this.session = null;
}
}
/**
* Allow Map access to the JavaMail properties of this sender,
* with the option to add or override specific entries.
* <p>Useful for specifying entries directly, for example via
* "javaMailProperties[mail.smtp.auth]".
*/
public Properties getJavaMailProperties() {
return this.javaMailProperties;
}
/**
* Set the JavaMail <code>Session</code>, possibly pulled from JNDI.
* <p>Default is a new <code>Session</code> without defaults, that is
* completely configured via this instance's properties.
* <p>If using a pre-configured <code>Session</code>, non-default properties
* in this instance will override the settings in the <code>Session</code>.
* @see #setJavaMailProperties
*/
public synchronized void setSession(Session session) {
Assert.notNull(session, "Session must not be null");
this.session = session;
}
/**
* Return the JavaMail <code>Session</code>,
* lazily initializing it if hasn't been specified explicitly.
*/
public synchronized Session getSession() {
if (this.session == null) {
this.session = Session.getInstance(this.javaMailProperties);
}
return this.session;
}
/**
* Set the mail protocol. Default is "smtp".
*/
public void setProtocol(String protocol) {
this.protocol = protocol;
}
/**
* Return the mail protocol.
*/
public String getProtocol() {
return this.protocol;
}
/**
* Set the mail server host, typically an SMTP host.
* <p>Default is the default host of the underlying JavaMail Session.
*/
public void setHost(String host) {
this.host = host;
}
/**
* Return the mail server host.
*/
public String getHost() {
return this.host;
}
/**
* Set the mail server port.
* <p>Default is {@link #DEFAULT_PORT}, letting JavaMail use the default
* SMTP port (25).
*/
public void setPort(int port) {
this.port = port;
}
/**
* Return the mail server port.
*/
public int getPort() {
return this.port;
}
/**
* Set the username for the account at the mail host, if any.
* <p>Note that the underlying JavaMail <code>Session</code> has to be
* configured with the property <code>"mail.smtp.auth"</code> set to
* <code>true</code>, else the specified username will not be sent to the
* mail server by the JavaMail runtime. If you are not explicitly passing
* in a <code>Session</code> to use, simply specify this setting via
* {@link #setJavaMailProperties}.
* @see #setSession
* @see #setPassword
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Return the username for the account at the mail host.
*/
public String getUsername() {
return this.username;
}
/**
* Set the password for the account at the mail host, if any.
* <p>Note that the underlying JavaMail <code>Session</code> has to be
* configured with the property <code>"mail.smtp.auth"</code> set to
* <code>true</code>, else the specified password will not be sent to the
* mail server by the JavaMail runtime. If you are not explicitly passing
* in a <code>Session</code> to use, simply specify this setting via
* {@link #setJavaMailProperties}.
* @see #setSession
* @see #setUsername
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Return the password for the account at the mail host.
*/
public String getPassword() {
return this.password;
}
/**
* Set the default encoding to use for {@link MimeMessage MimeMessages}
* created by this instance.
* <p>Such an encoding will be auto-detected by {@link MimeMessageHelper}.
*/
public void setDefaultEncoding(String defaultEncoding) {
this.defaultEncoding = defaultEncoding;
}
/**
* Return the default encoding for {@link MimeMessage MimeMessages},
* or <code>null</code> if none.
*/
public String getDefaultEncoding() {
return this.defaultEncoding;
}
/**
* Set the default Java Activation {@link FileTypeMap} to use for
* {@link MimeMessage MimeMessages} created by this instance.
* <p>A <code>FileTypeMap</code> specified here will be autodetected by
* {@link MimeMessageHelper}, avoiding the need to specify the
* <code>FileTypeMap</code> for each <code>MimeMessageHelper</code> instance.
* <p>For example, you can specify a custom instance of Spring's
* {@link ConfigurableMimeFileTypeMap} here. If not explicitly specified,
* a default <code>ConfigurableMimeFileTypeMap</code> will be used, containing
* an extended set of MIME type mappings (as defined by the
* <code>mime.types</code> file contained in the Spring jar).
* @see MimeMessageHelper#setFileTypeMap
*/
public void setDefaultFileTypeMap(FileTypeMap defaultFileTypeMap) {
this.defaultFileTypeMap = defaultFileTypeMap;
}
/**
* Return the default Java Activation {@link FileTypeMap} for
* {@link MimeMessage MimeMessages}, or <code>null</code> if none.
*/
public FileTypeMap getDefaultFileTypeMap() {
return this.defaultFileTypeMap;
}
//---------------------------------------------------------------------
// Implementation of MailSender
//---------------------------------------------------------------------
public void send(SimpleMailMessage simpleMessage) throws MailException {
send(new SimpleMailMessage[] { simpleMessage });
}
public void send(SimpleMailMessage[] simpleMessages) throws MailException {
List mimeMessages = new ArrayList(simpleMessages.length);
for (int i = 0; i < simpleMessages.length; i++) {
SimpleMailMessage simpleMessage = simpleMessages[i];
MimeMailMessage message = new MimeMailMessage(createMimeMessage());
simpleMessage.copyTo(message);
mimeMessages.add(message.getMimeMessage());
}
doSend((MimeMessage[]) mimeMessages.toArray(new MimeMessage[mimeMessages.size()]), simpleMessages);
}
//---------------------------------------------------------------------
// Implementation of JavaMailSender
//---------------------------------------------------------------------
/**
* This implementation creates a SmartMimeMessage, holding the specified
* default encoding and default FileTypeMap. This special defaults-carrying
* message will be autodetected by {@link MimeMessageHelper}, which will use
* the carried encoding and FileTypeMap unless explicitly overridden.
* @see #setDefaultEncoding
* @see #setDefaultFileTypeMap
*/
public MimeMessage createMimeMessage() {
return new SmartMimeMessage(getSession(), getDefaultEncoding(), getDefaultFileTypeMap());
}
public MimeMessage createMimeMessage(InputStream contentStream) throws MailException {
try {
return new MimeMessage(getSession(), contentStream);
}
catch (MessagingException ex) {
throw new MailParseException("Could not parse raw MIME content", ex);
}
}
public void send(MimeMessage mimeMessage) throws MailException {
send(new MimeMessage[] { mimeMessage });
}
public void send(MimeMessage[] mimeMessages) throws MailException {
doSend(mimeMessages, null);
}
public void send(MimeMessagePreparator mimeMessagePreparator) throws MailException {
send(new MimeMessagePreparator[] { mimeMessagePreparator });
}
public void send(MimeMessagePreparator[] mimeMessagePreparators) throws MailException {
try {
List mimeMessages = new ArrayList(mimeMessagePreparators.length);
for (int i = 0; i < mimeMessagePreparators.length; i++) {
MimeMessage mimeMessage = createMimeMessage();
mimeMessagePreparators[i].prepare(mimeMessage);
mimeMessages.add(mimeMessage);
}
send((MimeMessage[]) mimeMessages.toArray(new MimeMessage[mimeMessages.size()]));
}
catch (MailException ex) {
throw ex;
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
catch (IOException ex) {
throw new MailPreparationException(ex);
}
catch (Exception ex) {
throw new MailPreparationException(ex);
}
}
/**
* Actually send the given array of MimeMessages via JavaMail.
* @param mimeMessages MimeMessage objects to send
* @param originalMessages corresponding original message objects
* that the MimeMessages have been created from (with same array
* length and indices as the "mimeMessages" array), if any
* @throws org.springframework.mail.MailAuthenticationException
* in case of authentication failure
* @throws org.springframework.mail.MailSendException
* in case of failure when sending a message
*/
protected void doSend(MimeMessage[] mimeMessages, Object[] originalMessages) throws MailException {
Map failedMessages = new LinkedHashMap();
try {
Transport transport = getTransport(getSession());
transport.connect(getHost(), getPort(), getUsername(), getPassword());
try {
for (int i = 0; i < mimeMessages.length; i++) {
MimeMessage mimeMessage = mimeMessages[i];
try {
if (mimeMessage.getSentDate() == null) {
mimeMessage.setSentDate(new Date());
}
String messageId = mimeMessage.getMessageID();
mimeMessage.saveChanges();
if (messageId != null) {
// Preserve explicitly specified message id...
mimeMessage.setHeader(HEADER_MESSAGE_ID, messageId);
}
transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
}
catch (MessagingException ex) {
Object original = (originalMessages != null ? originalMessages[i] : mimeMessage);
failedMessages.put(original, ex);
}
}
}
finally {
transport.close();
}
}
catch (AuthenticationFailedException ex) {
throw new MailAuthenticationException(ex);
}
catch (MessagingException ex) {
throw new MailSendException("Mail server connection failed", ex);
}
if (!failedMessages.isEmpty()) {
throw new MailSendException(failedMessages);
}
}
/**
* Obtain a Transport object from the given JavaMail Session,
* using the configured protocol.
* <p>Can be overridden in subclasses, e.g. to return a mock Transport object.
* @see javax.mail.Session#getTransport(String)
* @see #getProtocol()
*/
protected Transport getTransport(Session session) throws NoSuchProviderException {
return session.getTransport(getProtocol());
}
}

View File

@ -0,0 +1,175 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail.javamail;
import java.util.Date;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import org.springframework.mail.MailMessage;
import org.springframework.mail.MailParseException;
/**
* Implementation of the MailMessage interface for a JavaMail MIME message,
* to let message population code interact with a simple message or a MIME
* message through a common interface.
*
* <p>Uses a MimeMessageHelper underneath. Can either be created with a
* MimeMessageHelper instance or with a JavaMail MimeMessage instance.
*
* @author Juergen Hoeller
* @since 1.1.5
* @see MimeMessageHelper
* @see javax.mail.internet.MimeMessage
*/
public class MimeMailMessage implements MailMessage {
private final MimeMessageHelper helper;
/**
* Create a new MimeMailMessage based on the given MimeMessageHelper.
* @param mimeMessageHelper the MimeMessageHelper
*/
public MimeMailMessage(MimeMessageHelper mimeMessageHelper) {
this.helper = mimeMessageHelper;
}
/**
* Create a new MimeMailMessage based on the given JavaMail MimeMessage.
* @param mimeMessage the JavaMail MimeMessage
*/
public MimeMailMessage(MimeMessage mimeMessage) {
this.helper = new MimeMessageHelper(mimeMessage);
}
/**
* Return the MimeMessageHelper that this MimeMailMessage is based on.
*/
public final MimeMessageHelper getMimeMessageHelper() {
return this.helper;
}
/**
* Return the JavaMail MimeMessage that this MimeMailMessage is based on.
*/
public final MimeMessage getMimeMessage() {
return this.helper.getMimeMessage();
}
public void setFrom(String from) throws MailParseException {
try {
this.helper.setFrom(from);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
public void setReplyTo(String replyTo) throws MailParseException {
try {
this.helper.setReplyTo(replyTo);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
public void setTo(String to) throws MailParseException {
try {
this.helper.setTo(to);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
public void setTo(String[] to) throws MailParseException {
try {
this.helper.setTo(to);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
public void setCc(String cc) throws MailParseException {
try {
this.helper.setCc(cc);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
public void setCc(String[] cc) throws MailParseException {
try {
this.helper.setCc(cc);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
public void setBcc(String bcc) throws MailParseException {
try {
this.helper.setBcc(bcc);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
public void setBcc(String[] bcc) throws MailParseException {
try {
this.helper.setBcc(bcc);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
public void setSentDate(Date sentDate) throws MailParseException {
try {
this.helper.setSentDate(sentDate);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
public void setSubject(String subject) throws MailParseException {
try {
this.helper.setSubject(subject);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
public void setText(String text) throws MailParseException {
try {
this.helper.setText(text);
}
catch (MessagingException ex) {
throw new MailParseException(ex);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail.javamail;
import javax.mail.internet.MimeMessage;
/**
* Callback interface for the preparation of JavaMail MIME messages.
*
* <p>The corresponding <code>send</code> methods of {@link JavaMailSender}
* will take care of the actual creation of a {@link MimeMessage} instance,
* and of proper exception conversion.
*
* <p>It is often convenient to use a {@link MimeMessageHelper} for populating
* the passed-in MimeMessage, in particular when working with attachments or
* special character encodings.
* See {@link MimeMessageHelper MimeMessageHelper's javadoc} for an example.
*
* @author Juergen Hoeller
* @since 07.10.2003
* @see JavaMailSender#send(MimeMessagePreparator)
* @see JavaMailSender#send(MimeMessagePreparator[])
* @see MimeMessageHelper
*/
public interface MimeMessagePreparator {
/**
* Prepare the given new MimeMessage instance.
* @param mimeMessage the message to prepare
* @throws javax.mail.MessagingException passing any exceptions thrown by MimeMessage
* methods through for automatic conversion to the MailException hierarchy
* @throws java.io.IOException passing any exceptions thrown by MimeMessage methods
* through for automatic conversion to the MailException hierarchy
* @throws Exception if mail preparation failed, for example when a
* Velocity template cannot be rendered for the mail text
*/
void prepare(MimeMessage mimeMessage) throws Exception;
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mail.javamail;
import javax.activation.FileTypeMap;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
/**
* Special subclass of the standard JavaMail {@link MimeMessage}, carrying a
* default encoding to be used when populating the message and a default Java
* Activation {@link FileTypeMap} to be used for resolving attachment types.
*
* <p>Created by {@link JavaMailSenderImpl} in case of a specified default encoding
* and/or default FileTypeMap. Autodetected by {@link MimeMessageHelper}, which
* will use the carried encoding and FileTypeMap unless explicitly overridden.
*
* @author Juergen Hoeller
* @since 1.2
* @see JavaMailSenderImpl#createMimeMessage()
* @see MimeMessageHelper#getDefaultEncoding(javax.mail.internet.MimeMessage)
* @see MimeMessageHelper#getDefaultFileTypeMap(javax.mail.internet.MimeMessage)
*/
class SmartMimeMessage extends MimeMessage {
private final String defaultEncoding;
private final FileTypeMap defaultFileTypeMap;
/**
* Create a new SmartMimeMessage.
* @param session the JavaMail Session to create the message for
* @param defaultEncoding the default encoding, or <code>null</code> if none
* @param defaultFileTypeMap the default FileTypeMap, or <code>null</code> if none
*/
public SmartMimeMessage(Session session, String defaultEncoding, FileTypeMap defaultFileTypeMap) {
super(session);
this.defaultEncoding = defaultEncoding;
this.defaultFileTypeMap = defaultFileTypeMap;
}
/**
* Return the default encoding of this message, or <code>null</code> if none.
*/
public final String getDefaultEncoding() {
return this.defaultEncoding;
}
/**
* Return the default FileTypeMap of this message, or <code>null</code> if none.
*/
public final FileTypeMap getDefaultFileTypeMap() {
return this.defaultFileTypeMap;
}
}

View File

@ -0,0 +1,306 @@
################################################################################
#
# Defaults for the Java Activation Framework
# Additional extensions registered in this file:
# text/plain java c c++ pl cc h
#
################################################################################
text/html html htm HTML HTM
text/plain txt text TXT TEXT java c c++ pl cc h
image/gif gif GIF
image/ief ief
image/jpeg jpeg jpg jpe JPG
image/tiff tiff tif
image/x-xwindowdump xwd
application/postscript ai eps ps
application/rtf rtf
application/x-tex tex
application/x-texinfo texinfo texi
application/x-troff t tr roff
audio/basic au
audio/midi midi mid
audio/x-aifc aifc
audio/x-aiff aif aiff
audio/x-mpeg mpeg mpg
audio/x-wav wav
video/mpeg mpeg mpg mpe
video/quicktime qt mov
video/x-msvideo avi
################################################################################
#
# Additional file types adapted from
# http://www.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html
#
################################################################################
# TEXT TYPES
text/x-speech talk
text/css css
text/csv csv
# IMAGE TYPES
# X-Windows bitmap (b/w)
image/x-xbitmap xbm
# X-Windows pixelmap (8-bit color)
image/x-xpixmap xpm
# Portable Network Graphics
image/x-png png
# Image Exchange Format (RFC 1314)
image/ief ief
# JPEG
image/jpeg jpeg jpg jpe
# RGB
image/rgb rgb
# Group III Fax (RFC 1494)
image/g3fax g3f
# X Windowdump format
image/x-xwindowdump xwd
# Macintosh PICT format
image/x-pict pict
# PPM (UNIX PPM package)
image/x-portable-pixmap ppm
# PGM (UNIX PPM package)
image/x-portable-graymap pgm
# PBM (UNIX PPM package)
image/x-portable-bitmap pbm
# PNM (UNIX PPM package)
image/x-portable-anymap pnm
# Microsoft Windows bitmap
image/x-ms-bmp bmp
# CMU raster
image/x-cmu-raster ras
# Kodak Photo-CD
image/x-photo-cd pcd
# Computer Graphics Metafile
image/cgm cgm
# CALS Type 1 or 2
image/x-cals mil cal
# Fractal Image Format (Iterated Systems)
image/fif fif
# QuickSilver active image (Micrografx)
image/x-mgx-dsf dsf
# CMX vector image (Corel)
image/x-cmx cmx
# Wavelet-compressed (Summus)
image/wavelet wi
# AutoCad Drawing (SoftSource)
image/vnd.dwg dwg
# AutoCad DXF file (SoftSource)
image/vnd.dxf dxf
# Simple Vector Format (SoftSource)
image/vnd.svf svf
# AUDIO/VOICE/MUSIC RELATED TYPES
# """basic""audio - 8-bit u-law PCM"
audio/basic au snd
# Macintosh audio format (AIpple)
audio/x-aiff aif aiff aifc
# Microsoft audio
audio/x-wav wav
# MPEG audio
audio/x-mpeg mpa abs mpega
# MPEG-2 audio
audio/x-mpeg-2 mp2a mpa2
# compressed speech (Echo Speech Corp.)
audio/echospeech es
# Toolvox speech audio (Voxware)
audio/voxware vox
# RapidTransit compressed audio (Fast Man)
application/fastman lcc
# Realaudio (Progressive Networks)
application/x-pn-realaudio ra ram
# MIDI music data
x-music/x-midi mmid
# Koan music data (SSeyo)
application/vnd.koan skp
# Speech synthesis data (MVP Solutions)
text/x-speech talk
# VIDEO TYPES
# MPEG video
video/mpeg mpeg mpg mpe
# MPEG-2 video
video/mpeg-2 mpv2 mp2v
# Macintosh Quicktime
video/quicktime qt mov
# Microsoft video
video/x-msvideo avi
# SGI Movie format
video/x-sgi-movie movie
# VDOlive streaming video (VDOnet)
video/vdo vdo
# Vivo streaming video (Vivo software)
video/vnd.vivo viv
# SPECIAL HTTP/WEB APPLICATION TYPES
# Proxy autoconfiguration (Netscape browsers)
application/x-ns-proxy-autoconfig pac
# Netscape Cooltalk chat data (Netscape)
x-conference/x-cooltalk ice
# TEXT-RELATED
# PostScript
application/postscript ai eps ps
# Microsoft Rich Text Format
application/rtf rtf
# Adobe Acrobat PDF
application/pdf pdf
# Maker Interchange Format (FrameMaker)
application/vnd.mif mif
# Troff document
application/x-troff t tr roff
# Troff document with MAN macros
application/x-troff-man man
# Troff document with ME macros
application/x-troff-me me
# Troff document with MS macros
application/x-troff-ms ms
# LaTeX document
application/x-latex latex
# Tex/LateX document
application/x-tex tex
# GNU TexInfo document
application/x-texinfo texinfo texi
# TeX dvi format
application/x-dvi dvi
# MS word document
application/msword doc DOC
# Office Document Architecture
application/oda oda
# Envoy Document
application/envoy evy
# ARCHIVE/COMPRESSED ARCHIVES
# Gnu tar format
application/x-gtar gtar
# 4.3BSD tar format
application/x-tar tar
# POSIX tar format
application/x-ustar ustar
# Old CPIO format
application/x-bcpio bcpio
# POSIX CPIO format
application/x-cpio cpio
# UNIX sh shell archive
application/x-shar shar
# DOS/PC - Pkzipped archive
application/zip zip
# Macintosh Binhexed archive
application/mac-binhex40 hqx
# Macintosh Stuffit Archive
application/x-stuffit sit sea
# Fractal Image Format
application/fractals fif
# "Binary UUencoded"
application/octet-stream bin uu
# PC executable
application/octet-stream exe
# "WAIS ""sources"""
application/x-wais-source src wsrc
# NCSA HDF data format
application/hdf hdf
# DOWNLOADABLE PROGRAM/SCRIPTS
# Javascript program
text/javascript js ls mocha
# UNIX bourne shell program
application/x-sh sh
# UNIX c-shell program
application/x-csh csh
# Perl program
application/x-perl pl
# Tcl (Tool Control Language) program
application/x-tcl tcl
# ANIMATION/MULTIMEDIA
# FutureSplash vector animation (FutureWave)
application/futuresplash spl
# mBED multimedia data (mBED)
application/mbedlet mbd
# PowerMedia multimedia (RadMedia)
application/x-rad-powermedia rad
# PRESENTATION
# PowerPoint presentation (Microsoft)
application/mspowerpoint ppz
# ASAP WordPower (Software Publishing Corp.)
application/x-asap asp
# Astound Web Player multimedia data (GoldDisk)
application/astound asn
# SPECIAL EMBEDDED OBJECT
# OLE script e.g. Visual Basic (Ncompass)
application/x-olescript axs
# OLE Object (Microsoft/NCompass)
application/x-oleobject ods
# OpenScape OLE/OCX objects (Business@Web)
x-form/x-openscape opp
# Visual Basic objects (Amara)
application/x-webbasic wba
# Specialized data entry forms (Alpha Software)
application/x-alpha-form frm
# client-server objects (Wayfarer Communications)
x-script/x-wfxclient wfx
# GENERAL APPLICATIONS
# Undefined binary data (often executable progs)
application/octet-stream exe com
# Pointcast news data (Pointcast)
application/x-pcn pcn
# Excel spreadsheet (Microsoft)
application/vnd.ms-excel xls
# PowerPoint (Microsoft)
application/vnd.ms-powerpoint ppt
# Microsoft Project (Microsoft)
application/vnd.ms-project mpp
# SourceView document (Dataware Electronics)
application/vnd.svd svd
# Net Install - software install (20/20 Software)
application/x-net-install ins
# Carbon Copy - remote control/access (Microcom)
application/ccv ccv
# Spreadsheets (Visual Components)
workbook/formulaone vts
# 2D/3D DATA/VIRTUAL REALITY TYPES
# VRML data file
x-world/x-vrml wrl vrml
# WIRL - VRML data (VREAM)
x-world/x-vream vrw
# Play3D 3d scene data (Play3D)
application/x-p3d p3d
# Viscape Interactive 3d world data (Superscape)
x-world/x-svr svr
# WebActive 3d data (Plastic Thought)
x-world/x-wvr wvr
# QuickDraw3D scene data (Apple)
x-world/x-3dmf 3dmf
# SCIENTIFIC/MATH/CAD TYPES
# Mathematica notebook
application/mathematica ma
# Computational meshes for numerical simulations
x-model/x-mesh msh
# Vis5D 5-dimensional data
application/vis5d v5d
# IGES models -- CAD/CAM (CGM) data
application/iges igs
# Autocad WHIP vector drawings
drawing/x-dwf dwf

View File

@ -0,0 +1,9 @@
<html>
<body>
JavaMail support for Spring's mail infrastructure.
Provides an extended JavaMailSender interface and a MimeMessageHelper
class for convenient population of a JavaMail MimeMessage.
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<body>
Spring's generic mail infrastructure.
Concrete implementations are provided in the subpackages.
</body>
</html>

View File

@ -0,0 +1,54 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.commonj;
import commonj.timers.Timer;
import commonj.timers.TimerListener;
import org.springframework.util.Assert;
/**
* Simple TimerListener adapter that delegates to a given Runnable.
*
* @author Juergen Hoeller
* @since 2.0
* @see commonj.timers.TimerListener
* @see java.lang.Runnable
*/
public class DelegatingTimerListener implements TimerListener {
private final Runnable runnable;
/**
* Create a new DelegatingTimerListener.
* @param runnable the Runnable implementation to delegate to
*/
public DelegatingTimerListener(Runnable runnable) {
Assert.notNull(runnable, "Runnable is required");
this.runnable = runnable;
}
/**
* Delegates execution to the underlying Runnable.
*/
public void timerExpired(Timer timer) {
this.runnable.run();
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.commonj;
import commonj.work.Work;
import org.springframework.scheduling.SchedulingAwareRunnable;
import org.springframework.util.Assert;
/**
* Simple Work adapter that delegates to a given Runnable.
*
* @author Juergen Hoeller
* @since 2.0
* @see commonj.work.Work
* @see java.lang.Runnable
*/
public class DelegatingWork implements Work {
private final Runnable delegate;
/**
* Create a new DelegatingWork.
* @param delegate the Runnable implementation to delegate to
* (may be a SchedulingAwareRunnable for extended support)
* @see org.springframework.scheduling.SchedulingAwareRunnable
* @see #isDaemon()
*/
public DelegatingWork(Runnable delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
/**
* Return the wrapped Runnable implementation.
*/
public final Runnable getDelegate() {
return this.delegate;
}
/**
* Delegates execution to the underlying Runnable.
*/
public void run() {
this.delegate.run();
}
/**
* This implementation delegates to
* {@link org.springframework.scheduling.SchedulingAwareRunnable#isLongLived()},
* if available.
*/
public boolean isDaemon() {
return (this.delegate instanceof SchedulingAwareRunnable &&
((SchedulingAwareRunnable) this.delegate).isLongLived());
}
/**
* This implementation is empty, since we expect the Runnable
* to terminate based on some specific shutdown signal.
*/
public void release() {
}
}

View File

@ -0,0 +1,225 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.commonj;
import commonj.timers.TimerListener;
/**
* JavaBean that describes a scheduled TimerListener, consisting of
* the TimerListener itself (or a Runnable to create a TimerListener for)
* and a delay plus period. Period needs to be specified;
* there is no point in a default for it.
*
* <p>The CommonJ TimerManager does not offer more sophisticated scheduling
* options such as cron expressions. Consider using Quartz for such
* advanced needs.
*
* <p>Note that the TimerManager uses a TimerListener instance that is
* shared between repeated executions, in contrast to Quartz which
* instantiates a new Job for each execution.
*
* @author Juergen Hoeller
* @since 2.0
* @see commonj.timers.TimerListener
* @see commonj.timers.TimerManager#schedule(commonj.timers.TimerListener, long, long)
* @see commonj.timers.TimerManager#scheduleAtFixedRate(commonj.timers.TimerListener, long, long)
*/
public class ScheduledTimerListener {
private TimerListener timerListener;
private long delay = 0;
private long period = -1;
private boolean fixedRate = false;
/**
* Create a new ScheduledTimerListener,
* to be populated via bean properties.
* @see #setTimerListener
* @see #setDelay
* @see #setPeriod
* @see #setFixedRate
*/
public ScheduledTimerListener() {
}
/**
* Create a new ScheduledTimerListener, with default
* one-time execution without delay.
* @param timerListener the TimerListener to schedule
*/
public ScheduledTimerListener(TimerListener timerListener) {
this.timerListener = timerListener;
}
/**
* Create a new ScheduledTimerListener, with default
* one-time execution with the given delay.
* @param timerListener the TimerListener to schedule
* @param delay the delay before starting the task for the first time (ms)
*/
public ScheduledTimerListener(TimerListener timerListener, long delay) {
this.timerListener = timerListener;
this.delay = delay;
}
/**
* Create a new ScheduledTimerListener.
* @param timerListener the TimerListener to schedule
* @param delay the delay before starting the task for the first time (ms)
* @param period the period between repeated task executions (ms)
* @param fixedRate whether to schedule as fixed-rate execution
*/
public ScheduledTimerListener(TimerListener timerListener, long delay, long period, boolean fixedRate) {
this.timerListener = timerListener;
this.delay = delay;
this.period = period;
this.fixedRate = fixedRate;
}
/**
* Create a new ScheduledTimerListener, with default
* one-time execution without delay.
* @param timerTask the Runnable to schedule as TimerListener
*/
public ScheduledTimerListener(Runnable timerTask) {
setRunnable(timerTask);
}
/**
* Create a new ScheduledTimerListener, with default
* one-time execution with the given delay.
* @param timerTask the Runnable to schedule as TimerListener
* @param delay the delay before starting the task for the first time (ms)
*/
public ScheduledTimerListener(Runnable timerTask, long delay) {
setRunnable(timerTask);
this.delay = delay;
}
/**
* Create a new ScheduledTimerListener.
* @param timerTask the Runnable to schedule as TimerListener
* @param delay the delay before starting the task for the first time (ms)
* @param period the period between repeated task executions (ms)
* @param fixedRate whether to schedule as fixed-rate execution
*/
public ScheduledTimerListener(Runnable timerTask, long delay, long period, boolean fixedRate) {
setRunnable(timerTask);
this.delay = delay;
this.period = period;
this.fixedRate = fixedRate;
}
/**
* Set the Runnable to schedule as TimerListener.
* @see DelegatingTimerListener
*/
public void setRunnable(Runnable timerTask) {
this.timerListener = new DelegatingTimerListener(timerTask);
}
/**
* Set the TimerListener to schedule.
*/
public void setTimerListener(TimerListener timerListener) {
this.timerListener = timerListener;
}
/**
* Return the TimerListener to schedule.
*/
public TimerListener getTimerListener() {
return this.timerListener;
}
/**
* Set the delay before starting the task for the first time,
* in milliseconds. Default is 0, immediately starting the
* task after successful scheduling.
* <p>If the "firstTime" property is specified, this property will be ignored.
* Specify one or the other, not both.
*/
public void setDelay(long delay) {
this.delay = delay;
}
/**
* Return the delay before starting the job for the first time.
*/
public long getDelay() {
return this.delay;
}
/**
* Set the period between repeated task executions, in milliseconds.
* <p>Default is -1, leading to one-time execution. In case of zero or a
* positive value, the task will be executed repeatedly, with the given
* interval inbetween executions.
* <p>Note that the semantics of the period value vary between fixed-rate
* and fixed-delay execution.
* <p><b>Note:</b> A period of 0 (for example as fixed delay) <i>is</i>
* supported, because the CommonJ specification defines this as a legal value.
* Hence a value of 0 will result in immediate re-execution after a job has
* finished (not in one-time execution like with <code>java.util.Timer</code>).
* @see #setFixedRate
* @see #isOneTimeTask()
* @see commonj.timers.TimerManager#schedule(commonj.timers.TimerListener, long, long)
*/
public void setPeriod(long period) {
this.period = period;
}
/**
* Return the period between repeated task executions.
*/
public long getPeriod() {
return this.period;
}
/**
* Is this task only ever going to execute once?
* @return <code>true</code> if this task is only ever going to execute once
* @see #getPeriod()
*/
public boolean isOneTimeTask() {
return (this.period < 0);
}
/**
* Set whether to schedule as fixed-rate execution, rather than
* fixed-delay execution. Default is "false", i.e. fixed delay.
* <p>See TimerManager javadoc for details on those execution modes.
* @see commonj.timers.TimerManager#schedule(commonj.timers.TimerListener, long, long)
* @see commonj.timers.TimerManager#scheduleAtFixedRate(commonj.timers.TimerListener, long, long)
*/
public void setFixedRate(boolean fixedRate) {
this.fixedRate = fixedRate;
}
/**
* Return whether to schedule as fixed-rate execution.
*/
public boolean isFixedRate() {
return this.fixedRate;
}
}

View File

@ -0,0 +1,252 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.commonj;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.naming.NamingException;
import commonj.timers.Timer;
import commonj.timers.TimerManager;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.Lifecycle;
import org.springframework.jndi.JndiLocatorSupport;
/**
* {@link org.springframework.beans.factory.FactoryBean} that retrieves a
* CommonJ {@link commonj.timers.TimerManager} and exposes it for bean references.
*
* <p><b>This is the central convenience class for setting up a
* CommonJ TimerManager in a Spring context.</b>
*
* <p>Allows for registration of ScheduledTimerListeners. This is the main
* purpose of this class; the TimerManager itself could also be fetched
* from JNDI via {@link org.springframework.jndi.JndiObjectFactoryBean}.
* In scenarios that just require static registration of tasks at startup,
* there is no need to access the TimerManager itself in application code.
*
* <p>Note that the TimerManager uses a TimerListener instance that is
* shared between repeated executions, in contrast to Quartz which
* instantiates a new Job for each execution.
*
* @author Juergen Hoeller
* @since 2.0
* @see ScheduledTimerListener
* @see commonj.timers.TimerManager
* @see commonj.timers.TimerListener
*/
public class TimerManagerFactoryBean extends JndiLocatorSupport
implements FactoryBean, InitializingBean, DisposableBean, Lifecycle {
private TimerManager timerManager;
private String timerManagerName;
private boolean shared = false;
private ScheduledTimerListener[] scheduledTimerListeners;
private final List timers = new LinkedList();
/**
* Specify the CommonJ TimerManager to delegate to.
* <p>Note that the given TimerManager's lifecycle will be managed
* by this FactoryBean.
* <p>Alternatively (and typically), you can specify the JNDI name
* of the target TimerManager.
* @see #setTimerManagerName
*/
public void setTimerManager(TimerManager timerManager) {
this.timerManager = timerManager;
}
/**
* Set the JNDI name of the CommonJ TimerManager.
* <p>This can either be a fully qualified JNDI name, or the JNDI name relative
* to the current environment naming context if "resourceRef" is set to "true".
* @see #setTimerManager
* @see #setResourceRef
*/
public void setTimerManagerName(String timerManagerName) {
this.timerManagerName = timerManagerName;
}
/**
* Specify whether the TimerManager obtained by this FactoryBean
* is a shared instance ("true") or an independent instance ("false").
* The lifecycle of the former is supposed to be managed by the application
* server, while the lifecycle of the latter is up to the application.
* <p>Default is "false", i.e. managing an independent TimerManager instance.
* This is what the CommonJ specification suggests that application servers
* are supposed to offer via JNDI lookups, typically declared as a
* <code>resource-ref</code> of type <code>commonj.timers.TimerManager</code>
* in <code>web.xml<code>, with <code>res-sharing-scope</code> set to 'Unshareable'.
* <p>Switch this flag to "true" if you are obtaining a shared TimerManager,
* typically through specifying the JNDI location of a TimerManager that
* has been explicitly declared as 'Shareable'. Note that WebLogic's
* cluster-aware Job Scheduler is a shared TimerManager too.
* <p>The sole difference between this FactoryBean being in shared or
* non-shared mode is that it will only attempt to suspend / resume / stop
* the underlying TimerManager in case of an independent (non-shared) instance.
* This only affects the {@link org.springframework.context.Lifecycle} support
* as well as application context shutdown.
* @see #stop()
* @see #start()
* @see #destroy()
* @see commonj.timers.TimerManager
*/
public void setShared(boolean shared) {
this.shared = shared;
}
/**
* Register a list of ScheduledTimerListener objects with the TimerManager
* that this FactoryBean creates. Depending on each ScheduledTimerListener's settings,
* it will be registered via one of TimerManager's schedule methods.
* @see commonj.timers.TimerManager#schedule(commonj.timers.TimerListener, long)
* @see commonj.timers.TimerManager#schedule(commonj.timers.TimerListener, long, long)
* @see commonj.timers.TimerManager#scheduleAtFixedRate(commonj.timers.TimerListener, long, long)
*/
public void setScheduledTimerListeners(ScheduledTimerListener[] scheduledTimerListeners) {
this.scheduledTimerListeners = scheduledTimerListeners;
}
//---------------------------------------------------------------------
// Implementation of InitializingBean interface
//---------------------------------------------------------------------
public void afterPropertiesSet() throws NamingException {
if (this.timerManager == null) {
if (this.timerManagerName == null) {
throw new IllegalArgumentException("Either 'timerManager' or 'timerManagerName' must be specified");
}
this.timerManager = (TimerManager) lookup(this.timerManagerName, TimerManager.class);
}
if (this.scheduledTimerListeners != null) {
for (int i = 0; i < this.scheduledTimerListeners.length; i++) {
ScheduledTimerListener scheduledTask = this.scheduledTimerListeners[i];
Timer timer = null;
if (scheduledTask.isOneTimeTask()) {
timer = this.timerManager.schedule(scheduledTask.getTimerListener(), scheduledTask.getDelay());
}
else {
if (scheduledTask.isFixedRate()) {
timer = this.timerManager.scheduleAtFixedRate(
scheduledTask.getTimerListener(), scheduledTask.getDelay(), scheduledTask.getPeriod());
}
else {
timer = this.timerManager.schedule(
scheduledTask.getTimerListener(), scheduledTask.getDelay(), scheduledTask.getPeriod());
}
}
this.timers.add(timer);
}
}
}
//---------------------------------------------------------------------
// Implementation of FactoryBean interface
//---------------------------------------------------------------------
public Object getObject() {
return this.timerManager;
}
public Class getObjectType() {
return (this.timerManager != null ? this.timerManager.getClass() : TimerManager.class);
}
public boolean isSingleton() {
return true;
}
//---------------------------------------------------------------------
// Implementation of Lifecycle interface
//---------------------------------------------------------------------
/**
* Resumes the underlying TimerManager (if not shared).
* @see commonj.timers.TimerManager#resume()
*/
public void start() {
if (!this.shared) {
this.timerManager.resume();
}
}
/**
* Suspends the underlying TimerManager (if not shared).
* @see commonj.timers.TimerManager#suspend()
*/
public void stop() {
if (!this.shared) {
this.timerManager.suspend();
}
}
/**
* Considers the underlying TimerManager as running if it is
* neither suspending nor stopping.
* @see commonj.timers.TimerManager#isSuspending()
* @see commonj.timers.TimerManager#isStopping()
*/
public boolean isRunning() {
return (!this.timerManager.isSuspending() && !this.timerManager.isStopping());
}
//---------------------------------------------------------------------
// Implementation of DisposableBean interface
//---------------------------------------------------------------------
/**
* Cancels all statically registered Timers on shutdown,
* and stops the underlying TimerManager (if not shared).
* @see commonj.timers.Timer#cancel()
* @see commonj.timers.TimerManager#stop()
*/
public void destroy() {
// Cancel all registered timers.
for (Iterator it = this.timers.iterator(); it.hasNext();) {
Timer timer = (Timer) it.next();
try {
timer.cancel();
}
catch (Throwable ex) {
logger.warn("Could not cancel CommonJ Timer", ex);
}
}
this.timers.clear();
// Stop the entire TimerManager, if necessary.
if (!this.shared) {
// May return early, but at least we already cancelled all known Timers.
this.timerManager.stop();
}
}
}

View File

@ -0,0 +1,198 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.commonj;
import java.util.Collection;
import javax.naming.NamingException;
import commonj.work.Work;
import commonj.work.WorkException;
import commonj.work.WorkItem;
import commonj.work.WorkListener;
import commonj.work.WorkManager;
import commonj.work.WorkRejectedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.jndi.JndiLocatorSupport;
import org.springframework.scheduling.SchedulingException;
import org.springframework.scheduling.SchedulingTaskExecutor;
import org.springframework.util.Assert;
/**
* TaskExecutor implementation that delegates to a CommonJ WorkManager,
* implementing the {@link commonj.work.WorkManager} interface,
* which either needs to be specified as reference or through the JNDI name.
*
* <p><b>This is the central convenience class for setting up a
* CommonJ WorkManager in a Spring context.</b>
*
* <p>Also implements the CommonJ WorkManager interface itself, delegating all
* calls to the target WorkManager. Hence, a caller can choose whether it wants
* to talk to this executor through the Spring TaskExecutor interface or the
* CommonJ WorkManager interface.
*
* <p>The CommonJ WorkManager will usually be retrieved from the application
* server's JNDI environment, as defined in the server's management console.
*
* <p><b>Note: At the time of this writing, the CommonJ WorkManager facility
* is only supported on IBM WebSphere 6.0+ and BEA WebLogic 9.0+,
* despite being such a crucial API for an application server.</b>
* (There is a similar facility available on WebSphere 5.1 Enterprise,
* though, which we will discuss below.)
*
* <p><b>On JBoss and GlassFish, a similar facility is available through
* the JCA WorkManager.</b> See the
* {@link org.springframework.jca.work.jboss.JBossWorkManagerTaskExecutor}
* {@link org.springframework.jca.work.glassfish.GlassFishWorkManagerTaskExecutor}
* classes which are the direct equivalent of this CommonJ adapter class.
*
* <p>A similar facility is available on WebSphere 5.1, under the name
* "Asynch Beans". Its central interface is called WorkManager too and is
* also obtained from JNDI, just like a standard CommonJ WorkManager.
* However, this WorkManager variant is notably different: The central
* execution method is called "startWork" instead of "schedule",
* and takes a slightly different Work interface as parameter.
*
* <p>Support for this WebSphere 5.1 variant can be built with this class
* and its helper DelegatingWork as template: Call the WorkManager's
* <code>startWork(Work)</code> instead of <code>schedule(Work)</code>
* in the <code>execute(Runnable)</code> implementation. Furthermore,
* for simplicity's sake, drop the entire "Implementation of the CommonJ
* WorkManager interface" section (and the corresponding
* <code>implements WorkManager</code> clause at the class level).
* Of course, you also need to change all <code>commonj.work</code> imports in
* your WorkManagerTaskExecutor and DelegatingWork variants to the corresponding
* WebSphere API imports (<code>com.ibm.websphere.asynchbeans.WorkManager</code>
* and <code>com.ibm.websphere.asynchbeans.Work</code>, respectively).
* This should be sufficient to get a TaskExecutor adapter for WebSphere 5.
*
* @author Juergen Hoeller
* @since 2.0
*/
public class WorkManagerTaskExecutor extends JndiLocatorSupport
implements SchedulingTaskExecutor, WorkManager, InitializingBean {
private WorkManager workManager;
private String workManagerName;
private WorkListener workListener;
/**
* Specify the CommonJ WorkManager to delegate to.
* <p>Alternatively, you can also specify the JNDI name
* of the target WorkManager.
* @see #setWorkManagerName
*/
public void setWorkManager(WorkManager workManager) {
this.workManager = workManager;
}
/**
* Set the JNDI name of the CommonJ WorkManager.
* <p>This can either be a fully qualified JNDI name,
* or the JNDI name relative to the current environment
* naming context if "resourceRef" is set to "true".
* @see #setWorkManager
* @see #setResourceRef
*/
public void setWorkManagerName(String workManagerName) {
this.workManagerName = workManagerName;
}
/**
* Specify a CommonJ WorkListener to apply, if any.
* <p>This shared WorkListener instance will be passed on to the
* WorkManager by all {@link #execute} calls on this TaskExecutor.
*/
public void setWorkListener(WorkListener workListener) {
this.workListener = workListener;
}
public void afterPropertiesSet() throws NamingException {
if (this.workManager == null) {
if (this.workManagerName == null) {
throw new IllegalArgumentException("Either 'workManager' or 'workManagerName' must be specified");
}
this.workManager = (WorkManager) lookup(this.workManagerName, WorkManager.class);
}
}
//-------------------------------------------------------------------------
// Implementation of the Spring SchedulingTaskExecutor interface
//-------------------------------------------------------------------------
public void execute(Runnable task) {
Assert.state(this.workManager != null, "No WorkManager specified");
Work work = new DelegatingWork(task);
try {
if (this.workListener != null) {
this.workManager.schedule(work, this.workListener);
}
else {
this.workManager.schedule(work);
}
}
catch (WorkRejectedException ex) {
throw new TaskRejectedException("CommonJ WorkManager did not accept task: " + task, ex);
}
catch (WorkException ex) {
throw new SchedulingException("Could not schedule task on CommonJ WorkManager", ex);
}
}
/**
* This task executor prefers short-lived work units.
*/
public boolean prefersShortLivedTasks() {
return true;
}
//-------------------------------------------------------------------------
// Implementation of the CommonJ WorkManager interface
//-------------------------------------------------------------------------
public WorkItem schedule(Work work)
throws WorkException, IllegalArgumentException {
return this.workManager.schedule(work);
}
public WorkItem schedule(Work work, WorkListener workListener)
throws WorkException, IllegalArgumentException {
return this.workManager.schedule(work, workListener);
}
public boolean waitForAll(Collection workItems, long timeout)
throws InterruptedException, IllegalArgumentException {
return this.workManager.waitForAll(workItems, timeout);
}
public Collection waitForAny(Collection workItems, long timeout)
throws InterruptedException, IllegalArgumentException {
return this.workManager.waitForAny(workItems, timeout);
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Convenience classes for scheduling based on the CommonJ WorkManager/TimerManager
facility, as supported by IBM WebSphere 6.0+ and BEA WebLogic 9.0+.
</body>
</html>

View File

@ -0,0 +1,79 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import org.quartz.Job;
import org.quartz.SchedulerException;
import org.quartz.spi.JobFactory;
import org.quartz.spi.TriggerFiredBundle;
/**
* JobFactory implementation that supports {@link java.lang.Runnable}
* objects as well as standard Quartz {@link org.quartz.Job} instances.
*
* @author Juergen Hoeller
* @since 2.0
* @see DelegatingJob
* @see #adaptJob(Object)
*/
public class AdaptableJobFactory implements JobFactory {
public Job newJob(TriggerFiredBundle bundle) throws SchedulerException {
try {
Object jobObject = createJobInstance(bundle);
return adaptJob(jobObject);
}
catch (Exception ex) {
throw new SchedulerException("Job instantiation failed", ex);
}
}
/**
* Create an instance of the specified job class.
* <p>Can be overridden to post-process the job instance.
* @param bundle the TriggerFiredBundle from which the JobDetail
* and other info relating to the trigger firing can be obtained
* @return the job instance
* @throws Exception if job instantiation failed
*/
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
return bundle.getJobDetail().getJobClass().newInstance();
}
/**
* Adapt the given job object to the Quartz Job interface.
* <p>The default implementation supports straight Quartz Jobs
* as well as Runnables, which get wrapped in a DelegatingJob.
* @param jobObject the original instance of the specified job class
* @return the adapted Quartz Job instance
* @throws Exception if the given job could not be adapted
* @see DelegatingJob
*/
protected Job adaptJob(Object jobObject) throws Exception {
if (jobObject instanceof Job) {
return (Job) jobObject;
}
else if (jobObject instanceof Runnable) {
return new DelegatingJob((Runnable) jobObject);
}
else {
throw new IllegalArgumentException("Unable to execute job class [" + jobObject.getClass().getName() +
"]: only [org.quartz.Job] and [java.lang.Runnable] supported.");
}
}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Constants;
/**
* Convenience subclass of Quartz's {@link org.quartz.CronTrigger}
* class, making bean-style usage easier.
*
* <p>CronTrigger itself is already a JavaBean but lacks sensible defaults.
* This class uses the Spring bean name as job name, the Quartz default group
* ("DEFAULT") as job group, the current time as start time, and indefinite
* repetition, if not specified.
*
* <p>This class will also register the trigger with the job name and group of
* a given {@link org.quartz.JobDetail}. This allows {@link SchedulerFactoryBean}
* to automatically register a trigger for the corresponding JobDetail,
* instead of registering the JobDetail separately.
*
* <p><b>NOTE:</b> This convenience subclass does not work with trigger
* persistence in Quartz 1.6, due to a change in Quartz's trigger handling.
* Use Quartz 1.5 if you rely on trigger persistence based on this class,
* or the standard Quartz {@link org.quartz.CronTrigger} class instead.
*
* @author Juergen Hoeller
* @since 18.02.2004
* @see #setName
* @see #setGroup
* @see #setStartTime
* @see #setJobName
* @see #setJobGroup
* @see #setJobDetail
* @see SchedulerFactoryBean#setTriggers
* @see SchedulerFactoryBean#setJobDetails
* @see SimpleTriggerBean
*/
public class CronTriggerBean extends CronTrigger
implements JobDetailAwareTrigger, BeanNameAware, InitializingBean {
/** Constants for the CronTrigger class */
private static final Constants constants = new Constants(CronTrigger.class);
private JobDetail jobDetail;
private String beanName;
/**
* Register objects in the JobDataMap via a given Map.
* <p>These objects will be available to this Trigger only,
* in contrast to objects in the JobDetail's data map.
* @param jobDataAsMap Map with String keys and any objects as values
* (for example Spring-managed beans)
* @see JobDetailBean#setJobDataAsMap
*/
public void setJobDataAsMap(Map jobDataAsMap) {
getJobDataMap().putAll(jobDataAsMap);
}
/**
* Set the misfire instruction via the name of the corresponding
* constant in the {@link org.quartz.CronTrigger} class.
* Default is <code>MISFIRE_INSTRUCTION_SMART_POLICY</code>.
* @see org.quartz.CronTrigger#MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
* @see org.quartz.CronTrigger#MISFIRE_INSTRUCTION_DO_NOTHING
* @see org.quartz.Trigger#MISFIRE_INSTRUCTION_SMART_POLICY
*/
public void setMisfireInstructionName(String constantName) {
setMisfireInstruction(constants.asNumber(constantName).intValue());
}
/**
* Set a list of TriggerListener names for this job, referring to
* non-global TriggerListeners registered with the Scheduler.
* <p>A TriggerListener name always refers to the name returned
* by the TriggerListener implementation.
* @see SchedulerFactoryBean#setTriggerListeners
* @see org.quartz.TriggerListener#getName
*/
public void setTriggerListenerNames(String[] names) {
for (int i = 0; i < names.length; i++) {
addTriggerListener(names[i]);
}
}
/**
* Set the JobDetail that this trigger should be associated with.
* <p>This is typically used with a bean reference if the JobDetail
* is a Spring-managed bean. Alternatively, the trigger can also
* be associated with a job by name and group.
* @see #setJobName
* @see #setJobGroup
*/
public void setJobDetail(JobDetail jobDetail) {
this.jobDetail = jobDetail;
}
public JobDetail getJobDetail() {
return this.jobDetail;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public void afterPropertiesSet() throws ParseException {
if (getName() == null) {
setName(this.beanName);
}
if (getGroup() == null) {
setGroup(Scheduler.DEFAULT_GROUP);
}
if (getStartTime() == null) {
setStartTime(new Date());
}
if (getTimeZone() == null) {
setTimeZone(TimeZone.getDefault());
}
if (this.jobDetail != null) {
setJobName(this.jobDetail.getName());
setJobGroup(this.jobDetail.getGroup());
}
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.util.Assert;
/**
* Simple Quartz {@link org.quartz.Job} adapter that delegates to a
* given {@link java.lang.Runnable} instance.
*
* <p>Typically used in combination with property injection on the
* Runnable instance, receiving parameters from the Quartz JobDataMap
* that way instead of via the JobExecutionContext.
*
* @author Juergen Hoeller
* @since 2.0
* @see SpringBeanJobFactory
* @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
*/
public class DelegatingJob implements Job {
private final Runnable delegate;
/**
* Create a new DelegatingJob.
* @param delegate the Runnable implementation to delegate to
*/
public DelegatingJob(Runnable delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
/**
* Return the wrapped Runnable implementation.
*/
public final Runnable getDelegate() {
return this.delegate;
}
/**
* Delegates execution to the underlying Runnable.
*/
public void execute(JobExecutionContext context) throws JobExecutionException {
this.delegate.run();
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import org.quartz.JobDetail;
/**
* Interface to be implemented by Quartz Triggers that are aware
* of the JobDetail object that they are associated with.
*
* <p>SchedulerFactoryBean will auto-detect Triggers that implement this
* interface and register them for the respective JobDetail accordingly.
*
* <p>The alternative is to configure a Trigger for a Job name and group:
* This involves the need to register the JobDetail object separately
* with SchedulerFactoryBean.
*
* @author Juergen Hoeller
* @since 18.02.2004
* @see SchedulerFactoryBean#setTriggers
* @see SchedulerFactoryBean#setJobDetails
* @see org.quartz.Trigger#setJobName
* @see org.quartz.Trigger#setJobGroup
*/
public interface JobDetailAwareTrigger {
/**
* Return the JobDetail that this Trigger is associated with.
* @return the associated JobDetail, or <code>null</code> if none
*/
JobDetail getJobDetail();
}

View File

@ -0,0 +1,155 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import java.util.Map;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* Convenience subclass of Quartz' JobDetail class that eases bean-style
* usage.
*
* <p><code>JobDetail</code> itself is already a JavaBean but lacks
* sensible defaults. This class uses the Spring bean name as job name,
* and the Quartz default group ("DEFAULT") as job group if not specified.
*
* @author Juergen Hoeller
* @since 18.02.2004
* @see #setName
* @see #setGroup
* @see org.springframework.beans.factory.BeanNameAware
* @see org.quartz.Scheduler#DEFAULT_GROUP
*/
public class JobDetailBean extends JobDetail
implements BeanNameAware, ApplicationContextAware, InitializingBean {
private Class actualJobClass;
private String beanName;
private ApplicationContext applicationContext;
private String applicationContextJobDataKey;
/**
* Overridden to support any job class, to allow a custom JobFactory
* to adapt the given job class to the Quartz Job interface.
* @see SchedulerFactoryBean#setJobFactory
*/
public void setJobClass(Class jobClass) {
if (jobClass != null && !Job.class.isAssignableFrom(jobClass)) {
super.setJobClass(DelegatingJob.class);
this.actualJobClass = jobClass;
}
else {
super.setJobClass(jobClass);
}
}
/**
* Overridden to support any job class, to allow a custom JobFactory
* to adapt the given job class to the Quartz Job interface.
*/
public Class getJobClass() {
return (this.actualJobClass != null ? this.actualJobClass : super.getJobClass());
}
/**
* Register objects in the JobDataMap via a given Map.
* <p>These objects will be available to this Job only,
* in contrast to objects in the SchedulerContext.
* <p>Note: When using persistent Jobs whose JobDetail will be kept in the
* database, do not put Spring-managed beans or an ApplicationContext
* reference into the JobDataMap but rather into the SchedulerContext.
* @param jobDataAsMap Map with String keys and any objects as values
* (for example Spring-managed beans)
* @see SchedulerFactoryBean#setSchedulerContextAsMap
*/
public void setJobDataAsMap(Map jobDataAsMap) {
getJobDataMap().putAll(jobDataAsMap);
}
/**
* Set a list of JobListener names for this job, referring to
* non-global JobListeners registered with the Scheduler.
* <p>A JobListener name always refers to the name returned
* by the JobListener implementation.
* @see SchedulerFactoryBean#setJobListeners
* @see org.quartz.JobListener#getName
*/
public void setJobListenerNames(String[] names) {
for (int i = 0; i < names.length; i++) {
addJobListener(names[i]);
}
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Set the key of an ApplicationContext reference to expose in the JobDataMap,
* for example "applicationContext". Default is none.
* Only applicable when running in a Spring ApplicationContext.
* <p>In case of a QuartzJobBean, the reference will be applied to the Job
* instance as bean property. An "applicationContext" attribute will correspond
* to a "setApplicationContext" method in that scenario.
* <p>Note that BeanFactory callback interfaces like ApplicationContextAware
* are not automatically applied to Quartz Job instances, because Quartz
* itself is responsible for the lifecycle of its Jobs.
* <p><b>Note: When using persistent job stores where JobDetail contents will
* be kept in the database, do not put an ApplicationContext reference into
* the JobDataMap but rather into the SchedulerContext.</b>
* @see SchedulerFactoryBean#setApplicationContextSchedulerContextKey
* @see org.springframework.context.ApplicationContext
*/
public void setApplicationContextJobDataKey(String applicationContextJobDataKey) {
this.applicationContextJobDataKey = applicationContextJobDataKey;
}
public void afterPropertiesSet() {
if (getName() == null) {
setName(this.beanName);
}
if (getGroup() == null) {
setGroup(Scheduler.DEFAULT_GROUP);
}
if (this.applicationContextJobDataKey != null) {
if (this.applicationContext == null) {
throw new IllegalStateException(
"JobDetailBean needs to be set up in an ApplicationContext " +
"to be able to handle an 'applicationContextJobDataKey'");
}
getJobDataMap().put(this.applicationContextJobDataKey, this.applicationContext);
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import org.springframework.core.NestedRuntimeException;
import org.springframework.util.MethodInvoker;
/**
* Unchecked exception that wraps an exception thrown from a target method.
* Propagated to the Quartz scheduler from a Job that reflectively invokes
* an arbitrary target method.
*
* @author Juergen Hoeller
* @since 2.5.3
* @see MethodInvokingJobDetailFactoryBean
*/
public class JobMethodInvocationFailedException extends NestedRuntimeException {
/**
* Constructor for JobMethodInvocationFailedException.
* @param methodInvoker the MethodInvoker used for reflective invocation
* @param cause the root cause (as thrown from the target method)
*/
public JobMethodInvocationFailedException(MethodInvoker methodInvoker, Throwable cause) {
super("Invocation of method '" + methodInvoker.getTargetMethod() +
"' on target class [" + methodInvoker.getTargetClass() + "] failed", cause);
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.quartz.SchedulerConfigException;
import org.quartz.impl.jdbcjobstore.JobStoreCMT;
import org.quartz.spi.ClassLoadHelper;
import org.quartz.spi.SchedulerSignaler;
import org.quartz.utils.ConnectionProvider;
import org.quartz.utils.DBConnectionManager;
import org.springframework.jdbc.datasource.DataSourceUtils;
/**
* Subclass of Quartz's JobStoreCMT class that delegates to a Spring-managed
* DataSource instead of using a Quartz-managed connection pool. This JobStore
* will be used if SchedulerFactoryBean's "dataSource" property is set.
*
* <p>Supports both transactional and non-transactional DataSource access.
* With a non-XA DataSource and local Spring transactions, a single DataSource
* argument is sufficient. In case of an XA DataSource and global JTA transactions,
* SchedulerFactoryBean's "nonTransactionalDataSource" property should be set,
* passing in a non-XA DataSource that will not participate in global transactions.
*
* <p>Operations performed by this JobStore will properly participate in any
* kind of Spring-managed transaction, as it uses Spring's DataSourceUtils
* connection handling methods that are aware of a current transaction.
*
* <p>Note that all Quartz Scheduler operations that affect the persistent
* job store should usually be performed within active transactions,
* as they assume to get proper locks etc.
*
* @author Juergen Hoeller
* @since 1.1
* @see SchedulerFactoryBean#setDataSource
* @see SchedulerFactoryBean#setNonTransactionalDataSource
* @see org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
* @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
*/
public class LocalDataSourceJobStore extends JobStoreCMT {
/**
* Name used for the transactional ConnectionProvider for Quartz.
* This provider will delegate to the local Spring-managed DataSource.
* @see org.quartz.utils.DBConnectionManager#addConnectionProvider
* @see SchedulerFactoryBean#setDataSource
*/
public static final String TX_DATA_SOURCE_PREFIX = "springTxDataSource.";
/**
* Name used for the non-transactional ConnectionProvider for Quartz.
* This provider will delegate to the local Spring-managed DataSource.
* @see org.quartz.utils.DBConnectionManager#addConnectionProvider
* @see SchedulerFactoryBean#setDataSource
*/
public static final String NON_TX_DATA_SOURCE_PREFIX = "springNonTxDataSource.";
private DataSource dataSource;
public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler)
throws SchedulerConfigException {
// Absolutely needs thread-bound DataSource to initialize.
this.dataSource = SchedulerFactoryBean.getConfigTimeDataSource();
if (this.dataSource == null) {
throw new SchedulerConfigException(
"No local DataSource found for configuration - " +
"'dataSource' property must be set on SchedulerFactoryBean");
}
// Configure transactional connection settings for Quartz.
setDataSource(TX_DATA_SOURCE_PREFIX + getInstanceName());
setDontSetAutoCommitFalse(true);
// Register transactional ConnectionProvider for Quartz.
DBConnectionManager.getInstance().addConnectionProvider(
TX_DATA_SOURCE_PREFIX + getInstanceName(),
new ConnectionProvider() {
public Connection getConnection() throws SQLException {
// Return a transactional Connection, if any.
return DataSourceUtils.doGetConnection(dataSource);
}
public void shutdown() {
// Do nothing - a Spring-managed DataSource has its own lifecycle.
}
}
);
// Non-transactional DataSource is optional: fall back to default
// DataSource if not explicitly specified.
DataSource nonTxDataSource = SchedulerFactoryBean.getConfigTimeNonTransactionalDataSource();
final DataSource nonTxDataSourceToUse =
(nonTxDataSource != null ? nonTxDataSource : this.dataSource);
// Configure non-transactional connection settings for Quartz.
setNonManagedTXDataSource(NON_TX_DATA_SOURCE_PREFIX + getInstanceName());
// Register non-transactional ConnectionProvider for Quartz.
DBConnectionManager.getInstance().addConnectionProvider(
NON_TX_DATA_SOURCE_PREFIX + getInstanceName(),
new ConnectionProvider() {
public Connection getConnection() throws SQLException {
// Always return a non-transactional Connection.
return nonTxDataSourceToUse.getConnection();
}
public void shutdown() {
// Do nothing - a Spring-managed DataSource has its own lifecycle.
}
}
);
super.initialize(loadHelper, signaler);
}
protected void closeConnection(Connection con) {
// Will work for transactional and non-transactional connections.
DataSourceUtils.releaseConnection(con, this.dataSource);
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.SchedulerConfigException;
import org.quartz.spi.ThreadPool;
import org.springframework.core.task.TaskExecutor;
import org.springframework.core.task.TaskRejectedException;
/**
* Quartz ThreadPool adapter that delegates to a Spring-managed
* TaskExecutor instance, specified on SchedulerFactoryBean.
*
* @author Juergen Hoeller
* @since 2.0
* @see SchedulerFactoryBean#setTaskExecutor
*/
public class LocalTaskExecutorThreadPool implements ThreadPool {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private TaskExecutor taskExecutor;
public void initialize() throws SchedulerConfigException {
// Absolutely needs thread-bound TaskExecutor to initialize.
this.taskExecutor = SchedulerFactoryBean.getConfigTimeTaskExecutor();
if (this.taskExecutor == null) {
throw new SchedulerConfigException(
"No local TaskExecutor found for configuration - " +
"'taskExecutor' property must be set on SchedulerFactoryBean");
}
}
public void shutdown(boolean waitForJobsToComplete) {
}
public int getPoolSize() {
return -1;
}
public boolean runInThread(Runnable runnable) {
if (runnable == null) {
return false;
}
try {
this.taskExecutor.execute(runnable);
return true;
}
catch (TaskRejectedException ex) {
logger.error("Task has been rejected by TaskExecutor", ex);
return false;
}
}
public int blockForAvailableThreads() {
// The present implementation always returns 1, making Quartz (1.6)
// always schedule any tasks that it feels like scheduling.
// This could be made smarter for specific TaskExecutors,
// for example calling <code>getMaximumPoolSize() - getActiveCount()</code>
// on a <code>java.util.concurrent.ThreadPoolExecutor</code>.
return 1;
}
}

View File

@ -0,0 +1,291 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.StatefulJob;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.support.ArgumentConvertingMethodInvoker;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;
/**
* {@link org.springframework.beans.factory.FactoryBean} that exposes a
* {@link org.quartz.JobDetail} object which delegates job execution to a
* specified (static or non-static) method. Avoids the need for implementing
* a one-line Quartz Job that just invokes an existing service method on a
* Spring-managed target bean.
*
* <p>Inherits common configuration properties from the {@link MethodInvoker}
* base class, such as {@link #setTargetObject "targetObject"} and
* {@link #setTargetMethod "targetMethod"}, adding support for lookup of the target
* bean by name through the {@link #setTargetBeanName "targetBeanName"} property
* (as alternative to specifying a "targetObject" directly, allowing for
* non-singleton target objects).
*
* <p>Supports both concurrently running jobs and non-currently running
* jobs through the "concurrent" property. Jobs created by this
* MethodInvokingJobDetailFactoryBean are by default volatile and durable
* (according to Quartz terminology).
*
* <p><b>NOTE: JobDetails created via this FactoryBean are <i>not</i>
* serializable and thus not suitable for persistent job stores.</b>
* You need to implement your own Quartz Job as a thin wrapper for each case
* where you want a persistent job to delegate to a specific service method.
*
* @author Juergen Hoeller
* @author Alef Arendsen
* @since 18.02.2004
* @see #setTargetBeanName
* @see #setTargetObject
* @see #setTargetMethod
* @see #setConcurrent
*/
public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethodInvoker
implements FactoryBean, BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean {
private String name;
private String group = Scheduler.DEFAULT_GROUP;
private boolean concurrent = true;
private String targetBeanName;
private String[] jobListenerNames;
private String beanName;
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private BeanFactory beanFactory;
private JobDetail jobDetail;
/**
* Set the name of the job.
* <p>Default is the bean name of this FactoryBean.
* @see org.quartz.JobDetail#setName
*/
public void setName(String name) {
this.name = name;
}
/**
* Set the group of the job.
* <p>Default is the default group of the Scheduler.
* @see org.quartz.JobDetail#setGroup
* @see org.quartz.Scheduler#DEFAULT_GROUP
*/
public void setGroup(String group) {
this.group = group;
}
/**
* Specify whether or not multiple jobs should be run in a concurrent
* fashion. The behavior when one does not want concurrent jobs to be
* executed is realized through adding the {@link StatefulJob} interface.
* More information on stateful versus stateless jobs can be found
* <a href="http://www.opensymphony.com/quartz/tutorial.html#jobsMore">here</a>.
* <p>The default setting is to run jobs concurrently.
*/
public void setConcurrent(boolean concurrent) {
this.concurrent = concurrent;
}
/**
* Set the name of the target bean in the Spring BeanFactory.
* <p>This is an alternative to specifying {@link #setTargetObject "targetObject"},
* allowing for non-singleton beans to be invoked. Note that specified
* "targetObject" and {@link #setTargetClass "targetClass"} values will
* override the corresponding effect of this "targetBeanName" setting
* (i.e. statically pre-define the bean type or even the bean object).
*/
public void setTargetBeanName(String targetBeanName) {
this.targetBeanName = targetBeanName;
}
/**
* Set a list of JobListener names for this job, referring to
* non-global JobListeners registered with the Scheduler.
* <p>A JobListener name always refers to the name returned
* by the JobListener implementation.
* @see SchedulerFactoryBean#setJobListeners
* @see org.quartz.JobListener#getName
*/
public void setJobListenerNames(String[] names) {
this.jobListenerNames = names;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
protected Class resolveClassName(String className) throws ClassNotFoundException {
return ClassUtils.forName(className, this.beanClassLoader);
}
public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException {
prepare();
// Use specific name if given, else fall back to bean name.
String name = (this.name != null ? this.name : this.beanName);
// Consider the concurrent flag to choose between stateful and stateless job.
Class jobClass = (this.concurrent ? (Class) MethodInvokingJob.class : StatefulMethodInvokingJob.class);
// Build JobDetail instance.
this.jobDetail = new JobDetail(name, this.group, jobClass);
this.jobDetail.getJobDataMap().put("methodInvoker", this);
this.jobDetail.setVolatility(true);
this.jobDetail.setDurability(true);
// Register job listener names.
if (this.jobListenerNames != null) {
for (int i = 0; i < this.jobListenerNames.length; i++) {
this.jobDetail.addJobListener(this.jobListenerNames[i]);
}
}
postProcessJobDetail(this.jobDetail);
}
/**
* Callback for post-processing the JobDetail to be exposed by this FactoryBean.
* <p>The default implementation is empty. Can be overridden in subclasses.
* @param jobDetail the JobDetail prepared by this FactoryBean
*/
protected void postProcessJobDetail(JobDetail jobDetail) {
}
/**
* Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature.
*/
public Class getTargetClass() {
Class targetClass = super.getTargetClass();
if (targetClass == null && this.targetBeanName != null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'");
targetClass = this.beanFactory.getType(this.targetBeanName);
}
return targetClass;
}
/**
* Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature.
*/
public Object getTargetObject() {
Object targetObject = super.getTargetObject();
if (targetObject == null && this.targetBeanName != null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'");
targetObject = this.beanFactory.getBean(this.targetBeanName);
}
return targetObject;
}
public Object getObject() {
return this.jobDetail;
}
public Class getObjectType() {
return JobDetail.class;
}
public boolean isSingleton() {
return true;
}
/**
* Quartz Job implementation that invokes a specified method.
* Automatically applied by MethodInvokingJobDetailFactoryBean.
*/
public static class MethodInvokingJob extends QuartzJobBean {
protected static final Log logger = LogFactory.getLog(MethodInvokingJob.class);
private MethodInvoker methodInvoker;
/**
* Set the MethodInvoker to use.
*/
public void setMethodInvoker(MethodInvoker methodInvoker) {
this.methodInvoker = methodInvoker;
}
/**
* Invoke the method via the MethodInvoker.
*/
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
this.methodInvoker.invoke();
}
catch (InvocationTargetException ex) {
if (ex.getTargetException() instanceof JobExecutionException) {
// -> JobExecutionException, to be logged at info level by Quartz
throw (JobExecutionException) ex.getTargetException();
}
else {
// -> "unhandled exception", to be logged at error level by Quartz
throw new JobMethodInvocationFailedException(this.methodInvoker, ex.getTargetException());
}
}
catch (Exception ex) {
// -> "unhandled exception", to be logged at error level by Quartz
throw new JobMethodInvocationFailedException(this.methodInvoker, ex);
}
}
}
/**
* Extension of the MethodInvokingJob, implementing the StatefulJob interface.
* Quartz checks whether or not jobs are stateful and if so,
* won't let jobs interfere with each other.
*/
public static class StatefulMethodInvokingJob extends MethodInvokingJob implements StatefulJob {
// No implementation, just an addition of the tag interface StatefulJob
// in order to allow stateful method invoking jobs.
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
/**
* Simple implementation of the Quartz Job interface, applying the
* passed-in JobDataMap and also the SchedulerContext as bean property
* values. This is appropriate because a new Job instance will be created
* for each execution. JobDataMap entries will override SchedulerContext
* entries with the same keys.
*
* <p>For example, let's assume that the JobDataMap contains a key
* "myParam" with value "5": The Job implementation can then expose
* a bean property "myParam" of type int to receive such a value,
* i.e. a method "setMyParam(int)". This will also work for complex
* types like business objects etc.
*
* <p>Note: The QuartzJobBean class itself only implements the standard
* Quartz {@link org.quartz.Job} interface. Let your subclass explicitly
* implement the Quartz {@link org.quartz.StatefulJob} interface to
* mark your concrete job bean as stateful.
*
* <p>This version of QuartzJobBean requires Quartz 1.5 or higher,
* due to the support for trigger-specific job data.
*
* <p><b>Note that as of Spring 2.0 and Quartz 1.5, the preferred way
* to apply dependency injection to Job instances is via a JobFactory:</b>
* that is, to specify {@link SpringBeanJobFactory} as Quartz JobFactory
* (typically via
* {@link SchedulerFactoryBean#setJobFactory} SchedulerFactoryBean's "jobFactory" property}).
* This allows to implement dependency-injected Quartz Jobs without
* a dependency on Spring base classes.
*
* @author Juergen Hoeller
* @since 18.02.2004
* @see org.quartz.JobExecutionContext#getMergedJobDataMap()
* @see org.quartz.Scheduler#getContext()
* @see JobDetailBean#setJobDataAsMap
* @see SimpleTriggerBean#setJobDataAsMap
* @see CronTriggerBean#setJobDataAsMap
* @see SchedulerFactoryBean#setSchedulerContextAsMap
* @see SpringBeanJobFactory
* @see SchedulerFactoryBean#setJobFactory
*/
public abstract class QuartzJobBean implements Job {
/**
* This implementation applies the passed-in job data map as bean property
* values, and delegates to <code>executeInternal</code> afterwards.
* @see #executeInternal
*/
public final void execute(JobExecutionContext context) throws JobExecutionException {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValues(context.getScheduler().getContext());
pvs.addPropertyValues(context.getMergedJobDataMap());
bw.setPropertyValues(pvs, true);
}
catch (SchedulerException ex) {
throw new JobExecutionException(ex);
}
executeInternal(context);
}
/**
* Execute the actual job. The job data map will already have been
* applied as bean property values by execute. The contract is
* exactly the same as for the standard Quartz execute method.
* @see #execute
*/
protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.spi.ClassLoadHelper;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
/**
* Wrapper that adapts from the Quartz {@link ClassLoadHelper} interface
* onto Spring's {@link ResourceLoader} interface. Used by default when
* the SchedulerFactoryBean runs in a Spring ApplicationContext.
*
* @author Juergen Hoeller
* @since 2.5.5
* @see SchedulerFactoryBean#setApplicationContext
*/
public class ResourceLoaderClassLoadHelper implements ClassLoadHelper {
protected static final Log logger = LogFactory.getLog(ResourceLoaderClassLoadHelper.class);
private ResourceLoader resourceLoader;
/**
* Create a new ResourceLoaderClassLoadHelper for the default
* ResourceLoader.
* @see SchedulerFactoryBean#getConfigTimeResourceLoader()
*/
public ResourceLoaderClassLoadHelper() {
}
/**
* Create a new ResourceLoaderClassLoadHelper for the given ResourceLoader.
* @param resourceLoader the ResourceLoader to delegate to
*/
public ResourceLoaderClassLoadHelper(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void initialize() {
if (this.resourceLoader == null) {
this.resourceLoader = SchedulerFactoryBean.getConfigTimeResourceLoader();
if (this.resourceLoader == null) {
this.resourceLoader = new DefaultResourceLoader();
}
}
}
public Class loadClass(String name) throws ClassNotFoundException {
return this.resourceLoader.getClassLoader().loadClass(name);
}
public URL getResource(String name) {
Resource resource = this.resourceLoader.getResource(name);
try {
return resource.getURL();
}
catch (FileNotFoundException ex) {
return null;
}
catch (IOException ex) {
logger.warn("Could not load " + resource);
return null;
}
}
public InputStream getResourceAsStream(String name) {
Resource resource = this.resourceLoader.getResource(name);
try {
return resource.getInputStream();
}
catch (FileNotFoundException ex) {
return null;
}
catch (IOException ex) {
logger.warn("Could not load " + resource);
return null;
}
}
public ClassLoader getClassLoader() {
return this.resourceLoader.getClassLoader();
}
}

View File

@ -0,0 +1,407 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Calendar;
import org.quartz.JobDetail;
import org.quartz.JobListener;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerListener;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
import org.quartz.spi.ClassLoadHelper;
import org.quartz.xml.JobSchedulingDataProcessor;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* Common base class for accessing a Quartz Scheduler, i.e. for registering jobs,
* triggers and listeners on a {@link org.quartz.Scheduler} instance.
*
* <p>For concrete usage, check out the {@link SchedulerFactoryBean} and
* {@link SchedulerAccessorBean} classes.
*
* @author Juergen Hoeller
* @since 2.5.6
*/
public abstract class SchedulerAccessor implements ResourceLoaderAware {
protected final Log logger = LogFactory.getLog(getClass());
private boolean overwriteExistingJobs = false;
private String[] jobSchedulingDataLocations;
private List jobDetails;
private Map calendars;
private List triggers;
private SchedulerListener[] schedulerListeners;
private JobListener[] globalJobListeners;
private JobListener[] jobListeners;
private TriggerListener[] globalTriggerListeners;
private TriggerListener[] triggerListeners;
private PlatformTransactionManager transactionManager;
protected ResourceLoader resourceLoader;
/**
* Set whether any jobs defined on this SchedulerFactoryBean should overwrite
* existing job definitions. Default is "false", to not overwrite already
* registered jobs that have been read in from a persistent job store.
*/
public void setOverwriteExistingJobs(boolean overwriteExistingJobs) {
this.overwriteExistingJobs = overwriteExistingJobs;
}
/**
* Set the location of a Quartz job definition XML file that follows the
* "job_scheduling_data_1_5" XSD. Can be specified to automatically
* register jobs that are defined in such a file, possibly in addition
* to jobs defined directly on this SchedulerFactoryBean.
* @see org.quartz.xml.JobSchedulingDataProcessor
*/
public void setJobSchedulingDataLocation(String jobSchedulingDataLocation) {
this.jobSchedulingDataLocations = new String[] {jobSchedulingDataLocation};
}
/**
* Set the locations of Quartz job definition XML files that follow the
* "job_scheduling_data_1_5" XSD. Can be specified to automatically
* register jobs that are defined in such files, possibly in addition
* to jobs defined directly on this SchedulerFactoryBean.
* @see org.quartz.xml.JobSchedulingDataProcessor
*/
public void setJobSchedulingDataLocations(String[] jobSchedulingDataLocations) {
this.jobSchedulingDataLocations = jobSchedulingDataLocations;
}
/**
* Register a list of JobDetail objects with the Scheduler that
* this FactoryBean creates, to be referenced by Triggers.
* <p>This is not necessary when a Trigger determines the JobDetail
* itself: In this case, the JobDetail will be implicitly registered
* in combination with the Trigger.
* @see #setTriggers
* @see org.quartz.JobDetail
* @see JobDetailBean
* @see JobDetailAwareTrigger
* @see org.quartz.Trigger#setJobName
*/
public void setJobDetails(JobDetail[] jobDetails) {
// Use modifiable ArrayList here, to allow for further adding of
// JobDetail objects during autodetection of JobDetailAwareTriggers.
this.jobDetails = new ArrayList(Arrays.asList(jobDetails));
}
/**
* Register a list of Quartz Calendar objects with the Scheduler
* that this FactoryBean creates, to be referenced by Triggers.
* @param calendars Map with calendar names as keys as Calendar
* objects as values
* @see org.quartz.Calendar
* @see org.quartz.Trigger#setCalendarName
*/
public void setCalendars(Map calendars) {
this.calendars = calendars;
}
/**
* Register a list of Trigger objects with the Scheduler that
* this FactoryBean creates.
* <p>If the Trigger determines the corresponding JobDetail itself,
* the job will be automatically registered with the Scheduler.
* Else, the respective JobDetail needs to be registered via the
* "jobDetails" property of this FactoryBean.
* @see #setJobDetails
* @see org.quartz.JobDetail
* @see JobDetailAwareTrigger
* @see CronTriggerBean
* @see SimpleTriggerBean
*/
public void setTriggers(Trigger[] triggers) {
this.triggers = Arrays.asList(triggers);
}
/**
* Specify Quartz SchedulerListeners to be registered with the Scheduler.
*/
public void setSchedulerListeners(SchedulerListener[] schedulerListeners) {
this.schedulerListeners = schedulerListeners;
}
/**
* Specify global Quartz JobListeners to be registered with the Scheduler.
* Such JobListeners will apply to all Jobs in the Scheduler.
*/
public void setGlobalJobListeners(JobListener[] globalJobListeners) {
this.globalJobListeners = globalJobListeners;
}
/**
* Specify named Quartz JobListeners to be registered with the Scheduler.
* Such JobListeners will only apply to Jobs that explicitly activate
* them via their name.
* @see org.quartz.JobListener#getName
* @see org.quartz.JobDetail#addJobListener
* @see JobDetailBean#setJobListenerNames
*/
public void setJobListeners(JobListener[] jobListeners) {
this.jobListeners = jobListeners;
}
/**
* Specify global Quartz TriggerListeners to be registered with the Scheduler.
* Such TriggerListeners will apply to all Triggers in the Scheduler.
*/
public void setGlobalTriggerListeners(TriggerListener[] globalTriggerListeners) {
this.globalTriggerListeners = globalTriggerListeners;
}
/**
* Specify named Quartz TriggerListeners to be registered with the Scheduler.
* Such TriggerListeners will only apply to Triggers that explicitly activate
* them via their name.
* @see org.quartz.TriggerListener#getName
* @see org.quartz.Trigger#addTriggerListener
* @see CronTriggerBean#setTriggerListenerNames
* @see SimpleTriggerBean#setTriggerListenerNames
*/
public void setTriggerListeners(TriggerListener[] triggerListeners) {
this.triggerListeners = triggerListeners;
}
/**
* Set the transaction manager to be used for registering jobs and triggers
* that are defined by this SchedulerFactoryBean. Default is none; setting
* this only makes sense when specifying a DataSource for the Scheduler.
*/
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* Register jobs and triggers (within a transaction, if possible).
*/
protected void registerJobsAndTriggers() throws SchedulerException {
TransactionStatus transactionStatus = null;
if (this.transactionManager != null) {
transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
}
try {
if (this.jobSchedulingDataLocations != null) {
ClassLoadHelper clh = new ResourceLoaderClassLoadHelper(this.resourceLoader);
clh.initialize();
JobSchedulingDataProcessor dataProcessor = new JobSchedulingDataProcessor(clh, true, true);
for (int i = 0; i < this.jobSchedulingDataLocations.length; i++) {
dataProcessor.processFileAndScheduleJobs(
this.jobSchedulingDataLocations[i], getScheduler(), this.overwriteExistingJobs);
}
}
// Register JobDetails.
if (this.jobDetails != null) {
for (Iterator it = this.jobDetails.iterator(); it.hasNext();) {
JobDetail jobDetail = (JobDetail) it.next();
addJobToScheduler(jobDetail);
}
}
else {
// Create empty list for easier checks when registering triggers.
this.jobDetails = new LinkedList();
}
// Register Calendars.
if (this.calendars != null) {
for (Iterator it = this.calendars.keySet().iterator(); it.hasNext();) {
String calendarName = (String) it.next();
Calendar calendar = (Calendar) this.calendars.get(calendarName);
getScheduler().addCalendar(calendarName, calendar, true, true);
}
}
// Register Triggers.
if (this.triggers != null) {
for (Iterator it = this.triggers.iterator(); it.hasNext();) {
Trigger trigger = (Trigger) it.next();
addTriggerToScheduler(trigger);
}
}
}
catch (Throwable ex) {
if (transactionStatus != null) {
try {
this.transactionManager.rollback(transactionStatus);
}
catch (TransactionException tex) {
logger.error("Job registration exception overridden by rollback exception", ex);
throw tex;
}
}
if (ex instanceof SchedulerException) {
throw (SchedulerException) ex;
}
if (ex instanceof Exception) {
throw new SchedulerException(
"Registration of jobs and triggers failed: " + ex.getMessage(), (Exception) ex);
}
throw new SchedulerException("Registration of jobs and triggers failed: " + ex.getMessage());
}
if (transactionStatus != null) {
this.transactionManager.commit(transactionStatus);
}
}
/**
* Add the given job to the Scheduler, if it doesn't already exist.
* Overwrites the job in any case if "overwriteExistingJobs" is set.
* @param jobDetail the job to add
* @return <code>true</code> if the job was actually added,
* <code>false</code> if it already existed before
* @see #setOverwriteExistingJobs
*/
private boolean addJobToScheduler(JobDetail jobDetail) throws SchedulerException {
if (this.overwriteExistingJobs ||
getScheduler().getJobDetail(jobDetail.getName(), jobDetail.getGroup()) == null) {
getScheduler().addJob(jobDetail, true);
return true;
}
else {
return false;
}
}
/**
* Add the given trigger to the Scheduler, if it doesn't already exist.
* Overwrites the trigger in any case if "overwriteExistingJobs" is set.
* @param trigger the trigger to add
* @return <code>true</code> if the trigger was actually added,
* <code>false</code> if it already existed before
* @see #setOverwriteExistingJobs
*/
private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException {
boolean triggerExists = (getScheduler().getTrigger(trigger.getName(), trigger.getGroup()) != null);
if (!triggerExists || this.overwriteExistingJobs) {
// Check if the Trigger is aware of an associated JobDetail.
if (trigger instanceof JobDetailAwareTrigger) {
JobDetail jobDetail = ((JobDetailAwareTrigger) trigger).getJobDetail();
// Automatically register the JobDetail too.
if (!this.jobDetails.contains(jobDetail) && addJobToScheduler(jobDetail)) {
this.jobDetails.add(jobDetail);
}
}
if (!triggerExists) {
try {
getScheduler().scheduleJob(trigger);
}
catch (ObjectAlreadyExistsException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Unexpectedly found existing trigger, assumably due to cluster race condition: " +
ex.getMessage() + " - can safely be ignored");
}
if (this.overwriteExistingJobs) {
getScheduler().rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
}
}
}
else {
getScheduler().rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
}
return true;
}
else {
return false;
}
}
/**
* Register all specified listeners with the Scheduler.
*/
protected void registerListeners() throws SchedulerException {
if (this.schedulerListeners != null) {
for (int i = 0; i < this.schedulerListeners.length; i++) {
getScheduler().addSchedulerListener(this.schedulerListeners[i]);
}
}
if (this.globalJobListeners != null) {
for (int i = 0; i < this.globalJobListeners.length; i++) {
getScheduler().addGlobalJobListener(this.globalJobListeners[i]);
}
}
if (this.jobListeners != null) {
for (int i = 0; i < this.jobListeners.length; i++) {
getScheduler().addJobListener(this.jobListeners[i]);
}
}
if (this.globalTriggerListeners != null) {
for (int i = 0; i < this.globalTriggerListeners.length; i++) {
getScheduler().addGlobalTriggerListener(this.globalTriggerListeners[i]);
}
}
if (this.triggerListeners != null) {
for (int i = 0; i < this.triggerListeners.length; i++) {
getScheduler().addTriggerListener(this.triggerListeners[i]);
}
}
}
/**
* Template method that determines the Scheduler to operate on.
* To be implemented by subclasses.
*/
protected abstract Scheduler getScheduler();
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.SchedulerRepository;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
/**
* Spring bean-style class for accessing a Quartz Scheduler, i.e. for registering jobs,
* triggers and listeners on a given {@link org.quartz.Scheduler} instance.
*
* @author Juergen Hoeller
* @since 2.5.6
* @see #setScheduler
* @see #setSchedulerName
*/
public class SchedulerAccessorBean extends SchedulerAccessor implements BeanFactoryAware, InitializingBean {
private String schedulerName;
private Scheduler scheduler;
private BeanFactory beanFactory;
/**
* Specify the Quartz Scheduler to operate on via its scheduler name in the Spring
* application context or also in the Quartz {@link org.quartz.impl.SchedulerRepository}.
* <p>Schedulers can be registered in the repository through custom bootstrapping,
* e.g. via the {@link org.quartz.impl.StdSchedulerFactory} or
* {@link org.quartz.impl.DirectSchedulerFactory} factory classes.
* However, in general, it's preferable to use Spring's {@link SchedulerFactoryBean}
* which includes the job/trigger/listener capabilities of this accessor as well.
*/
public void setSchedulerName(String schedulerName) {
this.schedulerName = schedulerName;
}
/**
* Specify the Quartz Scheduler instance to operate on.
*/
public void setScheduler(Scheduler scheduler) {
this.scheduler = scheduler;
}
/**
* Return the Quartz Scheduler instance that this accessor operates on.
*/
public Scheduler getScheduler() {
return this.scheduler;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void afterPropertiesSet() throws SchedulerException {
if (this.scheduler == null) {
if (this.schedulerName != null) {
this.scheduler = findScheduler(this.schedulerName);
}
else {
throw new IllegalStateException("No Scheduler specified");
}
}
registerListeners();
registerJobsAndTriggers();
}
protected Scheduler findScheduler(String schedulerName) throws SchedulerException {
if (this.beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory lbf = (ListableBeanFactory) this.beanFactory;
String[] beanNames = lbf.getBeanNamesForType(Scheduler.class);
for (int i = 0; i < beanNames.length; i++) {
Scheduler schedulerBean = (Scheduler) lbf.getBean(beanNames[i]);
if (schedulerName.equals(schedulerBean.getSchedulerName())) {
return schedulerBean;
}
}
}
Scheduler schedulerInRepo = SchedulerRepository.getInstance().lookup(schedulerName);
if (schedulerInRepo == null) {
throw new IllegalStateException("No Scheduler named '" + schedulerName + "' found");
}
return schedulerInRepo;
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import org.quartz.SchedulerContext;
/**
* Callback interface to be implemented by Spring-managed
* Quartz artifacts that need access to the SchedulerContext
* (without having natural access to it).
*
* <p>Currently only supported for custom JobFactory implementations
* that are passed in via Spring's SchedulerFactoryBean.
*
* @author Juergen Hoeller
* @since 2.0
* @see org.quartz.spi.JobFactory
* @see SchedulerFactoryBean#setJobFactory
*/
public interface SchedulerContextAware {
/**
* Set the SchedulerContext of the current Quartz Scheduler.
* @see org.quartz.Scheduler#getContext()
*/
void setSchedulerContext(SchedulerContext schedulerContext);
}

View File

@ -0,0 +1,728 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.impl.RemoteScheduler;
import org.quartz.impl.SchedulerRepository;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.simpl.SimpleThreadPool;
import org.quartz.spi.JobFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.Lifecycle;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.SchedulingException;
import org.springframework.util.CollectionUtils;
/**
* {@link FactoryBean} that creates and configures a Quartz {@link org.quartz.Scheduler},
* manages its lifecycle as part of the Spring application context, and exposes the
* Scheduler as bean reference for dependency injection.
*
* <p>Allows registration of JobDetails, Calendars and Triggers, automatically
* starting the scheduler on initialization and shutting it down on destruction.
* In scenarios that just require static registration of jobs at startup, there
* is no need to access the Scheduler instance itself in application code.
*
* <p>For dynamic registration of jobs at runtime, use a bean reference to
* this SchedulerFactoryBean to get direct access to the Quartz Scheduler
* (<code>org.quartz.Scheduler</code>). This allows you to create new jobs
* and triggers, and also to control and monitor the entire Scheduler.
*
* <p>Note that Quartz instantiates a new Job for each execution, in
* contrast to Timer which uses a TimerTask instance that is shared
* between repeated executions. Just JobDetail descriptors are shared.
*
* <p>When using persistent jobs, it is strongly recommended to perform all
* operations on the Scheduler within Spring-managed (or plain JTA) transactions.
* Else, database locking will not properly work and might even break.
* (See {@link #setDataSource setDataSource} javadoc for details.)
*
* <p>The preferred way to achieve transactional execution is to demarcate
* declarative transactions at the business facade level, which will
* automatically apply to Scheduler operations performed within those scopes.
* Alternatively, you may add transactional advice for the Scheduler itself.
*
* <p><b>Note:</b> This version of Spring's SchedulerFactoryBean requires
* Quartz 1.5.x or 1.6.x. The "jobSchedulingDataLocation" feature requires
* Quartz 1.6.1 or higher (as of Spring 2.5.5).
*
* @author Juergen Hoeller
* @since 18.02.2004
* @see #setDataSource
* @see org.quartz.Scheduler
* @see org.quartz.SchedulerFactory
* @see org.quartz.impl.StdSchedulerFactory
* @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean
*/
public class SchedulerFactoryBean extends SchedulerAccessor
implements FactoryBean, BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, Lifecycle {
public static final String PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount";
public static final int DEFAULT_THREAD_COUNT = 10;
private static final ThreadLocal configTimeResourceLoaderHolder = new ThreadLocal();
private static final ThreadLocal configTimeTaskExecutorHolder = new ThreadLocal();
private static final ThreadLocal configTimeDataSourceHolder = new ThreadLocal();
private static final ThreadLocal configTimeNonTransactionalDataSourceHolder = new ThreadLocal();
/**
* Return the ResourceLoader for the currently configured Quartz Scheduler,
* to be used by ResourceLoaderClassLoadHelper.
* <p>This instance will be set before initialization of the corresponding
* Scheduler, and reset immediately afterwards. It is thus only available
* during configuration.
* @see #setApplicationContext
* @see ResourceLoaderClassLoadHelper
*/
public static ResourceLoader getConfigTimeResourceLoader() {
return (ResourceLoader) configTimeResourceLoaderHolder.get();
}
/**
* Return the TaskExecutor for the currently configured Quartz Scheduler,
* to be used by LocalTaskExecutorThreadPool.
* <p>This instance will be set before initialization of the corresponding
* Scheduler, and reset immediately afterwards. It is thus only available
* during configuration.
* @see #setTaskExecutor
* @see LocalTaskExecutorThreadPool
*/
public static TaskExecutor getConfigTimeTaskExecutor() {
return (TaskExecutor) configTimeTaskExecutorHolder.get();
}
/**
* Return the DataSource for the currently configured Quartz Scheduler,
* to be used by LocalDataSourceJobStore.
* <p>This instance will be set before initialization of the corresponding
* Scheduler, and reset immediately afterwards. It is thus only available
* during configuration.
* @see #setDataSource
* @see LocalDataSourceJobStore
*/
public static DataSource getConfigTimeDataSource() {
return (DataSource) configTimeDataSourceHolder.get();
}
/**
* Return the non-transactional DataSource for the currently configured
* Quartz Scheduler, to be used by LocalDataSourceJobStore.
* <p>This instance will be set before initialization of the corresponding
* Scheduler, and reset immediately afterwards. It is thus only available
* during configuration.
* @see #setNonTransactionalDataSource
* @see LocalDataSourceJobStore
*/
public static DataSource getConfigTimeNonTransactionalDataSource() {
return (DataSource) configTimeNonTransactionalDataSourceHolder.get();
}
private Class schedulerFactoryClass = StdSchedulerFactory.class;
private String schedulerName;
private Resource configLocation;
private Properties quartzProperties;
private TaskExecutor taskExecutor;
private DataSource dataSource;
private DataSource nonTransactionalDataSource;
private Map schedulerContextMap;
private ApplicationContext applicationContext;
private String applicationContextSchedulerContextKey;
private JobFactory jobFactory;
private boolean jobFactorySet = false;
private boolean autoStartup = true;
private int startupDelay = 0;
private boolean exposeSchedulerInRepository = false;
private boolean waitForJobsToCompleteOnShutdown = false;
private Scheduler scheduler;
/**
* Set the Quartz SchedulerFactory implementation to use.
* <p>Default is StdSchedulerFactory, reading in the standard
* <code>quartz.properties</code> from <code>quartz.jar</code>.
* To use custom Quartz properties, specify the "configLocation"
* or "quartzProperties" bean property on this FactoryBean.
* @see org.quartz.impl.StdSchedulerFactory
* @see #setConfigLocation
* @see #setQuartzProperties
*/
public void setSchedulerFactoryClass(Class schedulerFactoryClass) {
if (schedulerFactoryClass == null || !SchedulerFactory.class.isAssignableFrom(schedulerFactoryClass)) {
throw new IllegalArgumentException("schedulerFactoryClass must implement [org.quartz.SchedulerFactory]");
}
this.schedulerFactoryClass = schedulerFactoryClass;
}
/**
* Set the name of the Scheduler to create via the SchedulerFactory.
* <p>If not specified, the bean name will be used as default scheduler name.
* @see #setBeanName
* @see org.quartz.SchedulerFactory#getScheduler()
* @see org.quartz.SchedulerFactory#getScheduler(String)
*/
public void setSchedulerName(String schedulerName) {
this.schedulerName = schedulerName;
}
/**
* Set the location of the Quartz properties config file, for example
* as classpath resource "classpath:quartz.properties".
* <p>Note: Can be omitted when all necessary properties are specified
* locally via this bean, or when relying on Quartz' default configuration.
* @see #setQuartzProperties
*/
public void setConfigLocation(Resource configLocation) {
this.configLocation = configLocation;
}
/**
* Set Quartz properties, like "org.quartz.threadPool.class".
* <p>Can be used to override values in a Quartz properties config file,
* or to specify all necessary properties locally.
* @see #setConfigLocation
*/
public void setQuartzProperties(Properties quartzProperties) {
this.quartzProperties = quartzProperties;
}
/**
* Set the Spring TaskExecutor to use as Quartz backend.
* Exposed as thread pool through the Quartz SPI.
* <p>Can be used to assign a JDK 1.5 ThreadPoolExecutor or a CommonJ
* WorkManager as Quartz backend, to avoid Quartz's manual thread creation.
* <p>By default, a Quartz SimpleThreadPool will be used, configured through
* the corresponding Quartz properties.
* @see #setQuartzProperties
* @see LocalTaskExecutorThreadPool
* @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
* @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
/**
* Set the default DataSource to be used by the Scheduler. If set,
* this will override corresponding settings in Quartz properties.
* <p>Note: If this is set, the Quartz settings should not define
* a job store "dataSource" to avoid meaningless double configuration.
* <p>A Spring-specific subclass of Quartz' JobStoreCMT will be used.
* It is therefore strongly recommended to perform all operations on
* the Scheduler within Spring-managed (or plain JTA) transactions.
* Else, database locking will not properly work and might even break
* (e.g. if trying to obtain a lock on Oracle without a transaction).
* <p>Supports both transactional and non-transactional DataSource access.
* With a non-XA DataSource and local Spring transactions, a single DataSource
* argument is sufficient. In case of an XA DataSource and global JTA transactions,
* SchedulerFactoryBean's "nonTransactionalDataSource" property should be set,
* passing in a non-XA DataSource that will not participate in global transactions.
* @see #setNonTransactionalDataSource
* @see #setQuartzProperties
* @see #setTransactionManager
* @see LocalDataSourceJobStore
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* Set the DataSource to be used by the Scheduler <i>for non-transactional access</i>.
* <p>This is only necessary if the default DataSource is an XA DataSource that will
* always participate in transactions: A non-XA version of that DataSource should
* be specified as "nonTransactionalDataSource" in such a scenario.
* <p>This is not relevant with a local DataSource instance and Spring transactions.
* Specifying a single default DataSource as "dataSource" is sufficient there.
* @see #setDataSource
* @see LocalDataSourceJobStore
*/
public void setNonTransactionalDataSource(DataSource nonTransactionalDataSource) {
this.nonTransactionalDataSource = nonTransactionalDataSource;
}
/**
* Register objects in the Scheduler context via a given Map.
* These objects will be available to any Job that runs in this Scheduler.
* <p>Note: When using persistent Jobs whose JobDetail will be kept in the
* database, do not put Spring-managed beans or an ApplicationContext
* reference into the JobDataMap but rather into the SchedulerContext.
* @param schedulerContextAsMap Map with String keys and any objects as
* values (for example Spring-managed beans)
* @see JobDetailBean#setJobDataAsMap
*/
public void setSchedulerContextAsMap(Map schedulerContextAsMap) {
this.schedulerContextMap = schedulerContextAsMap;
}
/**
* Set the key of an ApplicationContext reference to expose in the
* SchedulerContext, for example "applicationContext". Default is none.
* Only applicable when running in a Spring ApplicationContext.
* <p>Note: When using persistent Jobs whose JobDetail will be kept in the
* database, do not put an ApplicationContext reference into the JobDataMap
* but rather into the SchedulerContext.
* <p>In case of a QuartzJobBean, the reference will be applied to the Job
* instance as bean property. An "applicationContext" attribute will
* correspond to a "setApplicationContext" method in that scenario.
* <p>Note that BeanFactory callback interfaces like ApplicationContextAware
* are not automatically applied to Quartz Job instances, because Quartz
* itself is reponsible for the lifecycle of its Jobs.
* @see JobDetailBean#setApplicationContextJobDataKey
* @see org.springframework.context.ApplicationContext
*/
public void setApplicationContextSchedulerContextKey(String applicationContextSchedulerContextKey) {
this.applicationContextSchedulerContextKey = applicationContextSchedulerContextKey;
}
/**
* Set the Quartz JobFactory to use for this Scheduler.
* <p>Default is Spring's {@link AdaptableJobFactory}, which supports
* {@link java.lang.Runnable} objects as well as standard Quartz
* {@link org.quartz.Job} instances. Note that this default only applies
* to a <i>local</i> Scheduler, not to a RemoteScheduler (where setting
* a custom JobFactory is not supported by Quartz).
* <p>Specify an instance of Spring's {@link SpringBeanJobFactory} here
* (typically as an inner bean definition) to automatically populate a job's
* bean properties from the specified job data map and scheduler context.
* @see AdaptableJobFactory
* @see SpringBeanJobFactory
*/
public void setJobFactory(JobFactory jobFactory) {
this.jobFactory = jobFactory;
this.jobFactorySet = true;
}
/**
* Set whether to automatically start the scheduler after initialization.
* <p>Default is "true"; set this to "false" to allow for manual startup.
*/
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
/**
* Set the number of seconds to wait after initialization before
* starting the scheduler asynchronously. Default is 0, meaning
* immediate synchronous startup on initialization of this bean.
* <p>Setting this to 10 or 20 seconds makes sense if no jobs
* should be run before the entire application has started up.
*/
public void setStartupDelay(int startupDelay) {
this.startupDelay = startupDelay;
}
/**
* Set whether to expose the Spring-managed {@link Scheduler} instance in the
* Quartz {@link SchedulerRepository}. Default is "false", since the Spring-managed
* Scheduler is usually exclusively intended for access within the Spring context.
* <p>Switch this flag to "true" in order to expose the Scheduler globally.
* This is not recommended unless you have an existing Spring application that
* relies on this behavior. Note that such global exposure was the accidental
* default in earlier Spring versions; this has been fixed as of Spring 2.5.6.
*/
public void setExposeSchedulerInRepository(boolean exposeSchedulerInRepository) {
this.exposeSchedulerInRepository = exposeSchedulerInRepository;
}
/**
* Set whether to wait for running jobs to complete on shutdown.
* <p>Default is "false". Switch this to "true" if you prefer
* fully completed jobs at the expense of a longer shutdown phase.
* @see org.quartz.Scheduler#shutdown(boolean)
*/
public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
}
public void setBeanName(String name) {
if (this.schedulerName == null) {
this.schedulerName = name;
}
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
//---------------------------------------------------------------------
// Implementation of InitializingBean interface
//---------------------------------------------------------------------
public void afterPropertiesSet() throws Exception {
if (this.applicationContext != null && this.resourceLoader == null) {
this.resourceLoader = this.applicationContext;
}
if (this.dataSource == null && this.nonTransactionalDataSource != null) {
this.dataSource = this.nonTransactionalDataSource;
}
// Create SchedulerFactory instance.
SchedulerFactory schedulerFactory = (SchedulerFactory) BeanUtils.instantiateClass(this.schedulerFactoryClass);
initSchedulerFactory(schedulerFactory);
if (this.resourceLoader != null) {
// Make given ResourceLoader available for SchedulerFactory configuration.
configTimeResourceLoaderHolder.set(this.resourceLoader);
}
if (this.taskExecutor != null) {
// Make given TaskExecutor available for SchedulerFactory configuration.
configTimeTaskExecutorHolder.set(this.taskExecutor);
}
if (this.dataSource != null) {
// Make given DataSource available for SchedulerFactory configuration.
configTimeDataSourceHolder.set(this.dataSource);
}
if (this.nonTransactionalDataSource != null) {
// Make given non-transactional DataSource available for SchedulerFactory configuration.
configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource);
}
// Get Scheduler instance from SchedulerFactory.
try {
this.scheduler = createScheduler(schedulerFactory, this.schedulerName);
populateSchedulerContext();
if (!this.jobFactorySet && !(this.scheduler instanceof RemoteScheduler)) {
// Use AdaptableJobFactory as default for a local Scheduler, unless when
// explicitly given a null value through the "jobFactory" bean property.
this.jobFactory = new AdaptableJobFactory();
}
if (this.jobFactory != null) {
if (this.jobFactory instanceof SchedulerContextAware) {
((SchedulerContextAware) this.jobFactory).setSchedulerContext(this.scheduler.getContext());
}
this.scheduler.setJobFactory(this.jobFactory);
}
}
finally {
if (this.resourceLoader != null) {
configTimeResourceLoaderHolder.set(null);
}
if (this.taskExecutor != null) {
configTimeTaskExecutorHolder.set(null);
}
if (this.dataSource != null) {
configTimeDataSourceHolder.set(null);
}
if (this.nonTransactionalDataSource != null) {
configTimeNonTransactionalDataSourceHolder.set(null);
}
}
registerListeners();
registerJobsAndTriggers();
// Start Scheduler immediately, if demanded.
if (this.autoStartup) {
startScheduler(this.scheduler, this.startupDelay);
}
}
/**
* Load and/or apply Quartz properties to the given SchedulerFactory.
* @param schedulerFactory the SchedulerFactory to initialize
*/
private void initSchedulerFactory(SchedulerFactory schedulerFactory)
throws SchedulerException, IOException {
if (!(schedulerFactory instanceof StdSchedulerFactory)) {
if (this.configLocation != null || this.quartzProperties != null ||
this.taskExecutor != null || this.dataSource != null) {
throw new IllegalArgumentException(
"StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);
}
// Otherwise assume that no initialization is necessary...
return;
}
Properties mergedProps = new Properties();
if (this.resourceLoader != null) {
mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS,
ResourceLoaderClassLoadHelper.class.getName());
}
if (this.taskExecutor != null) {
mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS,
LocalTaskExecutorThreadPool.class.getName());
}
else {
// Set necessary default properties here, as Quartz will not apply
// its default configuration when explicitly given properties.
mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());
mergedProps.setProperty(PROP_THREAD_COUNT, Integer.toString(DEFAULT_THREAD_COUNT));
}
if (this.configLocation != null) {
if (logger.isInfoEnabled()) {
logger.info("Loading Quartz config from [" + this.configLocation + "]");
}
PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation);
}
CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps);
if (this.dataSource != null) {
mergedProps.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName());
}
// Make sure to set the scheduler name as configured in the Spring configuration.
if (this.schedulerName != null) {
mergedProps.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName);
}
((StdSchedulerFactory) schedulerFactory).initialize(mergedProps);
}
/**
* Create the Scheduler instance for the given factory and scheduler name.
* Called by {@link #afterPropertiesSet}.
* <p>The default implementation invokes SchedulerFactory's <code>getScheduler</code>
* method. Can be overridden for custom Scheduler creation.
* @param schedulerFactory the factory to create the Scheduler with
* @param schedulerName the name of the scheduler to create
* @return the Scheduler instance
* @throws SchedulerException if thrown by Quartz methods
* @see #afterPropertiesSet
* @see org.quartz.SchedulerFactory#getScheduler
*/
protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName)
throws SchedulerException {
// Override thread context ClassLoader to work around naive Quartz ClassLoadHelper loading.
Thread currentThread = Thread.currentThread();
ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
boolean overrideClassLoader = (this.resourceLoader != null &&
!this.resourceLoader.getClassLoader().equals(threadContextClassLoader));
if (overrideClassLoader) {
currentThread.setContextClassLoader(this.resourceLoader.getClassLoader());
}
try {
SchedulerRepository repository = SchedulerRepository.getInstance();
synchronized (repository) {
Scheduler existingScheduler = (schedulerName != null ? repository.lookup(schedulerName) : null);
Scheduler newScheduler = schedulerFactory.getScheduler();
if (newScheduler == existingScheduler) {
throw new IllegalStateException("Active Scheduler of name '" + schedulerName + "' already registered " +
"in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!");
}
if (!this.exposeSchedulerInRepository) {
// Need to remove it in this case, since Quartz shares the Scheduler instance by default!
SchedulerRepository.getInstance().remove(newScheduler.getSchedulerName());
}
return newScheduler;
}
}
finally {
if (overrideClassLoader) {
// Reset original thread context ClassLoader.
currentThread.setContextClassLoader(threadContextClassLoader);
}
}
}
/**
* Expose the specified context attributes and/or the current
* ApplicationContext in the Quartz SchedulerContext.
*/
private void populateSchedulerContext() throws SchedulerException {
// Put specified objects into Scheduler context.
if (this.schedulerContextMap != null) {
this.scheduler.getContext().putAll(this.schedulerContextMap);
}
// Register ApplicationContext in Scheduler context.
if (this.applicationContextSchedulerContextKey != null) {
if (this.applicationContext == null) {
throw new IllegalStateException(
"SchedulerFactoryBean needs to be set up in an ApplicationContext " +
"to be able to handle an 'applicationContextSchedulerContextKey'");
}
this.scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext);
}
}
/**
* Start the Quartz Scheduler, respecting the "startupDelay" setting.
* @param scheduler the Scheduler to start
* @param startupDelay the number of seconds to wait before starting
* the Scheduler asynchronously
*/
protected void startScheduler(final Scheduler scheduler, final int startupDelay) throws SchedulerException {
if (startupDelay <= 0) {
logger.info("Starting Quartz Scheduler now");
scheduler.start();
}
else {
if (logger.isInfoEnabled()) {
logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() +
"] in " + startupDelay + " seconds");
}
Thread schedulerThread = new Thread() {
public void run() {
try {
Thread.sleep(startupDelay * 1000);
}
catch (InterruptedException ex) {
// simply proceed
}
if (logger.isInfoEnabled()) {
logger.info("Starting Quartz Scheduler now, after delay of " + startupDelay + " seconds");
}
try {
scheduler.start();
}
catch (SchedulerException ex) {
throw new SchedulingException("Could not start Quartz Scheduler after delay", ex);
}
}
};
schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]");
schedulerThread.start();
}
}
//---------------------------------------------------------------------
// Implementation of FactoryBean interface
//---------------------------------------------------------------------
public Scheduler getScheduler() {
return this.scheduler;
}
public Object getObject() {
return this.scheduler;
}
public Class getObjectType() {
return (this.scheduler != null) ? this.scheduler.getClass() : Scheduler.class;
}
public boolean isSingleton() {
return true;
}
//---------------------------------------------------------------------
// Implementation of Lifecycle interface
//---------------------------------------------------------------------
public void start() throws SchedulingException {
if (this.scheduler != null) {
try {
this.scheduler.start();
}
catch (SchedulerException ex) {
throw new SchedulingException("Could not start Quartz Scheduler", ex);
}
}
}
public void stop() throws SchedulingException {
if (this.scheduler != null) {
try {
this.scheduler.standby();
}
catch (SchedulerException ex) {
throw new SchedulingException("Could not stop Quartz Scheduler", ex);
}
}
}
public boolean isRunning() throws SchedulingException {
if (this.scheduler != null) {
try {
return !this.scheduler.isInStandbyMode();
}
catch (SchedulerException ex) {
return false;
}
}
return false;
}
//---------------------------------------------------------------------
// Implementation of DisposableBean interface
//---------------------------------------------------------------------
/**
* Shut down the Quartz scheduler on bean factory shutdown,
* stopping all scheduled jobs.
*/
public void destroy() throws SchedulerException {
logger.info("Shutting down Quartz Scheduler");
this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown);
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import org.quartz.SchedulerConfigException;
import org.quartz.simpl.SimpleThreadPool;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.SchedulingException;
import org.springframework.scheduling.SchedulingTaskExecutor;
import org.springframework.util.Assert;
/**
* Subclass of Quartz's SimpleThreadPool that implements Spring's
* TaskExecutor interface and listens to Spring lifecycle callbacks.
*
* <p>Can be used as a thread-pooling TaskExecutor backend, in particular
* on JDK <= 1.5 (where the JDK ThreadPoolExecutor isn't available yet).
* Can be shared between a Quartz Scheduler (specified as "taskExecutor")
* and other TaskExecutor users, or even used completely independent of
* a Quartz Scheduler (as plain TaskExecutor backend).
*
* @author Juergen Hoeller
* @since 2.0
* @see org.quartz.simpl.SimpleThreadPool
* @see org.springframework.core.task.TaskExecutor
* @see SchedulerFactoryBean#setTaskExecutor
*/
public class SimpleThreadPoolTaskExecutor extends SimpleThreadPool
implements SchedulingTaskExecutor, InitializingBean, DisposableBean {
private boolean waitForJobsToCompleteOnShutdown = false;
/**
* Set whether to wait for running jobs to complete on shutdown.
* Default is "false".
* @see org.quartz.simpl.SimpleThreadPool#shutdown(boolean)
*/
public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
}
public void afterPropertiesSet() throws SchedulerConfigException {
initialize();
}
public void execute(Runnable task) {
Assert.notNull(task, "Runnable must not be null");
if (!runInThread(task)) {
throw new SchedulingException("Quartz SimpleThreadPool already shut down");
}
}
/**
* This task executor prefers short-lived work units.
*/
public boolean prefersShortLivedTasks() {
return true;
}
public void destroy() {
shutdown(this.waitForJobsToCompleteOnShutdown);
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SimpleTrigger;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Constants;
/**
* Convenience subclass of Quartz's {@link org.quartz.SimpleTrigger}
* class, making bean-style usage easier.
*
* <p>SimpleTrigger itself is already a JavaBean but lacks sensible defaults.
* This class uses the Spring bean name as job name, the Quartz default group
* ("DEFAULT") as job group, the current time as start time, and indefinite
* repetition, if not specified.
*
* <p>This class will also register the trigger with the job name and group of
* a given {@link org.quartz.JobDetail}. This allows {@link SchedulerFactoryBean}
* to automatically register a trigger for the corresponding JobDetail,
* instead of registering the JobDetail separately.
*
* <p><b>NOTE:</b> This convenience subclass does not work with trigger
* persistence in Quartz 1.6, due to a change in Quartz's trigger handling.
* Use Quartz 1.5 if you rely on trigger persistence based on this class,
* or the standard Quartz {@link org.quartz.SimpleTrigger} class instead.
*
* @author Juergen Hoeller
* @since 18.02.2004
* @see #setName
* @see #setGroup
* @see #setStartTime
* @see #setJobName
* @see #setJobGroup
* @see #setJobDetail
* @see SchedulerFactoryBean#setTriggers
* @see SchedulerFactoryBean#setJobDetails
* @see CronTriggerBean
*/
public class SimpleTriggerBean extends SimpleTrigger
implements JobDetailAwareTrigger, BeanNameAware, InitializingBean {
/** Constants for the SimpleTrigger class */
private static final Constants constants = new Constants(SimpleTrigger.class);
private long startDelay = 0;
private JobDetail jobDetail;
private String beanName;
public SimpleTriggerBean() {
setRepeatCount(REPEAT_INDEFINITELY);
}
/**
* Register objects in the JobDataMap via a given Map.
* <p>These objects will be available to this Trigger only,
* in contrast to objects in the JobDetail's data map.
* @param jobDataAsMap Map with String keys and any objects as values
* (for example Spring-managed beans)
* @see JobDetailBean#setJobDataAsMap
*/
public void setJobDataAsMap(Map jobDataAsMap) {
getJobDataMap().putAll(jobDataAsMap);
}
/**
* Set the misfire instruction via the name of the corresponding
* constant in the {@link org.quartz.SimpleTrigger} class.
* Default is <code>MISFIRE_INSTRUCTION_SMART_POLICY</code>.
* @see org.quartz.SimpleTrigger#MISFIRE_INSTRUCTION_FIRE_NOW
* @see org.quartz.SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
* @see org.quartz.SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
* @see org.quartz.SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
* @see org.quartz.SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
* @see org.quartz.Trigger#MISFIRE_INSTRUCTION_SMART_POLICY
*/
public void setMisfireInstructionName(String constantName) {
setMisfireInstruction(constants.asNumber(constantName).intValue());
}
/**
* Set a list of TriggerListener names for this job, referring to
* non-global TriggerListeners registered with the Scheduler.
* <p>A TriggerListener name always refers to the name returned
* by the TriggerListener implementation.
* @see SchedulerFactoryBean#setTriggerListeners
* @see org.quartz.TriggerListener#getName
*/
public void setTriggerListenerNames(String[] names) {
for (int i = 0; i < names.length; i++) {
addTriggerListener(names[i]);
}
}
/**
* Set the delay before starting the job for the first time.
* The given number of milliseconds will be added to the current
* time to calculate the start time. Default is 0.
* <p>This delay will just be applied if no custom start time was
* specified. However, in typical usage within a Spring context,
* the start time will be the container startup time anyway.
* Specifying a relative delay is appropriate in that case.
* @see #setStartTime
*/
public void setStartDelay(long startDelay) {
this.startDelay = startDelay;
}
/**
* Set the JobDetail that this trigger should be associated with.
* <p>This is typically used with a bean reference if the JobDetail
* is a Spring-managed bean. Alternatively, the trigger can also
* be associated with a job by name and group.
* @see #setJobName
* @see #setJobGroup
*/
public void setJobDetail(JobDetail jobDetail) {
this.jobDetail = jobDetail;
}
public JobDetail getJobDetail() {
return this.jobDetail;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public void afterPropertiesSet() throws ParseException {
if (getName() == null) {
setName(this.beanName);
}
if (getGroup() == null) {
setGroup(Scheduler.DEFAULT_GROUP);
}
if (getStartTime() == null) {
setStartTime(new Date(System.currentTimeMillis() + this.startDelay));
}
if (this.jobDetail != null) {
setJobName(this.jobDetail.getName());
setJobGroup(this.jobDetail.getGroup());
}
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import org.quartz.SchedulerContext;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.BeanUtils;
/**
* Subclass of {@link AdaptableJobFactory} that also supports Spring-style
* dependency injection on bean properties. This is essentially the direct
* equivalent of Spring's {@link QuartzJobBean} in the shape of a
* Quartz 1.5 {@link org.quartz.spi.JobFactory}.
*
* <p>Applies scheduler context, job data map and trigger data map entries
* as bean property values. If no matching bean property is found, the entry
* is by default simply ignored. This is analogous to QuartzJobBean's behavior.
*
* @author Juergen Hoeller
* @since 2.0
* @see SchedulerFactoryBean#setJobFactory
* @see QuartzJobBean
*/
public class SpringBeanJobFactory extends AdaptableJobFactory implements SchedulerContextAware {
private String[] ignoredUnknownProperties;
private SchedulerContext schedulerContext;
/**
* Specify the unknown properties (not found in the bean) that should be ignored.
* <p>Default is <code>null</code>, indicating that all unknown properties
* should be ignored. Specify an empty array to throw an exception in case
* of any unknown properties, or a list of property names that should be
* ignored if there is no corresponding property found on the particular
* job class (all other unknown properties will still trigger an exception).
*/
public void setIgnoredUnknownProperties(String[] ignoredUnknownProperties) {
this.ignoredUnknownProperties = ignoredUnknownProperties;
}
public void setSchedulerContext(SchedulerContext schedulerContext) {
this.schedulerContext = schedulerContext;
}
/**
* Create the job instance, populating it with property values taken
* from the scheduler context, job data map and trigger data map.
*/
protected Object createJobInstance(TriggerFiredBundle bundle) {
Object job = BeanUtils.instantiateClass(bundle.getJobDetail().getJobClass());
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
if (isEligibleForPropertyPopulation(bw.getWrappedInstance())) {
MutablePropertyValues pvs = new MutablePropertyValues();
if (this.schedulerContext != null) {
pvs.addPropertyValues(this.schedulerContext);
}
pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
if (this.ignoredUnknownProperties != null) {
for (int i = 0; i < this.ignoredUnknownProperties.length; i++) {
String propName = this.ignoredUnknownProperties[i];
if (pvs.contains(propName) && !bw.isWritableProperty(propName)) {
pvs.removePropertyValue(propName);
}
}
bw.setPropertyValues(pvs);
}
else {
bw.setPropertyValues(pvs, true);
}
}
return job;
}
/**
* Return whether the given job object is eligible for having
* its bean properties populated.
* <p>The default implementation ignores {@link QuartzJobBean} instances,
* which will inject bean properties themselves.
* @param jobObject the job object to introspect
* @see QuartzJobBean
*/
protected boolean isEligibleForPropertyPopulation(Object jobObject) {
return (!(jobObject instanceof QuartzJobBean));
}
}

View File

@ -0,0 +1,11 @@
<html>
<body>
Support classes for the open source scheduler
<a href="http://www.opensymphony.com/quartz">Quartz</a>,
allowing to set up Quartz Schedulers, JobDetails and
Triggers as beans in a Spring context. Also provides
convenience classes for implementing Quartz Jobs.
</body>
</html>

View File

@ -0,0 +1,422 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.freemarker;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.SimpleHash;
import freemarker.template.TemplateException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.CollectionUtils;
/**
* Factory that configures a FreeMarker Configuration. Can be used standalone, but
* typically you will either use FreeMarkerConfigurationFactoryBean for preparing a
* Configuration as bean reference, or FreeMarkerConfigurer for web views.
*
* <p>The optional "configLocation" property sets the location of a FreeMarker
* properties file, within the current application. FreeMarker properties can be
* overridden via "freemarkerSettings". All of these properties will be set by
* calling FreeMarker's <code>Configuration.setSettings()</code> method and are
* subject to constraints set by FreeMarker.
*
* <p>The "freemarkerVariables" property can be used to specify a Map of
* shared variables that will be applied to the Configuration via the
* <code>setAllSharedVariables()</code> method. Like <code>setSettings()</code>,
* these entries are subject to FreeMarker constraints.
*
* <p>The simplest way to use this class is to specify a "templateLoaderPath";
* FreeMarker does not need any further configuration then.
*
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher.
*
* @author Darren Davison
* @author Juergen Hoeller
* @since 03.03.2004
* @see #setConfigLocation
* @see #setFreemarkerSettings
* @see #setFreemarkerVariables
* @see #setTemplateLoaderPath
* @see #createConfiguration
* @see FreeMarkerConfigurationFactoryBean
* @see org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer
* @see freemarker.template.Configuration
*/
public class FreeMarkerConfigurationFactory {
protected final Log logger = LogFactory.getLog(getClass());
private Resource configLocation;
private Properties freemarkerSettings;
private Map freemarkerVariables;
private String defaultEncoding;
private final List templateLoaders = new ArrayList();
private List preTemplateLoaders;
private List postTemplateLoaders;
private String[] templateLoaderPaths;
private ResourceLoader resourceLoader = new DefaultResourceLoader();
private boolean preferFileSystemAccess = true;
/**
* Set the location of the FreeMarker config file.
* Alternatively, you can specify all setting locally.
* @see #setFreemarkerSettings
* @see #setTemplateLoaderPath
*/
public void setConfigLocation(Resource resource) {
configLocation = resource;
}
/**
* Set properties that contain well-known FreeMarker keys which will be
* passed to FreeMarker's <code>Configuration.setSettings</code> method.
* @see freemarker.template.Configuration#setSettings
*/
public void setFreemarkerSettings(Properties settings) {
this.freemarkerSettings = settings;
}
/**
* Set a Map that contains well-known FreeMarker objects which will be passed
* to FreeMarker's <code>Configuration.setAllSharedVariables()</code> method.
* @see freemarker.template.Configuration#setAllSharedVariables
*/
public void setFreemarkerVariables(Map variables) {
this.freemarkerVariables = variables;
}
/**
* Set the default encoding for the FreeMarker configuration.
* If not specified, FreeMarker will use the platform file encoding.
* <p>Used for template rendering unless there is an explicit encoding specified
* for the rendering process (for example, on Spring's FreeMarkerView).
* @see freemarker.template.Configuration#setDefaultEncoding
* @see org.springframework.web.servlet.view.freemarker.FreeMarkerView#setEncoding
*/
public void setDefaultEncoding(String defaultEncoding) {
this.defaultEncoding = defaultEncoding;
}
/**
* Set a List of <code>TemplateLoader<code>s that will be used to search
* for templates. For example, one or more custom loaders such as database
* loaders could be configured and injected here.
* @deprecated as of Spring 2.0.1, in favor of the "preTemplateLoaders"
* and "postTemplateLoaders" properties
* @see #setPreTemplateLoaders
* @see #setPostTemplateLoaders
*/
public void setTemplateLoaders(TemplateLoader[] templateLoaders) {
if (templateLoaders != null) {
this.templateLoaders.addAll(Arrays.asList(templateLoaders));
}
}
/**
* Set a List of <code>TemplateLoader<code>s that will be used to search
* for templates. For example, one or more custom loaders such as database
* loaders could be configured and injected here.
* <p>The {@link TemplateLoader TemplateLoaders} specified here will be
* registered <i>before</i> the default template loaders that this factory
* registers (such as loaders for specified "templateLoaderPaths" or any
* loaders registered in {@link #postProcessTemplateLoaders}).
* @see #setTemplateLoaderPaths
* @see #postProcessTemplateLoaders
*/
public void setPreTemplateLoaders(TemplateLoader[] preTemplateLoaders) {
this.preTemplateLoaders = Arrays.asList(preTemplateLoaders);
}
/**
* Set a List of <code>TemplateLoader<code>s that will be used to search
* for templates. For example, one or more custom loaders such as database
* loaders can be configured.
* <p>The {@link TemplateLoader TemplateLoaders} specified here will be
* registered <i>after</i> the default template loaders that this factory
* registers (such as loaders for specified "templateLoaderPaths" or any
* loaders registered in {@link #postProcessTemplateLoaders}).
* @see #setTemplateLoaderPaths
* @see #postProcessTemplateLoaders
*/
public void setPostTemplateLoaders(TemplateLoader[] postTemplateLoaders) {
this.postTemplateLoaders = Arrays.asList(postTemplateLoaders);
}
/**
* Set the Freemarker template loader path via a Spring resource location.
* See the "templateLoaderPaths" property for details on path handling.
* @see #setTemplateLoaderPaths
*/
public void setTemplateLoaderPath(String templateLoaderPath) {
this.templateLoaderPaths = new String[] {templateLoaderPath};
}
/**
* Set multiple Freemarker template loader paths via Spring resource locations.
* <p>When populated via a String, standard URLs like "file:" and "classpath:"
* pseudo URLs are supported, as understood by ResourceEditor. Allows for
* relative paths when running in an ApplicationContext.
* <p>Will define a path for the default FreeMarker template loader.
* If a specified resource cannot be resolved to a <code>java.io.File</code>,
* a generic SpringTemplateLoader will be used, without modification detection.
* <p>To enforce the use of SpringTemplateLoader, i.e. to not resolve a path
* as file system resource in any case, turn off the "preferFileSystemAccess"
* flag. See the latter's javadoc for details.
* <p>If you wish to specify your own list of TemplateLoaders, do not set this
* property and instead use <code>setTemplateLoaders(List templateLoaders)</code>
* @see org.springframework.core.io.ResourceEditor
* @see org.springframework.context.ApplicationContext#getResource
* @see freemarker.template.Configuration#setDirectoryForTemplateLoading
* @see SpringTemplateLoader
* @see #setTemplateLoaders
*/
public void setTemplateLoaderPaths(String[] templateLoaderPaths) {
this.templateLoaderPaths = templateLoaderPaths;
}
/**
* Set the Spring ResourceLoader to use for loading FreeMarker template files.
* The default is DefaultResourceLoader. Will get overridden by the
* ApplicationContext if running in a context.
* @see org.springframework.core.io.DefaultResourceLoader
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* Return the Spring ResourceLoader to use for loading FreeMarker template files.
*/
protected ResourceLoader getResourceLoader() {
return resourceLoader;
}
/**
* Set whether to prefer file system access for template loading.
* File system access enables hot detection of template changes.
* <p>If this is enabled, FreeMarkerConfigurationFactory will try to resolve
* the specified "templateLoaderPath" as file system resource (which will work
* for expanded class path resources and ServletContext resources too).
* <p>Default is "true". Turn this off to always load via SpringTemplateLoader
* (i.e. as stream, without hot detection of template changes), which might
* be necessary if some of your templates reside in an expanded classes
* directory while others reside in jar files.
* @see #setTemplateLoaderPath
*/
public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {
this.preferFileSystemAccess = preferFileSystemAccess;
}
/**
* Return whether to prefer file system access for template loading.
*/
protected boolean isPreferFileSystemAccess() {
return preferFileSystemAccess;
}
/**
* Prepare the FreeMarker Configuration and return it.
* @return the FreeMarker Configuration object
* @throws IOException if the config file wasn't found
* @throws TemplateException on FreeMarker initialization failure
*/
public Configuration createConfiguration() throws IOException, TemplateException {
Configuration config = newConfiguration();
Properties props = new Properties();
// Load config file if specified.
if (this.configLocation != null) {
if (logger.isInfoEnabled()) {
logger.info("Loading FreeMarker configuration from " + this.configLocation);
}
PropertiesLoaderUtils.fillProperties(props, this.configLocation);
}
// Merge local properties if specified.
if (this.freemarkerSettings != null) {
props.putAll(this.freemarkerSettings);
}
// FreeMarker will only accept known keys in its setSettings and
// setAllSharedVariables methods.
if (!props.isEmpty()) {
config.setSettings(props);
}
if (!CollectionUtils.isEmpty(this.freemarkerVariables)) {
config.setAllSharedVariables(new SimpleHash(this.freemarkerVariables));
}
if (this.defaultEncoding != null) {
config.setDefaultEncoding(this.defaultEncoding);
}
// Register template loaders that are supposed to kick in early.
if (this.preTemplateLoaders != null) {
this.templateLoaders.addAll(this.preTemplateLoaders);
}
// Register default template loaders.
if (this.templateLoaderPaths != null) {
for (int i = 0; i < this.templateLoaderPaths.length; i++) {
this.templateLoaders.add(getTemplateLoaderForPath(this.templateLoaderPaths[i]));
}
}
postProcessTemplateLoaders(this.templateLoaders);
// Register template loaders that are supposed to kick in late.
if (this.postTemplateLoaders != null) {
this.templateLoaders.addAll(this.postTemplateLoaders);
}
TemplateLoader loader = getAggregateTemplateLoader(this.templateLoaders);
if (loader != null) {
config.setTemplateLoader(loader);
}
postProcessConfiguration(config);
return config;
}
/**
* Return a new Configuration object. Subclasses can override this for
* custom initialization, or for using a mock object for testing.
* <p>Called by <code>createConfiguration()</code>.
* @return the Configuration object
* @throws IOException if a config file wasn't found
* @throws TemplateException on FreeMarker initialization failure
* @see #createConfiguration()
*/
protected Configuration newConfiguration() throws IOException, TemplateException {
return new Configuration();
}
/**
* Determine a FreeMarker TemplateLoader for the given path.
* <p>Default implementation creates either a FileTemplateLoader or
* a SpringTemplateLoader.
* @param templateLoaderPath the path to load templates from
* @return an appropriate TemplateLoader
* @see freemarker.cache.FileTemplateLoader
* @see SpringTemplateLoader
*/
protected TemplateLoader getTemplateLoaderForPath(String templateLoaderPath) {
if (isPreferFileSystemAccess()) {
// Try to load via the file system, fall back to SpringTemplateLoader
// (for hot detection of template changes, if possible).
try {
Resource path = getResourceLoader().getResource(templateLoaderPath);
File file = path.getFile(); // will fail if not resolvable in the file system
if (logger.isDebugEnabled()) {
logger.debug(
"Template loader path [" + path + "] resolved to file path [" + file.getAbsolutePath() + "]");
}
return new FileTemplateLoader(file);
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot resolve template loader path [" + templateLoaderPath +
"] to [java.io.File]: using SpringTemplateLoader as fallback", ex);
}
return new SpringTemplateLoader(getResourceLoader(), templateLoaderPath);
}
}
else {
// Always load via SpringTemplateLoader (without hot detection of template changes).
logger.debug("File system access not preferred: using SpringTemplateLoader");
return new SpringTemplateLoader(getResourceLoader(), templateLoaderPath);
}
}
/**
* To be overridden by subclasses that want to to register custom
* TemplateLoader instances after this factory created its default
* template loaders.
* <p>Called by <code>createConfiguration()</code>. Note that specified
* "postTemplateLoaders" will be registered <i>after</i> any loaders
* registered by this callback; as a consequence, they are are <i>not</i>
* included in the given List.
* @param templateLoaders the current List of TemplateLoader instances,
* to be modified by a subclass
* @see #createConfiguration()
* @see #setPostTemplateLoaders
*/
protected void postProcessTemplateLoaders(List templateLoaders) {
}
/**
* Return a TemplateLoader based on the given TemplateLoader list.
* If more than one TemplateLoader has been registered, a FreeMarker
* MultiTemplateLoader needs to be created.
* @param templateLoaders the final List of TemplateLoader instances
* @return the aggregate TemplateLoader
*/
protected TemplateLoader getAggregateTemplateLoader(List templateLoaders) {
int loaderCount = templateLoaders.size();
switch (loaderCount) {
case 0:
logger.info("No FreeMarker TemplateLoaders specified");
return null;
case 1:
return (TemplateLoader) templateLoaders.get(0);
default:
TemplateLoader[] loaders = (TemplateLoader[]) templateLoaders.toArray(new TemplateLoader[loaderCount]);
return new MultiTemplateLoader(loaders);
}
}
/**
* To be overridden by subclasses that want to to perform custom
* post-processing of the Configuration object after this factory
* performed its default initialization.
* <p>Called by <code>createConfiguration()</code>.
* @param config the current Configuration object
* @throws IOException if a config file wasn't found
* @throws TemplateException on FreeMarker initialization failure
* @see #createConfiguration()
*/
protected void postProcessConfiguration(Configuration config) throws IOException, TemplateException {
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.freemarker;
import java.io.IOException;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
/**
* Factory bean that creates a FreeMarker Configuration and provides it as
* bean reference. This bean is intended for any kind of usage of FreeMarker
* in application code, e.g. for generating email content. For web views,
* FreeMarkerConfigurer is used to set up a FreeMarkerConfigurationFactory.
*
* The simplest way to use this class is to specify just a "templateLoaderPath";
* you do not need any further configuration then. For example, in a web
* application context:
*
* <pre class="code"> &lt;bean id="freemarkerConfiguration" class="org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean"&gt;
* &lt;property name="templateLoaderPath" value="/WEB-INF/freemarker/"/&gt;
* &lt;/bean&gt;</pre>
* See the base class FreeMarkerConfigurationFactory for configuration details.
*
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher.
*
* @author Darren Davison
* @since 03.03.2004
* @see #setConfigLocation
* @see #setFreemarkerSettings
* @see #setTemplateLoaderPath
* @see org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer
*/
public class FreeMarkerConfigurationFactoryBean extends FreeMarkerConfigurationFactory
implements FactoryBean, InitializingBean, ResourceLoaderAware {
private Configuration configuration;
public void afterPropertiesSet() throws IOException, TemplateException {
this.configuration = createConfiguration();
}
public Object getObject() {
return this.configuration;
}
public Class getObjectType() {
return Configuration.class;
}
public boolean isSingleton() {
return true;
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.freemarker;
import java.io.IOException;
import java.io.StringWriter;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
* Utility class for working with FreeMarker.
* Provides convenience methods to process a FreeMarker template with a model.
*
* @author Juergen Hoeller
* @since 14.03.2004
*/
public abstract class FreeMarkerTemplateUtils {
/**
* Process the specified FreeMarker template with the given model and write
* the result to the given Writer.
* <p>When using this method to prepare a text for a mail to be sent with Spring's
* mail support, consider wrapping IO/TemplateException in MailPreparationException.
* @param model the model object, typically a Map that contains model names
* as keys and model objects as values
* @return the result as String
* @throws IOException if the template wasn't found or couldn't be read
* @throws freemarker.template.TemplateException if rendering failed
* @see org.springframework.mail.MailPreparationException
*/
public static String processTemplateIntoString(Template template, Object model)
throws IOException, TemplateException {
StringWriter result = new StringWriter();
template.process(model, result);
return result.toString();
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.freemarker;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import freemarker.cache.TemplateLoader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
/**
* FreeMarker TemplateLoader adapter that loads via a Spring ResourceLoader.
* Used by FreeMarkerConfigurationFactory for any resource loader path that
* cannot be resolved to a java.io.File.
*
* <p>Note that this loader does not allow for modification detection:
* Use FreeMarker's default TemplateLoader for java.io.File resources.
*
* @author Juergen Hoeller
* @since 14.03.2004
* @see FreeMarkerConfigurationFactory#setTemplateLoaderPath
* @see freemarker.template.Configuration#setDirectoryForTemplateLoading
*/
public class SpringTemplateLoader implements TemplateLoader {
protected final Log logger = LogFactory.getLog(getClass());
private final ResourceLoader resourceLoader;
private final String templateLoaderPath;
/**
* Create a new SpringTemplateLoader.
* @param resourceLoader the Spring ResourceLoader to use
* @param templateLoaderPath the template loader path to use
*/
public SpringTemplateLoader(ResourceLoader resourceLoader, String templateLoaderPath) {
this.resourceLoader = resourceLoader;
if (!templateLoaderPath.endsWith("/")) {
templateLoaderPath += "/";
}
this.templateLoaderPath = templateLoaderPath;
if (logger.isInfoEnabled()) {
logger.info("SpringTemplateLoader for FreeMarker: using resource loader [" + this.resourceLoader +
"] and template loader path [" + this.templateLoaderPath + "]");
}
}
public Object findTemplateSource(String name) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for FreeMarker template with name [" + name + "]");
}
Resource resource = this.resourceLoader.getResource(this.templateLoaderPath + name);
return (resource.exists() ? resource : null);
}
public Reader getReader(Object templateSource, String encoding) throws IOException {
Resource resource = (Resource) templateSource;
try {
return new InputStreamReader(resource.getInputStream(), encoding);
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find FreeMarker template: " + resource);
}
throw ex;
}
}
public long getLastModified(Object templateSource) {
return -1;
}
public void closeTemplateSource(Object templateSource) throws IOException {
}
}

View File

@ -0,0 +1,9 @@
<html>
<body>
Support classes for setting up
<a href="http://www.freemarker.org">FreeMarker</a>
within a Spring application context.
</body>
</html>

View File

@ -0,0 +1,278 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.jasperreports;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Collection;
import java.util.Map;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporter;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanArrayDataSource;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.export.JRCsvExporter;
import net.sf.jasperreports.engine.export.JRHtmlExporter;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.export.JRXlsExporter;
/**
* Utility methods for working with JasperReports. Provides a set of convenience
* methods for generating reports in a CSV, HTML, PDF and XLS formats.
*
* @author Rob Harrop
* @author Juergen Hoeller
* @since 1.1.3
*/
public abstract class JasperReportsUtils {
/**
* Convert the given report data value to a <code>JRDataSource</code>.
* <p>In the default implementation, a <code>JRDataSource</code>,
* <code>java.util.Collection</code> or object array is detected.
* The latter are converted to <code>JRBeanCollectionDataSource</code>
* or <code>JRBeanArrayDataSource</code>, respectively.
* @param value the report data value to convert
* @return the JRDataSource (never <code>null</code>)
* @throws IllegalArgumentException if the value could not be converted
* @see net.sf.jasperreports.engine.JRDataSource
* @see net.sf.jasperreports.engine.data.JRBeanCollectionDataSource
* @see net.sf.jasperreports.engine.data.JRBeanArrayDataSource
*/
public static JRDataSource convertReportData(Object value) throws IllegalArgumentException {
if (value instanceof JRDataSource) {
return (JRDataSource) value;
}
else if (value instanceof Collection) {
return new JRBeanCollectionDataSource((Collection) value);
}
else if (value instanceof Object[]) {
return new JRBeanArrayDataSource((Object[]) value);
}
else {
throw new IllegalArgumentException("Value [" + value + "] cannot be converted to a JRDataSource");
}
}
/**
* Render the supplied <code>JasperPrint</code> instance using the
* supplied <code>JRAbstractExporter</code> instance and write the results
* to the supplied <code>Writer</code>.
* <p>Make sure that the <code>JRAbstractExporter</code> implementation
* you supply is capable of writing to a <code>Writer</code>.
* @param exporter the <code>JRAbstractExporter</code> to use to render the report
* @param print the <code>JasperPrint</code> instance to render
* @param writer the <code>Writer</code> to write the result to
* @throws JRException if rendering failed
*/
public static void render(JRExporter exporter, JasperPrint print, Writer writer)
throws JRException {
exporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
exporter.setParameter(JRExporterParameter.OUTPUT_WRITER, writer);
exporter.exportReport();
}
/**
* Render the supplied <code>JasperPrint</code> instance using the
* supplied <code>JRAbstractExporter</code> instance and write the results
* to the supplied <code>OutputStream</code>.
* <p>Make sure that the <code>JRAbstractExporter</code> implementation you
* supply is capable of writing to a <code>OutputStream</code>.
* @param exporter the <code>JRAbstractExporter</code> to use to render the report
* @param print the <code>JasperPrint</code> instance to render
* @param outputStream the <code>OutputStream</code> to write the result to
* @throws JRException if rendering failed
*/
public static void render(JRExporter exporter, JasperPrint print, OutputStream outputStream)
throws JRException {
exporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, outputStream);
exporter.exportReport();
}
/**
* Render a report in CSV format using the supplied report data.
* Writes the results to the supplied <code>Writer</code>.
* @param report the <code>JasperReport</code> instance to render
* @param parameters the parameters to use for rendering
* @param writer the <code>Writer</code> to write the rendered report to
* @param reportData a <code>JRDataSource</code>, <code>java.util.Collection</code>
* or object array (converted accordingly), representing the report data to read
* fields from
* @throws JRException if rendering failed
* @see #convertReportData
*/
public static void renderAsCsv(JasperReport report, Map parameters, Object reportData, Writer writer)
throws JRException {
JasperPrint print = JasperFillManager.fillReport(report, parameters, convertReportData(reportData));
render(new JRCsvExporter(), print, writer);
}
/**
* Render a report in CSV format using the supplied report data.
* Writes the results to the supplied <code>Writer</code>.
* @param report the <code>JasperReport</code> instance to render
* @param parameters the parameters to use for rendering
* @param writer the <code>Writer</code> to write the rendered report to
* @param reportData a <code>JRDataSource</code>, <code>java.util.Collection</code>
* or object array (converted accordingly), representing the report data to read
* fields from
* @param exporterParameters a {@link Map} of {@link JRExporterParameter exporter parameters}
* @throws JRException if rendering failed
* @see #convertReportData
*/
public static void renderAsCsv(JasperReport report, Map parameters, Object reportData, Writer writer,
Map exporterParameters) throws JRException {
JasperPrint print = JasperFillManager.fillReport(report, parameters, convertReportData(reportData));
JRCsvExporter exporter = new JRCsvExporter();
exporter.setParameters(exporterParameters);
render(exporter, print, writer);
}
/**
* Render a report in HTML format using the supplied report data.
* Writes the results to the supplied <code>Writer</code>.
* @param report the <code>JasperReport</code> instance to render
* @param parameters the parameters to use for rendering
* @param writer the <code>Writer</code> to write the rendered report to
* @param reportData a <code>JRDataSource</code>, <code>java.util.Collection</code>
* or object array (converted accordingly), representing the report data to read
* fields from
* @throws JRException if rendering failed
* @see #convertReportData
*/
public static void renderAsHtml(JasperReport report, Map parameters, Object reportData, Writer writer)
throws JRException {
JasperPrint print = JasperFillManager.fillReport(report, parameters, convertReportData(reportData));
render(new JRHtmlExporter(), print, writer);
}
/**
* Render a report in HTML format using the supplied report data.
* Writes the results to the supplied <code>Writer</code>.
* @param report the <code>JasperReport</code> instance to render
* @param parameters the parameters to use for rendering
* @param writer the <code>Writer</code> to write the rendered report to
* @param reportData a <code>JRDataSource</code>, <code>java.util.Collection</code>
* or object array (converted accordingly), representing the report data to read
* fields from
* @param exporterParameters a {@link Map} of {@link JRExporterParameter exporter parameters}
* @throws JRException if rendering failed
* @see #convertReportData
*/
public static void renderAsHtml(JasperReport report, Map parameters, Object reportData, Writer writer,
Map exporterParameters) throws JRException {
JasperPrint print = JasperFillManager.fillReport(report, parameters, convertReportData(reportData));
JRHtmlExporter exporter = new JRHtmlExporter();
exporter.setParameters(exporterParameters);
render(exporter, print, writer);
}
/**
* Render a report in PDF format using the supplied report data.
* Writes the results to the supplied <code>OutputStream</code>.
* @param report the <code>JasperReport</code> instance to render
* @param parameters the parameters to use for rendering
* @param stream the <code>OutputStream</code> to write the rendered report to
* @param reportData a <code>JRDataSource</code>, <code>java.util.Collection</code>
* or object array (converted accordingly), representing the report data to read
* fields from
* @throws JRException if rendering failed
* @see #convertReportData
*/
public static void renderAsPdf(JasperReport report, Map parameters, Object reportData, OutputStream stream)
throws JRException {
JasperPrint print = JasperFillManager.fillReport(report, parameters, convertReportData(reportData));
render(new JRPdfExporter(), print, stream);
}
/**
* Render a report in PDF format using the supplied report data.
* Writes the results to the supplied <code>OutputStream</code>.
* @param report the <code>JasperReport</code> instance to render
* @param parameters the parameters to use for rendering
* @param stream the <code>OutputStream</code> to write the rendered report to
* @param reportData a <code>JRDataSource</code>, <code>java.util.Collection</code>
* or object array (converted accordingly), representing the report data to read
* fields from
* @param exporterParameters a {@link Map} of {@link JRExporterParameter exporter parameters}
* @throws JRException if rendering failed
* @see #convertReportData
*/
public static void renderAsPdf(JasperReport report, Map parameters, Object reportData, OutputStream stream,
Map exporterParameters) throws JRException {
JasperPrint print = JasperFillManager.fillReport(report, parameters, convertReportData(reportData));
JRPdfExporter exporter = new JRPdfExporter();
exporter.setParameters(exporterParameters);
render(exporter, print, stream);
}
/**
* Render a report in XLS format using the supplied report data.
* Writes the results to the supplied <code>OutputStream</code>.
* @param report the <code>JasperReport</code> instance to render
* @param parameters the parameters to use for rendering
* @param stream the <code>OutputStream</code> to write the rendered report to
* @param reportData a <code>JRDataSource</code>, <code>java.util.Collection</code>
* or object array (converted accordingly), representing the report data to read
* fields from
* @throws JRException if rendering failed
* @see #convertReportData
*/
public static void renderAsXls(JasperReport report, Map parameters, Object reportData, OutputStream stream)
throws JRException {
JasperPrint print = JasperFillManager.fillReport(report, parameters, convertReportData(reportData));
render(new JRXlsExporter(), print, stream);
}
/**
* Render a report in XLS format using the supplied report data.
* Writes the results to the supplied <code>OutputStream</code>.
* @param report the <code>JasperReport</code> instance to render
* @param parameters the parameters to use for rendering
* @param stream the <code>OutputStream</code> to write the rendered report to
* @param reportData a <code>JRDataSource</code>, <code>java.util.Collection</code>
* or object array (converted accordingly), representing the report data to read
* fields from
* @param exporterParameters a {@link Map} of {@link JRExporterParameter exporter parameters}
* @throws JRException if rendering failed
* @see #convertReportData
*/
public static void renderAsXls(JasperReport report, Map parameters, Object reportData, OutputStream stream,
Map exporterParameters) throws JRException {
JasperPrint print = JasperFillManager.fillReport(report, parameters, convertReportData(reportData));
JRXlsExporter exporter = new JRXlsExporter();
exporter.setParameters(exporterParameters);
render(exporter, print, stream);
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Support classes for
<a href="http://jasperreports.sourceforge.net">JasperReports</a>.
</body>
</html>

View File

@ -0,0 +1,57 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.velocity;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.log.LogSystem;
/**
* Velocity LogSystem implementation for Jakarta Commons Logging.
* Used by VelocityConfigurer to redirect log output.
*
* @author Juergen Hoeller
* @since 07.08.2003
* @see VelocityEngineFactoryBean
*/
public class CommonsLoggingLogSystem implements LogSystem {
private static final Log logger = LogFactory.getLog(VelocityEngine.class);
public void init(RuntimeServices runtimeServices) {
}
public void logVelocityMessage(int type, String msg) {
switch (type) {
case ERROR_ID:
logger.error(msg);
break;
case WARN_ID:
logger.warn(msg);
break;
case INFO_ID:
logger.info(msg);
break;
case DEBUG_ID:
logger.debug(msg);
break;
}
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.velocity;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.apache.commons.collections.ExtendedProperties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.resource.Resource;
import org.apache.velocity.runtime.resource.loader.ResourceLoader;
import org.springframework.util.StringUtils;
/**
* Velocity ResourceLoader adapter that loads via a Spring ResourceLoader.
* Used by VelocityEngineFactory for any resource loader path that cannot
* be resolved to a <code>java.io.File</code>.
*
* <p>Note that this loader does not allow for modification detection:
* Use Velocity's default FileResourceLoader for <code>java.io.File</code>
* resources.
*
* <p>Expects "spring.resource.loader" and "spring.resource.loader.path"
* application attributes in the Velocity runtime: the former of type
* <code>org.springframework.core.io.ResourceLoader</code>, the latter a String.
*
* @author Juergen Hoeller
* @since 14.03.2004
* @see VelocityEngineFactory#setResourceLoaderPath
* @see org.springframework.core.io.ResourceLoader
* @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
*/
public class SpringResourceLoader extends ResourceLoader {
public static final String NAME = "spring";
public static final String SPRING_RESOURCE_LOADER_CLASS = "spring.resource.loader.class";
public static final String SPRING_RESOURCE_LOADER_CACHE = "spring.resource.loader.cache";
public static final String SPRING_RESOURCE_LOADER = "spring.resource.loader";
public static final String SPRING_RESOURCE_LOADER_PATH = "spring.resource.loader.path";
protected final Log logger = LogFactory.getLog(getClass());
private org.springframework.core.io.ResourceLoader resourceLoader;
private String[] resourceLoaderPaths;
public void init(ExtendedProperties configuration) {
this.resourceLoader = (org.springframework.core.io.ResourceLoader)
this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER);
String resourceLoaderPath = (String) this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER_PATH);
if (this.resourceLoader == null) {
throw new IllegalArgumentException(
"'resourceLoader' application attribute must be present for SpringResourceLoader");
}
if (resourceLoaderPath == null) {
throw new IllegalArgumentException(
"'resourceLoaderPath' application attribute must be present for SpringResourceLoader");
}
this.resourceLoaderPaths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
for (int i = 0; i < this.resourceLoaderPaths.length; i++) {
String path = this.resourceLoaderPaths[i];
if (!path.endsWith("/")) {
this.resourceLoaderPaths[i] = path + "/";
}
}
if (logger.isInfoEnabled()) {
logger.info("SpringResourceLoader for Velocity: using resource loader [" + this.resourceLoader +
"] and resource loader paths " + Arrays.asList(this.resourceLoaderPaths));
}
}
public InputStream getResourceStream(String source) throws ResourceNotFoundException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for Velocity resource with name [" + source + "]");
}
for (int i = 0; i < this.resourceLoaderPaths.length; i++) {
org.springframework.core.io.Resource resource =
this.resourceLoader.getResource(this.resourceLoaderPaths[i] + source);
try {
return resource.getInputStream();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find Velocity resource: " + resource);
}
}
}
throw new ResourceNotFoundException(
"Could not find resource [" + source + "] in Spring resource loader path");
}
public boolean isSourceModified(Resource resource) {
return false;
}
public long getLastModified(Resource resource) {
return 0;
}
}

View File

@ -0,0 +1,376 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.velocity;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.StringUtils;
/**
* Factory that configures a VelocityEngine. Can be used standalone,
* but typically you will either use {@link VelocityEngineFactoryBean}
* for preparing a VelocityEngine as bean reference, or
* {@link org.springframework.web.servlet.view.velocity.VelocityConfigurer}
* for web views.
*
* <p>The optional "configLocation" property sets the location of the Velocity
* properties file, within the current application. Velocity properties can be
* overridden via "velocityProperties", or even completely specified locally,
* avoiding the need for an external properties file.
*
* <p>The "resourceLoaderPath" property can be used to specify the Velocity
* resource loader path via Spring's Resource abstraction, possibly relative
* to the Spring application context.
*
* <p>If "overrideLogging" is true (the default), the VelocityEngine will be
* configured to log via Commons Logging, that is, using the Spring-provided
* {@link CommonsLoggingLogSystem} as log system.
*
* <p>The simplest way to use this class is to specify a
* {@link #setResourceLoaderPath(String) "resourceLoaderPath"}; the
* VelocityEngine typically then does not need any further configuration.
*
* @author Juergen Hoeller
* @see #setConfigLocation
* @see #setVelocityProperties
* @see #setResourceLoaderPath
* @see #setOverrideLogging
* @see #createVelocityEngine
* @see CommonsLoggingLogSystem
* @see VelocityEngineFactoryBean
* @see org.springframework.web.servlet.view.velocity.VelocityConfigurer
* @see org.apache.velocity.app.VelocityEngine
*/
public class VelocityEngineFactory {
protected final Log logger = LogFactory.getLog(getClass());
private Resource configLocation;
private final Map velocityProperties = new HashMap();
private String resourceLoaderPath;
private ResourceLoader resourceLoader = new DefaultResourceLoader();
private boolean preferFileSystemAccess = true;
private boolean overrideLogging = true;
/**
* Set the location of the Velocity config file.
* Alternatively, you can specify all properties locally.
* @see #setVelocityProperties
* @see #setResourceLoaderPath
*/
public void setConfigLocation(Resource configLocation) {
this.configLocation = configLocation;
}
/**
* Set Velocity properties, like "file.resource.loader.path".
* Can be used to override values in a Velocity config file,
* or to specify all necessary properties locally.
* <p>Note that the Velocity resource loader path also be set to any
* Spring resource location via the "resourceLoaderPath" property.
* Setting it here is just necessary when using a non-file-based
* resource loader.
* @see #setVelocityPropertiesMap
* @see #setConfigLocation
* @see #setResourceLoaderPath
*/
public void setVelocityProperties(Properties velocityProperties) {
setVelocityPropertiesMap(velocityProperties);
}
/**
* Set Velocity properties as Map, to allow for non-String values
* like "ds.resource.loader.instance".
* @see #setVelocityProperties
*/
public void setVelocityPropertiesMap(Map velocityPropertiesMap) {
if (velocityPropertiesMap != null) {
this.velocityProperties.putAll(velocityPropertiesMap);
}
}
/**
* Set the Velocity resource loader path via a Spring resource location.
* Accepts multiple locations in Velocity's comma-separated path style.
* <p>When populated via a String, standard URLs like "file:" and "classpath:"
* pseudo URLs are supported, as understood by ResourceLoader. Allows for
* relative paths when running in an ApplicationContext.
* <p>Will define a path for the default Velocity resource loader with the name
* "file". If the specified resource cannot be resolved to a <code>java.io.File</code>,
* a generic SpringResourceLoader will be used under the name "spring", without
* modification detection.
* <p>Note that resource caching will be enabled in any case. With the file
* resource loader, the last-modified timestamp will be checked on access to
* detect changes. With SpringResourceLoader, the resource will be cached
* forever (for example for class path resources).
* <p>To specify a modification check interval for files, use Velocity's
* standard "file.resource.loader.modificationCheckInterval" property. By default,
* the file timestamp is checked on every access (which is surprisingly fast).
* Of course, this just applies when loading resources from the file system.
* <p>To enforce the use of SpringResourceLoader, i.e. to not resolve a path
* as file system resource in any case, turn off the "preferFileSystemAccess"
* flag. See the latter's javadoc for details.
* @see #setResourceLoader
* @see #setVelocityProperties
* @see #setPreferFileSystemAccess
* @see SpringResourceLoader
* @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
*/
public void setResourceLoaderPath(String resourceLoaderPath) {
this.resourceLoaderPath = resourceLoaderPath;
}
/**
* Set the Spring ResourceLoader to use for loading Velocity template files.
* The default is DefaultResourceLoader. Will get overridden by the
* ApplicationContext if running in a context.
* @see org.springframework.core.io.DefaultResourceLoader
* @see org.springframework.context.ApplicationContext
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* Return the Spring ResourceLoader to use for loading Velocity template files.
*/
protected ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
/**
* Set whether to prefer file system access for template loading.
* File system access enables hot detection of template changes.
* <p>If this is enabled, VelocityEngineFactory will try to resolve the
* specified "resourceLoaderPath" as file system resource (which will work
* for expanded class path resources and ServletContext resources too).
* <p>Default is "true". Turn this off to always load via SpringResourceLoader
* (i.e. as stream, without hot detection of template changes), which might
* be necessary if some of your templates reside in an expanded classes
* directory while others reside in jar files.
* @see #setResourceLoaderPath
*/
public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {
this.preferFileSystemAccess = preferFileSystemAccess;
}
/**
* Return whether to prefer file system access for template loading.
*/
protected boolean isPreferFileSystemAccess() {
return this.preferFileSystemAccess;
}
/**
* Set whether Velocity should log via Commons Logging, i.e. whether Velocity's
* log system should be set to CommonsLoggingLogSystem. Default value is true.
* @see CommonsLoggingLogSystem
*/
public void setOverrideLogging(boolean overrideLogging) {
this.overrideLogging = overrideLogging;
}
/**
* Prepare the VelocityEngine instance and return it.
* @return the VelocityEngine instance
* @throws IOException if the config file wasn't found
* @throws VelocityException on Velocity initialization failure
*/
public VelocityEngine createVelocityEngine() throws IOException, VelocityException {
VelocityEngine velocityEngine = newVelocityEngine();
Properties props = new Properties();
// Load config file if set.
if (this.configLocation != null) {
if (logger.isInfoEnabled()) {
logger.info("Loading Velocity config from [" + this.configLocation + "]");
}
PropertiesLoaderUtils.fillProperties(props, this.configLocation);
}
// Merge local properties if set.
if (!this.velocityProperties.isEmpty()) {
props.putAll(this.velocityProperties);
}
// Set a resource loader path, if required.
if (this.resourceLoaderPath != null) {
initVelocityResourceLoader(velocityEngine, this.resourceLoaderPath);
}
// Log via Commons Logging?
if (this.overrideLogging) {
velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new CommonsLoggingLogSystem());
}
// Apply properties to VelocityEngine.
for (Iterator it = props.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
if (!(entry.getKey() instanceof String)) {
throw new IllegalArgumentException(
"Illegal property key [" + entry.getKey() + "]: only Strings allowed");
}
velocityEngine.setProperty((String) entry.getKey(), entry.getValue());
}
postProcessVelocityEngine(velocityEngine);
try {
// Perform actual initialization.
velocityEngine.init();
}
catch (IOException ex) {
throw ex;
}
catch (VelocityException ex) {
throw ex;
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
logger.error("Why does VelocityEngine throw a generic checked exception, after all?", ex);
throw new VelocityException(ex.toString());
}
return velocityEngine;
}
/**
* Return a new VelocityEngine. Subclasses can override this for
* custom initialization, or for using a mock object for testing.
* <p>Called by <code>createVelocityEngine()</code>.
* @return the VelocityEngine instance
* @throws IOException if a config file wasn't found
* @throws VelocityException on Velocity initialization failure
* @see #createVelocityEngine()
*/
protected VelocityEngine newVelocityEngine() throws IOException, VelocityException {
return new VelocityEngine();
}
/**
* Initialize a Velocity resource loader for the given VelocityEngine:
* either a standard Velocity FileResourceLoader or a SpringResourceLoader.
* <p>Called by <code>createVelocityEngine()</code>.
* @param velocityEngine the VelocityEngine to configure
* @param resourceLoaderPath the path to load Velocity resources from
* @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
* @see SpringResourceLoader
* @see #initSpringResourceLoader
* @see #createVelocityEngine()
*/
protected void initVelocityResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) {
if (isPreferFileSystemAccess()) {
// Try to load via the file system, fall back to SpringResourceLoader
// (for hot detection of template changes, if possible).
try {
StringBuffer resolvedPath = new StringBuffer();
String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
Resource resource = getResourceLoader().getResource(path);
File file = resource.getFile(); // will fail if not resolvable in the file system
if (logger.isDebugEnabled()) {
logger.debug("Resource loader path [" + path + "] resolved to file [" + file.getAbsolutePath() + "]");
}
resolvedPath.append(file.getAbsolutePath());
if (i < paths.length - 1) {
resolvedPath.append(',');
}
}
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true");
velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, resolvedPath.toString());
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot resolve resource loader path [" + resourceLoaderPath +
"] to [java.io.File]: using SpringResourceLoader", ex);
}
initSpringResourceLoader(velocityEngine, resourceLoaderPath);
}
}
else {
// Always load via SpringResourceLoader
// (without hot detection of template changes).
if (logger.isDebugEnabled()) {
logger.debug("File system access not preferred: using SpringResourceLoader");
}
initSpringResourceLoader(velocityEngine, resourceLoaderPath);
}
}
/**
* Initialize a SpringResourceLoader for the given VelocityEngine.
* <p>Called by <code>initVelocityResourceLoader</code>.
* @param velocityEngine the VelocityEngine to configure
* @param resourceLoaderPath the path to load Velocity resources from
* @see SpringResourceLoader
* @see #initVelocityResourceLoader
*/
protected void initSpringResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) {
velocityEngine.setProperty(
RuntimeConstants.RESOURCE_LOADER, SpringResourceLoader.NAME);
velocityEngine.setProperty(
SpringResourceLoader.SPRING_RESOURCE_LOADER_CLASS, SpringResourceLoader.class.getName());
velocityEngine.setProperty(
SpringResourceLoader.SPRING_RESOURCE_LOADER_CACHE, "true");
velocityEngine.setApplicationAttribute(
SpringResourceLoader.SPRING_RESOURCE_LOADER, getResourceLoader());
velocityEngine.setApplicationAttribute(
SpringResourceLoader.SPRING_RESOURCE_LOADER_PATH, resourceLoaderPath);
}
/**
* To be implemented by subclasses that want to to perform custom
* post-processing of the VelocityEngine after this FactoryBean
* performed its default configuration (but before VelocityEngine.init).
* <p>Called by <code>createVelocityEngine()</code>.
* @param velocityEngine the current VelocityEngine
* @throws IOException if a config file wasn't found
* @throws VelocityException on Velocity initialization failure
* @see #createVelocityEngine()
* @see org.apache.velocity.app.VelocityEngine#init
*/
protected void postProcessVelocityEngine(VelocityEngine velocityEngine)
throws IOException, VelocityException {
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.velocity;
import java.io.IOException;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
/**
* Factory bean that configures a VelocityEngine and provides it as bean
* reference. This bean is intended for any kind of usage of Velocity in
* application code, e.g. for generating email content. For web views,
* VelocityConfigurer is used to set up a VelocityEngine for views.
*
* <p>The simplest way to use this class is to specify a "resourceLoaderPath";
* you do not need any further configuration then. For example, in a web
* application context:
*
* <pre class="code"> &lt;bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean"&gt;
* &lt;property name="resourceLoaderPath" value="/WEB-INF/velocity/"/&gt;
* &lt;/bean&gt;</pre>
*
* See the base class VelocityEngineFactory for configuration details.
*
* @author Juergen Hoeller
* @see #setConfigLocation
* @see #setVelocityProperties
* @see #setResourceLoaderPath
* @see org.springframework.web.servlet.view.velocity.VelocityConfigurer
*/
public class VelocityEngineFactoryBean extends VelocityEngineFactory
implements FactoryBean, InitializingBean, ResourceLoaderAware {
private VelocityEngine velocityEngine;
public void afterPropertiesSet() throws IOException, VelocityException {
this.velocityEngine = createVelocityEngine();
}
public Object getObject() {
return this.velocityEngine;
}
public Class getObjectType() {
return VelocityEngine.class;
}
public boolean isSingleton() {
return true;
}
}

View File

@ -0,0 +1,149 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.velocity;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;
/**
* Utility class for working with a VelocityEngine.
* Provides convenience methods to merge a Velocity template with a model.
*
* @author Juergen Hoeller
* @since 22.01.2004
*/
public abstract class VelocityEngineUtils {
private static final Log logger = LogFactory.getLog(VelocityEngineUtils.class);
/**
* Merge the specified Velocity template with the given model and write
* the result to the given Writer.
* @param velocityEngine VelocityEngine to work with
* @param templateLocation the location of template, relative to Velocity's
* resource loader path
* @param model the Map that contains model names as keys and model objects
* as values
* @param writer the Writer to write the result to
* @throws VelocityException if the template wasn't found or rendering failed
*/
public static void mergeTemplate(
VelocityEngine velocityEngine, String templateLocation, Map model, Writer writer)
throws VelocityException {
try {
VelocityContext velocityContext = new VelocityContext(model);
velocityEngine.mergeTemplate(templateLocation, velocityContext, writer);
}
catch (VelocityException ex) {
throw ex;
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
logger.error("Why does VelocityEngine throw a generic checked exception, after all?", ex);
throw new VelocityException(ex.toString());
}
}
/**
* Merge the specified Velocity template with the given model and write
* the result to the given Writer.
* @param velocityEngine VelocityEngine to work with
* @param templateLocation the location of template, relative to Velocity's
* resource loader path
* @param encoding the encoding of the template file
* @param model the Map that contains model names as keys and model objects
* as values
* @param writer the Writer to write the result to
* @throws VelocityException if the template wasn't found or rendering failed
*/
public static void mergeTemplate(
VelocityEngine velocityEngine, String templateLocation, String encoding, Map model, Writer writer)
throws VelocityException {
try {
VelocityContext velocityContext = new VelocityContext(model);
velocityEngine.mergeTemplate(templateLocation, encoding, velocityContext, writer);
}
catch (VelocityException ex) {
throw ex;
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
logger.error("Why does VelocityEngine throw a generic checked exception, after all?", ex);
throw new VelocityException(ex.toString());
}
}
/**
* Merge the specified Velocity template with the given model into a String.
* <p>When using this method to prepare a text for a mail to be sent with Spring's
* mail support, consider wrapping VelocityException in MailPreparationException.
* @param velocityEngine VelocityEngine to work with
* @param templateLocation the location of template, relative to Velocity's
* resource loader path
* @param model the Map that contains model names as keys and model objects
* as values
* @return the result as String
* @throws VelocityException if the template wasn't found or rendering failed
* @see org.springframework.mail.MailPreparationException
*/
public static String mergeTemplateIntoString(
VelocityEngine velocityEngine, String templateLocation, Map model)
throws VelocityException {
StringWriter result = new StringWriter();
mergeTemplate(velocityEngine, templateLocation, model, result);
return result.toString();
}
/**
* Merge the specified Velocity template with the given model into a String.
* <p>When using this method to prepare a text for a mail to be sent with Spring's
* mail support, consider wrapping VelocityException in MailPreparationException.
* @param velocityEngine VelocityEngine to work with
* @param templateLocation the location of template, relative to Velocity's
* resource loader path
* @param encoding the encoding of the template file
* @param model the Map that contains model names as keys and model objects
* as values
* @return the result as String
* @throws VelocityException if the template wasn't found or rendering failed
* @see org.springframework.mail.MailPreparationException
*/
public static String mergeTemplateIntoString(
VelocityEngine velocityEngine, String templateLocation, String encoding, Map model)
throws VelocityException {
StringWriter result = new StringWriter();
mergeTemplate(velocityEngine, templateLocation, encoding, model, result);
return result.toString();
}
}

View File

@ -0,0 +1,9 @@
<html>
<body>
Support classes for setting up
<a href="http://velocity.apache.org">Velocity</a>
within a Spring application context.
</body>
</html>

View File

@ -0,0 +1,7 @@
<html>
<body>
<p>
The Spring Data Binding framework, an internal library used by Spring Web Flow.
</p>
</body>
</html>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- Appenders -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p: %c - %m%n" />
</layout>
</appender>
<logger name="org.springframework.beans">
<level value="warn" />
</logger>
<logger name="org.springframework.binding">
<level value="debug" />
</logger>
<!-- Root Logger -->
<root>
<priority value="warn" />
<appender-ref ref="console" />
</root>
</log4j:configuration>

View File

@ -0,0 +1,31 @@
Bundle-SymbolicName: org.springframework.context.support
Bundle-Name: Spring Context Support
Bundle-Vendor: SpringSource
Bundle-ManifestVersion: 2
Import-Template:
commonj.*;version="[1.1.0, 2.0.0)";resolution:=optional,
freemarker.*;version="[2.3.12, 3.0.0)";resolution:=optional,
javax.activation.*;version="[1.1.0, 2.0.0)";resolution:=optional,
javax.mail.*;version="[1.4.0, 2.0.0)";resolution:=optional,
net.sf.ehcache.*;version="[1.3.0, 2.0.0)";resolution:=optional,
net.sf.jasperreports.*;version="[2.0.5, 3.0.0)";resolution:=optional,
org.apache.commons.collections.*;version="[3.2.0, 4.0.0)";resolution:=optional,
org.apache.commons.logging.*;version="[1.1.1, 2.0.0)";resolution:=optional,
org.apache.velocity.*;version="[1.5.0, 2.0.0)";resolution:=optional,
org.quartz.*;version="[1.6.0, 2.0.0)";resolution:=optional,
org.springframework.beans.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional,
org.springframework.context.*;version="[2.5.5.A, 2.5.5.A]",
org.springframework.core.*;version="[2.5.5.A, 2.5.5.A]",
org.springframework.jdbc.datasource.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional,
org.springframework.jndi.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional,
org.springframework.scheduling.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional,
org.springframework.transaction.*;version="[2.5.5.A, 2.5.5.A]";resolution:=optional,
org.springframework.util.*;version="[2.5.5.A, 2.5.5.A]"
Unversioned-Imports:
javax.naming.*,
javax.sql.*
Ignored-Existing-Headers:
Bnd-LastModified,
Import-Package,
Export-Package,
Tool