Require Jackson 2.0+, EhCache 2.5+, Quartz 2.1.4+
Issue: SPR-11262
This commit is contained in:
@@ -19,7 +19,6 @@ configure(allprojects) { project ->
|
||||
ext.hibernate4Version = "4.2.11.Final"
|
||||
ext.hibValVersion = "4.3.1.Final"
|
||||
ext.hsqldbVersion = "2.3.2"
|
||||
ext.jackson1Version = "1.9.13"
|
||||
ext.jackson2Version = "2.3.1"
|
||||
ext.jasperReportsVersion = "5.5.1"
|
||||
ext.jettyVersion = "9.1.3.v20140225"
|
||||
@@ -495,7 +494,6 @@ project("spring-jms") {
|
||||
optional("aopalliance:aopalliance:1.0")
|
||||
optional("javax.transaction:javax.transaction-api:1.2")
|
||||
optional("javax.resource:connector-api:1.5")
|
||||
optional("org.codehaus.jackson:jackson-mapper-asl:${jackson1Version}")
|
||||
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
|
||||
}
|
||||
}
|
||||
@@ -530,9 +528,7 @@ project("spring-context-support") {
|
||||
optional("javax.cache:cache-api:1.0.0-RC1")
|
||||
optional("com.google.guava:guava:16.0.1")
|
||||
optional("net.sf.ehcache:ehcache-core:2.6.5")
|
||||
optional("org.quartz-scheduler:quartz:1.8.6") {
|
||||
exclude group: "org.slf4j", module: "slf4j-log4j12"
|
||||
}
|
||||
optional("org.quartz-scheduler:quartz:2.1.7")
|
||||
optional("org.codehaus.fabric3.api:commonj:1.1.0")
|
||||
optional("org.apache.velocity:velocity:1.7")
|
||||
optional("org.freemarker:freemarker:2.3.20")
|
||||
@@ -568,7 +564,6 @@ project("spring-web") {
|
||||
optional("commons-fileupload:commons-fileupload:1.3.1")
|
||||
optional("org.apache.httpcomponents:httpclient:4.3.3")
|
||||
optional("org.apache.httpcomponents:httpasyncclient:4.0.1")
|
||||
optional("org.codehaus.jackson:jackson-mapper-asl:${jackson1Version}")
|
||||
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
|
||||
optional("rome:rome:1.0")
|
||||
optional("taglibs:standard:1.1.2")
|
||||
@@ -709,7 +704,6 @@ project("spring-webmvc") {
|
||||
optional("net.sf.jasperreports:jasperreports:$jasperReportsVersion") {
|
||||
exclude group: "xml-apis", module: "xml-apis"
|
||||
}
|
||||
optional("org.codehaus.jackson:jackson-mapper-asl:${jackson1Version}")
|
||||
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
|
||||
optional("rome:rome:1.0")
|
||||
optional("org.apache.tiles:tiles-api:${tiles2Version}")
|
||||
@@ -847,7 +841,6 @@ project("spring-test") {
|
||||
testCompile("org.hibernate:hibernate-entitymanager:${hibernate3Version}")
|
||||
testCompile("org.hibernate:hibernate-validator:${hibValVersion}")
|
||||
testCompile("com.thoughtworks.xstream:xstream:${xstreamVersion}")
|
||||
testCompile("org.codehaus.jackson:jackson-mapper-asl:${jackson1Version}")
|
||||
testCompile("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
|
||||
testCompile("rome:rome:1.0")
|
||||
testCompile("org.apache.tiles:tiles-api:${tiles3Version}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -52,9 +52,8 @@ import org.springframework.util.ClassUtils;
|
||||
* <p>Note: If the named Cache instance is found, the properties will be ignored and the
|
||||
* Cache instance will be retrieved from the CacheManager.
|
||||
*
|
||||
* <p>Note: As of Spring 4.0, Spring's EhCache support requires EhCache 2.1 or higher.
|
||||
* We recommend the use of EhCache 2.5 or higher.
|
||||
|
||||
* <p>Note: As of Spring 4.1, Spring's EhCache support requires EhCache 2.5 or higher.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Dmitriy Kopylenko
|
||||
* @since 1.1.1
|
||||
@@ -92,10 +91,8 @@ public class EhCacheFactoryBean extends CacheConfiguration implements FactoryBea
|
||||
private Ehcache cache;
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public EhCacheFactoryBean() {
|
||||
// Using deprecated setMaxElementsInMemory method for EhCache 2.1-2.4 compatibility
|
||||
setMaxElementsInMemory(10000);
|
||||
setMaxEntriesLocalHeap(10000);
|
||||
setMaxElementsOnDisk(10000000);
|
||||
setTimeToLiveSeconds(120);
|
||||
setTimeToIdleSeconds(120);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -44,8 +44,7 @@ import org.springframework.core.io.Resource;
|
||||
* 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 4.0, Spring's EhCache support requires EhCache 2.1 or higher.
|
||||
* We recommend the use of EhCache 2.5 or higher.
|
||||
* <p>Note: As of Spring 4.1, Spring's EhCache support requires EhCache 2.5 or higher.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Dmitriy Kopylenko
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -16,22 +16,17 @@
|
||||
|
||||
package org.springframework.scheduling.quartz;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.quartz.Job;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.spi.JobFactory;
|
||||
import org.quartz.spi.TriggerFiredBundle;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* JobFactory implementation that supports {@link java.lang.Runnable}
|
||||
* objects as well as standard Quartz {@link org.quartz.Job} instances.
|
||||
*
|
||||
* <p>Compatible with Quartz 1.8 as well as Quartz 2.0-2.2, as of Spring 4.0.
|
||||
* <b>Note:</b> Quartz 1.x support is deprecated - please upgrade to Quartz 2.0+.
|
||||
* <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
@@ -40,19 +35,8 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
public class AdaptableJobFactory implements JobFactory {
|
||||
|
||||
/**
|
||||
* Quartz 2.0 version of newJob: simply delegates to old newJob variant.
|
||||
* @see #newJob(org.quartz.spi.TriggerFiredBundle)
|
||||
*/
|
||||
public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
|
||||
return newJob(bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quartz 1.x version of newJob: contains actual implementation code.
|
||||
*/
|
||||
@Override
|
||||
public Job newJob(TriggerFiredBundle bundle) throws SchedulerException {
|
||||
public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
|
||||
try {
|
||||
Object jobObject = createJobInstance(bundle);
|
||||
return adaptJob(jobObject);
|
||||
@@ -71,12 +55,7 @@ public class AdaptableJobFactory implements JobFactory {
|
||||
* @throws Exception if job instantiation failed
|
||||
*/
|
||||
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
|
||||
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
|
||||
Method getJobDetail = bundle.getClass().getMethod("getJobDetail");
|
||||
Object jobDetail = ReflectionUtils.invokeMethod(getJobDetail, bundle);
|
||||
Method getJobClass = jobDetail.getClass().getMethod("getJobClass");
|
||||
Class<?> jobClass = (Class<?>) ReflectionUtils.invokeMethod(getJobClass, jobDetail);
|
||||
return jobClass.newInstance();
|
||||
return bundle.getJobDetail().getJobClass().newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.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;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Convenience subclass of Quartz's {@link org.quartz.CronTrigger} class,
|
||||
* making bean-style usage easier.
|
||||
*
|
||||
* <p>{@code 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: This convenience subclass does not work against Quartz 2.0.</b>
|
||||
* Use Quartz 2.0's native {@code JobDetailImpl} class or the new Quartz 2.0
|
||||
* builder API instead. Alternatively, switch to Spring's {@link CronTriggerFactoryBean}
|
||||
* which largely is a drop-in replacement for this class and its properties and
|
||||
* consistently works against Quartz 1.x as well as Quartz 2.x.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
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;
|
||||
|
||||
private long startDelay = 0;
|
||||
|
||||
|
||||
/**
|
||||
* 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<String, ?> 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}.
|
||||
* @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
|
||||
* @deprecated as of Spring 4.0, since it only works on Quartz 1.x
|
||||
*/
|
||||
@Deprecated
|
||||
public void setTriggerListenerNames(String... names) {
|
||||
for (String name : names) {
|
||||
addTriggerListener(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start delay in milliseconds.
|
||||
* <p>The start delay is added to the current system time (when the bean starts)
|
||||
* to control the {@link #setStartTime start time} of the trigger.
|
||||
* <p>If the start delay is non-zero, it will <strong>always</strong>
|
||||
* take precedence over start time.
|
||||
* @param startDelay the start delay, in milliseconds
|
||||
*/
|
||||
public void setStartDelay(long startDelay) {
|
||||
Assert.state(startDelay >= 0, "Start delay cannot be negative.");
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobDetail getJobDetail() {
|
||||
return this.jobDetail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanName(String beanName) {
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (getName() == null) {
|
||||
setName(this.beanName);
|
||||
}
|
||||
if (getGroup() == null) {
|
||||
setGroup(Scheduler.DEFAULT_GROUP);
|
||||
}
|
||||
if (this.startDelay > 0 || getStartTime() == null) {
|
||||
setStartTime(new Date(System.currentTimeMillis() + this.startDelay));
|
||||
}
|
||||
if (getTimeZone() == null) {
|
||||
setTimeZone(TimeZone.getDefault());
|
||||
}
|
||||
if (this.jobDetail != null) {
|
||||
setJobName(this.jobDetail.getName());
|
||||
setJobGroup(this.jobDetail.getGroup());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.scheduling.quartz;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
@@ -25,16 +25,13 @@ import org.quartz.CronTrigger;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.impl.triggers.CronTriggerImpl;
|
||||
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.Constants;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* A Spring {@link FactoryBean} for creating a Quartz {@link org.quartz.CronTrigger}
|
||||
@@ -49,9 +46,6 @@ import org.springframework.util.ReflectionUtils;
|
||||
* to automatically register a trigger for the corresponding JobDetail,
|
||||
* instead of registering the JobDetail separately.
|
||||
*
|
||||
* <p><b>NOTE:</b> This FactoryBean works against both Quartz 1.x and Quartz 2.x,
|
||||
* in contrast to the older {@link CronTriggerBean} class.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
* @see #setName
|
||||
@@ -60,7 +54,6 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @see #setJobDetail
|
||||
* @see SchedulerFactoryBean#setTriggers
|
||||
* @see SchedulerFactoryBean#setJobDetails
|
||||
* @see SimpleTriggerBean
|
||||
*/
|
||||
public class CronTriggerFactoryBean implements FactoryBean<CronTrigger>, BeanNameAware, InitializingBean {
|
||||
|
||||
@@ -139,7 +132,6 @@ public class CronTriggerFactoryBean implements FactoryBean<CronTrigger>, BeanNam
|
||||
* 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 org.springframework.scheduling.quartz.JobDetailBean#setJobDataAsMap
|
||||
*/
|
||||
public void setJobDataAsMap(Map<String, ?> jobDataAsMap) {
|
||||
this.jobDataMap.putAll(jobDataAsMap);
|
||||
@@ -225,7 +217,7 @@ public class CronTriggerFactoryBean implements FactoryBean<CronTrigger>, BeanNam
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
public void afterPropertiesSet() throws ParseException {
|
||||
if (this.name == null) {
|
||||
this.name = this.beanName;
|
||||
}
|
||||
@@ -233,7 +225,7 @@ public class CronTriggerFactoryBean implements FactoryBean<CronTrigger>, BeanNam
|
||||
this.group = Scheduler.DEFAULT_GROUP;
|
||||
}
|
||||
if (this.jobDetail != null) {
|
||||
this.jobDataMap.put(JobDetailAwareTrigger.JOB_DETAIL_KEY, this.jobDetail);
|
||||
this.jobDataMap.put("jobDetail", this.jobDetail);
|
||||
}
|
||||
if (this.startDelay > 0 || this.startTime == null) {
|
||||
this.startTime = new Date(System.currentTimeMillis() + this.startDelay);
|
||||
@@ -242,7 +234,6 @@ public class CronTriggerFactoryBean implements FactoryBean<CronTrigger>, BeanNam
|
||||
this.timeZone = TimeZone.getDefault();
|
||||
}
|
||||
|
||||
/*
|
||||
CronTriggerImpl cti = new CronTriggerImpl();
|
||||
cti.setName(this.name);
|
||||
cti.setGroup(this.group);
|
||||
@@ -256,42 +247,6 @@ public class CronTriggerFactoryBean implements FactoryBean<CronTrigger>, BeanNam
|
||||
cti.setMisfireInstruction(this.misfireInstruction);
|
||||
cti.setDescription(this.description);
|
||||
this.cronTrigger = cti;
|
||||
*/
|
||||
|
||||
Class<?> cronTriggerClass;
|
||||
Method jobKeyMethod;
|
||||
try {
|
||||
cronTriggerClass = getClass().getClassLoader().loadClass("org.quartz.impl.triggers.CronTriggerImpl");
|
||||
jobKeyMethod = JobDetail.class.getMethod("getKey");
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
cronTriggerClass = CronTrigger.class;
|
||||
jobKeyMethod = null;
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Incompatible Quartz version");
|
||||
}
|
||||
BeanWrapper bw = new BeanWrapperImpl(cronTriggerClass);
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("name", this.name);
|
||||
pvs.add("group", this.group);
|
||||
if (jobKeyMethod != null) {
|
||||
pvs.add("jobKey", ReflectionUtils.invokeMethod(jobKeyMethod, this.jobDetail));
|
||||
}
|
||||
else {
|
||||
pvs.add("jobName", this.jobDetail.getName());
|
||||
pvs.add("jobGroup", this.jobDetail.getGroup());
|
||||
}
|
||||
pvs.add("jobDataMap", this.jobDataMap);
|
||||
pvs.add("startTime", this.startTime);
|
||||
pvs.add("cronExpression", this.cronExpression);
|
||||
pvs.add("timeZone", this.timeZone);
|
||||
pvs.add("calendarName", this.calendarName);
|
||||
pvs.add("priority", this.priority);
|
||||
pvs.add("misfireInstruction", this.misfireInstruction);
|
||||
pvs.add("description", this.description);
|
||||
bw.setPropertyValues(pvs);
|
||||
this.cronTrigger = (CronTrigger) bw.getWrappedInstance();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.
|
||||
*
|
||||
* <p><b>NOTE: As of Quartz 2.0, the recommended strategy is to define an
|
||||
* entry of name "jobDetail" and type JobDetail in the trigger's JobDataMap.
|
||||
*
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* Name of the key for the JobDetail value in the trigger's JobDataMap.
|
||||
* This is an alternative to implementing the JobDetailAwareTrigger interface.
|
||||
*/
|
||||
String JOB_DETAIL_KEY = "jobDetail";
|
||||
|
||||
/**
|
||||
* Return the JobDetail that this Trigger is associated with.
|
||||
* @return the associated JobDetail, or {@code null} if none
|
||||
*/
|
||||
JobDetail getJobDetail();
|
||||
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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's {@link org.quartz.JobDetail} class,
|
||||
* making bean-style usage easier.
|
||||
*
|
||||
* <p>{@code JobDetail} 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.
|
||||
*
|
||||
* <p><b>NOTE: This convenience subclass does not work against Quartz 2.0.</b>
|
||||
* Use Quartz 2.0's native {@code JobDetailImpl} class or the new Quartz 2.0
|
||||
* builder API instead. Alternatively, switch to Spring's {@link JobDetailFactoryBean}
|
||||
* which largely is a drop-in replacement for this class and its properties and
|
||||
* consistently works against Quartz 1.x as well as Quartz 2.x.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 18.02.2004
|
||||
* @see #setName
|
||||
* @see #setGroup
|
||||
* @see org.springframework.beans.factory.BeanNameAware
|
||||
* @see org.quartz.Scheduler#DEFAULT_GROUP
|
||||
*/
|
||||
@SuppressWarnings({"serial", "rawtypes"})
|
||||
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
|
||||
*/
|
||||
@Override
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
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<String, ?> 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
|
||||
* @deprecated as of Spring 4.0, since it only works on Quartz 1.x
|
||||
*/
|
||||
@Deprecated
|
||||
public void setJobListenerNames(String... names) {
|
||||
for (String name : names) {
|
||||
addJobListener(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanName(String beanName) {
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -21,10 +21,8 @@ import java.util.Map;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.impl.JobDetailImpl;
|
||||
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -39,9 +37,6 @@ import org.springframework.context.ApplicationContextAware;
|
||||
* sensible defaults. This class uses the Spring bean name as job name,
|
||||
* and the Quartz default group ("DEFAULT") as job group if not specified.
|
||||
*
|
||||
* <p><b>NOTE:</b> This FactoryBean works against both Quartz 1.x and Quartz 2.x,
|
||||
* in contrast to the older {@link JobDetailBean} class.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
* @see #setName
|
||||
@@ -181,6 +176,7 @@ public class JobDetailFactoryBean
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void afterPropertiesSet() {
|
||||
if (this.name == null) {
|
||||
this.name = this.beanName;
|
||||
@@ -197,35 +193,15 @@ public class JobDetailFactoryBean
|
||||
getJobDataMap().put(this.applicationContextJobDataKey, this.applicationContext);
|
||||
}
|
||||
|
||||
/*
|
||||
JobDetailImpl jdi = new JobDetailImpl();
|
||||
jdi.setName(this.name);
|
||||
jdi.setGroup(this.group);
|
||||
jdi.setJobClass(this.jobClass);
|
||||
jdi.setJobClass((Class) this.jobClass);
|
||||
jdi.setJobDataMap(this.jobDataMap);
|
||||
jdi.setDurability(this.durability);
|
||||
jdi.setRequestsRecovery(this.requestsRecovery);
|
||||
jdi.setDescription(this.description);
|
||||
this.jobDetail = jdi;
|
||||
*/
|
||||
|
||||
Class<?> jobDetailClass;
|
||||
try {
|
||||
jobDetailClass = getClass().getClassLoader().loadClass("org.quartz.impl.JobDetailImpl");
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
jobDetailClass = JobDetail.class;
|
||||
}
|
||||
BeanWrapper bw = new BeanWrapperImpl(jobDetailClass);
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("name", this.name);
|
||||
pvs.add("group", this.group);
|
||||
pvs.add("jobClass", this.jobClass);
|
||||
pvs.add("jobDataMap", this.jobDataMap);
|
||||
pvs.add("durability", this.durability);
|
||||
pvs.add("requestsRecovery", this.requestsRecovery);
|
||||
pvs.add("description", this.description);
|
||||
bw.setPropertyValues(pvs);
|
||||
this.jobDetail = (JobDetail) bw.getWrappedInstance();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -87,7 +87,7 @@ public class LocalTaskExecutorThreadPool implements ThreadPool {
|
||||
|
||||
@Override
|
||||
public int blockForAvailableThreads() {
|
||||
// The present implementation always returns 1, making Quartz (1.6)
|
||||
// The present implementation always returns 1, making Quartz
|
||||
// always schedule any tasks that it feels like scheduling.
|
||||
// This could be made smarter for specific TaskExecutors,
|
||||
// for example calling {@code getMaximumPoolSize() - getActiveCount()}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -17,20 +17,17 @@
|
||||
package org.springframework.scheduling.quartz;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.quartz.PersistJobDataAfterExecution;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.StatefulJob;
|
||||
import org.quartz.impl.JobDetailImpl;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.PropertyAccessorFactory;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
@@ -41,7 +38,6 @@ import org.springframework.beans.support.ArgumentConvertingMethodInvoker;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MethodInvoker;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.FactoryBean} that exposes a
|
||||
@@ -67,8 +63,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* 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.
|
||||
*
|
||||
* <p>Compatible with Quartz 1.8 as well as Quartz 2.0-2.2, as of Spring 4.0.
|
||||
* <b>Note:</b> Quartz 1.x support is deprecated - please upgrade to Quartz 2.0+.
|
||||
* <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Alef Arendsen
|
||||
@@ -81,28 +76,6 @@ import org.springframework.util.ReflectionUtils;
|
||||
public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethodInvoker
|
||||
implements FactoryBean<JobDetail>, BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean {
|
||||
|
||||
private static Class<?> jobDetailImplClass;
|
||||
|
||||
private static Method setResultMethod;
|
||||
|
||||
static {
|
||||
try {
|
||||
jobDetailImplClass = Class.forName("org.quartz.impl.JobDetailImpl");
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
jobDetailImplClass = null;
|
||||
}
|
||||
try {
|
||||
Class<?> jobExecutionContextClass =
|
||||
QuartzJobBean.class.getClassLoader().loadClass("org.quartz.JobExecutionContext");
|
||||
setResultMethod = jobExecutionContextClass.getMethod("setResult", Object.class);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Incompatible Quartz API: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String name;
|
||||
|
||||
private String group = Scheduler.DEFAULT_GROUP;
|
||||
@@ -111,8 +84,6 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
|
||||
|
||||
private String targetBeanName;
|
||||
|
||||
private String[] jobListenerNames;
|
||||
|
||||
private String beanName;
|
||||
|
||||
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
|
||||
@@ -125,7 +96,6 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
|
||||
/**
|
||||
* 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;
|
||||
@@ -134,7 +104,6 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
|
||||
/**
|
||||
* 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) {
|
||||
@@ -142,9 +111,10 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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 {@code @PersistJobDataAfterExecution} and
|
||||
* {@code @DisallowConcurrentExecution} markers.
|
||||
* More information on stateful versus stateless jobs can be found
|
||||
* <a href="http://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-03">here</a>.
|
||||
* <p>The default setting is to run jobs concurrently.
|
||||
@@ -165,20 +135,6 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
|
||||
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
|
||||
* @deprecated as of Spring 4.0, since it only works on Quartz 1.x
|
||||
*/
|
||||
@Deprecated
|
||||
public void setJobListenerNames(String... names) {
|
||||
this.jobListenerNames = names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanName(String beanName) {
|
||||
this.beanName = beanName;
|
||||
@@ -201,6 +157,7 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException {
|
||||
prepare();
|
||||
|
||||
@@ -211,34 +168,13 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
|
||||
Class<?> jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class);
|
||||
|
||||
// Build JobDetail instance.
|
||||
if (jobDetailImplClass != null) {
|
||||
// Using Quartz 2.0 JobDetailImpl class...
|
||||
this.jobDetail = (JobDetail) BeanUtils.instantiate(jobDetailImplClass);
|
||||
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this.jobDetail);
|
||||
bw.setPropertyValue("name", name);
|
||||
bw.setPropertyValue("group", this.group);
|
||||
bw.setPropertyValue("jobClass", jobClass);
|
||||
bw.setPropertyValue("durability", true);
|
||||
((JobDataMap) bw.getPropertyValue("jobDataMap")).put("methodInvoker", this);
|
||||
}
|
||||
else {
|
||||
// Using Quartz 1.x JobDetail class...
|
||||
this.jobDetail = new JobDetail(name, this.group, jobClass);
|
||||
this.jobDetail.setVolatility(true);
|
||||
this.jobDetail.setDurability(true);
|
||||
this.jobDetail.getJobDataMap().put("methodInvoker", this);
|
||||
}
|
||||
|
||||
// Register job listener names.
|
||||
if (this.jobListenerNames != null) {
|
||||
for (String jobListenerName : this.jobListenerNames) {
|
||||
if (jobDetailImplClass != null) {
|
||||
throw new IllegalStateException("Non-global JobListeners not supported on Quartz 2 - " +
|
||||
"manually register a Matcher against the Quartz ListenerManager instead");
|
||||
}
|
||||
this.jobDetail.addJobListener(jobListenerName);
|
||||
}
|
||||
}
|
||||
JobDetailImpl jdi = new JobDetailImpl();
|
||||
jdi.setName(name);
|
||||
jdi.setGroup(group);
|
||||
jdi.setJobClass((Class) jobClass);
|
||||
jdi.setDurability(true);
|
||||
jdi.getJobDataMap().put("methodInvoker", this);
|
||||
this.jobDetail = jdi;
|
||||
|
||||
postProcessJobDetail(this.jobDetail);
|
||||
}
|
||||
@@ -318,7 +254,7 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
|
||||
try {
|
||||
ReflectionUtils.invokeMethod(setResultMethod, context, this.methodInvoker.invoke());
|
||||
context.setResult(this.methodInvoker.invoke());
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
if (ex.getTargetException() instanceof JobExecutionException) {
|
||||
@@ -343,7 +279,9 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
|
||||
* 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 {
|
||||
@PersistJobDataAfterExecution
|
||||
@DisallowConcurrentExecution
|
||||
public static class StatefulMethodInvokingJob extends MethodInvokingJob {
|
||||
|
||||
// No implementation, just an addition of the tag interface StatefulJob
|
||||
// in order to allow stateful method invoking jobs.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -16,19 +16,14 @@
|
||||
|
||||
package org.springframework.scheduling.quartz;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.PropertyAccessorFactory;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Simple implementation of the Quartz Job interface, applying the
|
||||
@@ -43,15 +38,9 @@ import org.springframework.util.ReflectionUtils;
|
||||
* 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><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
|
||||
* <p><b>Note that 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.
|
||||
@@ -60,33 +49,12 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @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 {
|
||||
|
||||
private static final Method getSchedulerMethod;
|
||||
|
||||
private static final Method getMergedJobDataMapMethod;
|
||||
|
||||
|
||||
static {
|
||||
try {
|
||||
Class<?> jobExecutionContextClass =
|
||||
QuartzJobBean.class.getClassLoader().loadClass("org.quartz.JobExecutionContext");
|
||||
getSchedulerMethod = jobExecutionContextClass.getMethod("getScheduler");
|
||||
getMergedJobDataMapMethod = jobExecutionContextClass.getMethod("getMergedJobDataMap");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Incompatible Quartz API: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This implementation applies the passed-in job data map as bean property
|
||||
* values, and delegates to {@code executeInternal} afterwards.
|
||||
@@ -95,14 +63,10 @@ public abstract class QuartzJobBean implements Job {
|
||||
@Override
|
||||
public final void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
try {
|
||||
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
|
||||
Scheduler scheduler = (Scheduler) ReflectionUtils.invokeMethod(getSchedulerMethod, context);
|
||||
Map<?, ?> mergedJobDataMap = (Map<?, ?>) ReflectionUtils.invokeMethod(getMergedJobDataMapMethod, context);
|
||||
|
||||
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.addPropertyValues(scheduler.getContext());
|
||||
pvs.addPropertyValues(mergedJobDataMap);
|
||||
pvs.addPropertyValues(context.getScheduler().getContext());
|
||||
pvs.addPropertyValues(context.getMergedJobDataMap());
|
||||
bw.setPropertyValues(pvs, true);
|
||||
}
|
||||
catch (SchedulerException ex) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.scheduling.quartz;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
@@ -28,6 +27,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.quartz.Calendar;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobListener;
|
||||
import org.quartz.ListenerManager;
|
||||
import org.quartz.ObjectAlreadyExistsException;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
@@ -43,7 +43,6 @@ import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Common base class for accessing a Quartz Scheduler, i.e. for registering jobs,
|
||||
@@ -52,8 +51,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* <p>For concrete usage, check out the {@link SchedulerFactoryBean} and
|
||||
* {@link SchedulerAccessorBean} classes.
|
||||
*
|
||||
* <p>Compatible with Quartz 1.8 as well as Quartz 2.0-2.2, as of Spring 4.0.
|
||||
* <b>Note:</b> Quartz 1.x support is deprecated - please upgrade to Quartz 2.0+.
|
||||
* <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Stephane Nicoll
|
||||
@@ -61,23 +59,6 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
public abstract class SchedulerAccessor implements ResourceLoaderAware {
|
||||
|
||||
private static Class<?> jobKeyClass;
|
||||
|
||||
private static Class<?> triggerKeyClass;
|
||||
|
||||
static {
|
||||
// Quartz 2.0 job/trigger key available?
|
||||
try {
|
||||
jobKeyClass = Class.forName("org.quartz.JobKey");
|
||||
triggerKeyClass = Class.forName("org.quartz.TriggerKey");
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
jobKeyClass = null;
|
||||
triggerKeyClass = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private boolean overwriteExistingJobs = false;
|
||||
@@ -94,24 +75,13 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
|
||||
|
||||
private JobListener[] globalJobListeners;
|
||||
|
||||
private JobListener[] jobListeners;
|
||||
|
||||
private TriggerListener[] globalTriggerListeners;
|
||||
|
||||
private TriggerListener[] triggerListeners;
|
||||
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
protected ResourceLoader resourceLoader;
|
||||
|
||||
|
||||
public SchedulerAccessor() {
|
||||
if (jobKeyClass == null && logger.isInfoEnabled()) {
|
||||
logger.info("Spring's Quartz 1.x support is deprecated - please upgrade to Quartz 2.0+");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether any jobs defined on this SchedulerFactoryBean should overwrite
|
||||
* existing job definitions. Default is "false", to not overwrite already
|
||||
@@ -151,8 +121,6 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
|
||||
* in combination with the Trigger.
|
||||
* @see #setTriggers
|
||||
* @see org.quartz.JobDetail
|
||||
* @see JobDetailBean
|
||||
* @see JobDetailAwareTrigger
|
||||
*/
|
||||
public void setJobDetails(JobDetail... jobDetails) {
|
||||
// Use modifiable ArrayList here, to allow for further adding of
|
||||
@@ -180,15 +148,11 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
|
||||
* "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.
|
||||
*/
|
||||
@@ -204,21 +168,6 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
|
||||
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.
|
||||
* <p>Note that non-global JobListeners are not supported on Quartz 2.x -
|
||||
* manually register a Matcher against the Quartz ListenerManager instead.
|
||||
* @see org.quartz.JobListener#getName
|
||||
* @see JobDetailBean#setJobListenerNames
|
||||
* @deprecated as of Spring 4.0, since it only works on Quartz 1.x
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
@@ -227,23 +176,6 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
|
||||
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.
|
||||
* <p>Note that non-global TriggerListeners are not supported on Quartz 2.x -
|
||||
* manually register a Matcher against the Quartz ListenerManager instead.
|
||||
* @see org.quartz.TriggerListener#getName
|
||||
* @see CronTriggerBean#setTriggerListenerNames
|
||||
* @see SimpleTriggerBean#setTriggerListenerNames
|
||||
* @deprecated as of Spring 4.0, since it only works on Quartz 1.x
|
||||
*/
|
||||
@Deprecated
|
||||
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
|
||||
@@ -338,7 +270,7 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
|
||||
* @see #setOverwriteExistingJobs
|
||||
*/
|
||||
private boolean addJobToScheduler(JobDetail jobDetail) throws SchedulerException {
|
||||
if (this.overwriteExistingJobs || !jobDetailExists(jobDetail)) {
|
||||
if (this.overwriteExistingJobs || getScheduler().getJobDetail(jobDetail.getKey()) == null) {
|
||||
getScheduler().addJob(jobDetail, true);
|
||||
return true;
|
||||
}
|
||||
@@ -356,10 +288,10 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
|
||||
* @see #setOverwriteExistingJobs
|
||||
*/
|
||||
private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException {
|
||||
boolean triggerExists = triggerExists(trigger);
|
||||
boolean triggerExists = (getScheduler().getTrigger(trigger.getKey()) != null);
|
||||
if (!triggerExists || this.overwriteExistingJobs) {
|
||||
// Check if the Trigger is aware of an associated JobDetail.
|
||||
JobDetail jobDetail = findJobDetail(trigger);
|
||||
JobDetail jobDetail = (JobDetail) trigger.getJobDataMap().remove("jobDetail");
|
||||
if (jobDetail != null) {
|
||||
// Automatically register the JobDetail too.
|
||||
if (!this.jobDetails.contains(jobDetail) && addJobToScheduler(jobDetail)) {
|
||||
@@ -376,12 +308,12 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
|
||||
ex.getMessage() + " - can safely be ignored");
|
||||
}
|
||||
if (this.overwriteExistingJobs) {
|
||||
rescheduleJob(trigger);
|
||||
getScheduler().rescheduleJob(trigger.getKey(), trigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
rescheduleJob(trigger);
|
||||
getScheduler().rescheduleJob(trigger.getKey(), trigger);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -390,160 +322,25 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
|
||||
}
|
||||
}
|
||||
|
||||
private JobDetail findJobDetail(Trigger trigger) {
|
||||
if (trigger instanceof JobDetailAwareTrigger) {
|
||||
return ((JobDetailAwareTrigger) trigger).getJobDetail();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
Map<?, ?> jobDataMap =
|
||||
(Map<?, ?>) ReflectionUtils.invokeMethod(Trigger.class.getMethod("getJobDataMap"), trigger);
|
||||
return (JobDetail) jobDataMap.remove(JobDetailAwareTrigger.JOB_DETAIL_KEY);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Inconsistent Quartz API: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
|
||||
private boolean jobDetailExists(JobDetail jobDetail) throws SchedulerException {
|
||||
if (jobKeyClass != null) {
|
||||
try {
|
||||
Method getJobDetail = Scheduler.class.getMethod("getJobDetail", jobKeyClass);
|
||||
Object key = ReflectionUtils.invokeMethod(JobDetail.class.getMethod("getKey"), jobDetail);
|
||||
return (ReflectionUtils.invokeMethod(getJobDetail, getScheduler(), key) != null);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Inconsistent Quartz 2.0 API: " + ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return (getScheduler().getJobDetail(jobDetail.getName(), jobDetail.getGroup()) != null);
|
||||
}
|
||||
}
|
||||
|
||||
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
|
||||
private boolean triggerExists(Trigger trigger) throws SchedulerException {
|
||||
if (triggerKeyClass != null) {
|
||||
try {
|
||||
Method getTrigger = Scheduler.class.getMethod("getTrigger", triggerKeyClass);
|
||||
Object key = ReflectionUtils.invokeMethod(Trigger.class.getMethod("getKey"), trigger);
|
||||
return (ReflectionUtils.invokeMethod(getTrigger, getScheduler(), key) != null);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Inconsistent Quartz 2.0 API: " + ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return (getScheduler().getTrigger(trigger.getName(), trigger.getGroup()) != null);
|
||||
}
|
||||
}
|
||||
|
||||
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
|
||||
private void rescheduleJob(Trigger trigger) throws SchedulerException {
|
||||
if (triggerKeyClass != null) {
|
||||
try {
|
||||
Method rescheduleJob = Scheduler.class.getMethod("rescheduleJob", triggerKeyClass, Trigger.class);
|
||||
Object key = ReflectionUtils.invokeMethod(Trigger.class.getMethod("getKey"), trigger);
|
||||
ReflectionUtils.invokeMethod(rescheduleJob, getScheduler(), key, trigger);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Inconsistent Quartz 2.0 API: " + ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
getScheduler().rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register all specified listeners with the Scheduler.
|
||||
*/
|
||||
protected void registerListeners() throws SchedulerException {
|
||||
Object target;
|
||||
boolean quartz2;
|
||||
try {
|
||||
Method getListenerManager = Scheduler.class.getMethod("getListenerManager");
|
||||
target = ReflectionUtils.invokeMethod(getListenerManager, getScheduler());
|
||||
quartz2 = true;
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
target = getScheduler();
|
||||
quartz2 = false;
|
||||
}
|
||||
Class<?> targetClass = target.getClass();
|
||||
|
||||
try {
|
||||
if (this.schedulerListeners != null) {
|
||||
Method addSchedulerListener = targetClass.getMethod("addSchedulerListener", SchedulerListener.class);
|
||||
for (SchedulerListener listener : this.schedulerListeners) {
|
||||
ReflectionUtils.invokeMethod(addSchedulerListener, target, listener);
|
||||
}
|
||||
}
|
||||
if (this.globalJobListeners != null) {
|
||||
Method addJobListener;
|
||||
if (quartz2) {
|
||||
// addJobListener(JobListener) only introduced as late as Quartz 2.2, so we need
|
||||
// to fall back to the Quartz 2.0/2.1 compatible variant with an empty matchers List
|
||||
addJobListener = targetClass.getMethod("addJobListener", JobListener.class, List.class);
|
||||
}
|
||||
else {
|
||||
addJobListener = targetClass.getMethod("addGlobalJobListener", JobListener.class);
|
||||
}
|
||||
for (JobListener listener : this.globalJobListeners) {
|
||||
if (quartz2) {
|
||||
List<?> emptyMatchers = new LinkedList<Object>();
|
||||
ReflectionUtils.invokeMethod(addJobListener, target, listener, emptyMatchers);
|
||||
}
|
||||
else {
|
||||
ReflectionUtils.invokeMethod(addJobListener, target, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.jobListeners != null) {
|
||||
for (JobListener listener : this.jobListeners) {
|
||||
if (quartz2) {
|
||||
throw new IllegalStateException("Non-global JobListeners not supported on Quartz 2 - " +
|
||||
"manually register a Matcher against the Quartz ListenerManager instead");
|
||||
}
|
||||
getScheduler().addJobListener(listener);
|
||||
}
|
||||
}
|
||||
if (this.globalTriggerListeners != null) {
|
||||
Method addTriggerListener;
|
||||
if (quartz2) {
|
||||
// addTriggerListener(TriggerListener) only introduced as late as Quartz 2.2, so we need
|
||||
// to fall back to the Quartz 2.0/2.1 compatible variant with an empty matchers List
|
||||
addTriggerListener = targetClass.getMethod("addTriggerListener", TriggerListener.class, List.class);
|
||||
}
|
||||
else {
|
||||
addTriggerListener = targetClass.getMethod("addGlobalTriggerListener", TriggerListener.class);
|
||||
}
|
||||
for (TriggerListener listener : this.globalTriggerListeners) {
|
||||
if (quartz2) {
|
||||
List<?> emptyMatchers = new LinkedList<Object>();
|
||||
ReflectionUtils.invokeMethod(addTriggerListener, target, listener, emptyMatchers);
|
||||
}
|
||||
else {
|
||||
ReflectionUtils.invokeMethod(addTriggerListener, target, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.triggerListeners != null) {
|
||||
for (TriggerListener listener : this.triggerListeners) {
|
||||
if (quartz2) {
|
||||
throw new IllegalStateException("Non-global TriggerListeners not supported on Quartz 2 - " +
|
||||
"manually register a Matcher against the Quartz ListenerManager instead");
|
||||
}
|
||||
getScheduler().addTriggerListener(listener);
|
||||
}
|
||||
ListenerManager listenerManager = getScheduler().getListenerManager();
|
||||
if (this.schedulerListeners != null) {
|
||||
for (SchedulerListener listener : this.schedulerListeners) {
|
||||
listenerManager.addSchedulerListener(listener);
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Expected Quartz API not present: " + ex);
|
||||
if (this.globalJobListeners != null) {
|
||||
for (JobListener listener : this.globalJobListeners) {
|
||||
listenerManager.addJobListener(listener);
|
||||
}
|
||||
}
|
||||
if (this.globalTriggerListeners != null) {
|
||||
for (TriggerListener listener : this.globalTriggerListeners) {
|
||||
listenerManager.addTriggerListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -29,8 +29,7 @@ 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.
|
||||
*
|
||||
* <p>Compatible with Quartz 1.8 as well as Quartz 2.0-2.2, as of Spring 4.0.
|
||||
* <b>Note:</b> Quartz 1.x support is deprecated - please upgrade to Quartz 2.0+.
|
||||
* <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.6
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -75,8 +75,7 @@ import org.springframework.util.CollectionUtils;
|
||||
* automatically apply to Scheduler operations performed within those scopes.
|
||||
* Alternatively, you may add transactional advice for the Scheduler itself.
|
||||
*
|
||||
* <p>Compatible with Quartz 1.8 as well as Quartz 2.0-2.2, as of Spring 4.0.
|
||||
* <b>Note:</b> Quartz 1.x support is deprecated - please upgrade to Quartz 2.0+.
|
||||
* <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 18.02.2004
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.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>{@code 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: This convenience subclass does not work against Quartz 2.0.</b>
|
||||
* Use Quartz 2.0's native {@code JobDetailImpl} class or the new Quartz 2.0
|
||||
* builder API instead. Alternatively, switch to Spring's {@link SimpleTriggerFactoryBean}
|
||||
* which largely is a drop-in replacement for this class and its properties and
|
||||
* consistently works against Quartz 1.x as well as Quartz 2.x.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
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<String, ?> 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}.
|
||||
* @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
|
||||
* @deprecated as of Spring 4.0, since it only works on Quartz 1.x
|
||||
*/
|
||||
@Deprecated
|
||||
public void setTriggerListenerNames(String... names) {
|
||||
for (String name : names) {
|
||||
addTriggerListener(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start delay in milliseconds.
|
||||
* <p>The start delay is added to the current system time (when the bean starts)
|
||||
* to control the {@link #setStartTime start time} of the trigger.
|
||||
* <p>If the start delay is non-zero, it will <strong>always</strong>
|
||||
* take precedence over start time.
|
||||
* @param startDelay the start delay, in milliseconds
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobDetail getJobDetail() {
|
||||
return this.jobDetail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanName(String beanName) {
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (getName() == null) {
|
||||
setName(this.beanName);
|
||||
}
|
||||
if (getGroup() == null) {
|
||||
setGroup(Scheduler.DEFAULT_GROUP);
|
||||
}
|
||||
if (this.startDelay > 0 || getStartTime() == null) {
|
||||
setStartTime(new Date(System.currentTimeMillis() + this.startDelay));
|
||||
}
|
||||
if (this.jobDetail != null) {
|
||||
setJobName(this.jobDetail.getName());
|
||||
setJobGroup(this.jobDetail.getGroup());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.scheduling.quartz;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -24,16 +23,13 @@ import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SimpleTrigger;
|
||||
import org.quartz.impl.triggers.SimpleTriggerImpl;
|
||||
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.Constants;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* A Spring {@link FactoryBean} for creating a Quartz {@link org.quartz.SimpleTrigger}
|
||||
@@ -48,9 +44,6 @@ import org.springframework.util.ReflectionUtils;
|
||||
* to automatically register a trigger for the corresponding JobDetail,
|
||||
* instead of registering the JobDetail separately.
|
||||
*
|
||||
* <p><b>NOTE:</b> This FactoryBean works against both Quartz 1.x and Quartz 2.x,
|
||||
* in contrast to the older {@link SimpleTriggerBean} class.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
* @see #setName
|
||||
@@ -59,7 +52,6 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @see #setJobDetail
|
||||
* @see SchedulerFactoryBean#setTriggers
|
||||
* @see SchedulerFactoryBean#setJobDetails
|
||||
* @see CronTriggerBean
|
||||
*/
|
||||
public class SimpleTriggerFactoryBean implements FactoryBean<SimpleTrigger>, BeanNameAware, InitializingBean {
|
||||
|
||||
@@ -136,7 +128,6 @@ public class SimpleTriggerFactoryBean implements FactoryBean<SimpleTrigger>, Bea
|
||||
* 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 org.springframework.scheduling.quartz.JobDetailBean#setJobDataAsMap
|
||||
*/
|
||||
public void setJobDataAsMap(Map<String, ?> jobDataAsMap) {
|
||||
this.jobDataMap.putAll(jobDataAsMap);
|
||||
@@ -228,13 +219,12 @@ public class SimpleTriggerFactoryBean implements FactoryBean<SimpleTrigger>, Bea
|
||||
this.group = Scheduler.DEFAULT_GROUP;
|
||||
}
|
||||
if (this.jobDetail != null) {
|
||||
this.jobDataMap.put(JobDetailAwareTrigger.JOB_DETAIL_KEY, this.jobDetail);
|
||||
this.jobDataMap.put("jobDetail", this.jobDetail);
|
||||
}
|
||||
if (this.startDelay > 0 || this.startTime == null) {
|
||||
this.startTime = new Date(System.currentTimeMillis() + this.startDelay);
|
||||
}
|
||||
|
||||
/*
|
||||
SimpleTriggerImpl sti = new SimpleTriggerImpl();
|
||||
sti.setName(this.name);
|
||||
sti.setGroup(this.group);
|
||||
@@ -245,43 +235,8 @@ public class SimpleTriggerFactoryBean implements FactoryBean<SimpleTrigger>, Bea
|
||||
sti.setRepeatCount(this.repeatCount);
|
||||
sti.setPriority(this.priority);
|
||||
sti.setMisfireInstruction(this.misfireInstruction);
|
||||
cti.setDescription(this.description);
|
||||
sti.setDescription(this.description);
|
||||
this.simpleTrigger = sti;
|
||||
*/
|
||||
|
||||
Class<?> simpleTriggerClass;
|
||||
Method jobKeyMethod;
|
||||
try {
|
||||
simpleTriggerClass = getClass().getClassLoader().loadClass("org.quartz.impl.triggers.SimpleTriggerImpl");
|
||||
jobKeyMethod = JobDetail.class.getMethod("getKey");
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
simpleTriggerClass = SimpleTrigger.class;
|
||||
jobKeyMethod = null;
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Incompatible Quartz version");
|
||||
}
|
||||
BeanWrapper bw = new BeanWrapperImpl(simpleTriggerClass);
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("name", this.name);
|
||||
pvs.add("group", this.group);
|
||||
if (jobKeyMethod != null) {
|
||||
pvs.add("jobKey", ReflectionUtils.invokeMethod(jobKeyMethod, this.jobDetail));
|
||||
}
|
||||
else {
|
||||
pvs.add("jobName", this.jobDetail.getName());
|
||||
pvs.add("jobGroup", this.jobDetail.getGroup());
|
||||
}
|
||||
pvs.add("jobDataMap", this.jobDataMap);
|
||||
pvs.add("startTime", this.startTime);
|
||||
pvs.add("repeatInterval", this.repeatInterval);
|
||||
pvs.add("repeatCount", this.repeatCount);
|
||||
pvs.add("priority", this.priority);
|
||||
pvs.add("misfireInstruction", this.misfireInstruction);
|
||||
pvs.add("description", this.description);
|
||||
bw.setPropertyValues(pvs);
|
||||
this.simpleTrigger = (SimpleTrigger) bw.getWrappedInstance();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -16,29 +16,24 @@
|
||||
|
||||
package org.springframework.scheduling.quartz;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.quartz.JobDataMap;
|
||||
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.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* equivalent of Spring's {@link QuartzJobBean} in the shape of a Quartz
|
||||
* {@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.
|
||||
*
|
||||
* <p>Compatible with Quartz 1.8 as well as Quartz 2.0-2.2, as of Spring 4.0.
|
||||
* <b>Note:</b> Quartz 1.x support is deprecated - please upgrade to Quartz 2.0+.
|
||||
* <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
@@ -83,8 +78,8 @@ public class SpringBeanJobFactory extends AdaptableJobFactory implements Schedul
|
||||
if (this.schedulerContext != null) {
|
||||
pvs.addPropertyValues(this.schedulerContext);
|
||||
}
|
||||
pvs.addPropertyValues(getJobDetailDataMap(bundle));
|
||||
pvs.addPropertyValues(getTriggerDataMap(bundle));
|
||||
pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
|
||||
pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
|
||||
if (this.ignoredUnknownProperties != null) {
|
||||
for (String propName : this.ignoredUnknownProperties) {
|
||||
if (pvs.contains(propName) && !bw.isWritableProperty(propName)) {
|
||||
@@ -112,20 +107,4 @@ public class SpringBeanJobFactory extends AdaptableJobFactory implements Schedul
|
||||
return (!(jobObject instanceof QuartzJobBean));
|
||||
}
|
||||
|
||||
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
|
||||
private JobDataMap getJobDetailDataMap(TriggerFiredBundle bundle) throws Exception {
|
||||
Method getJobDetail = bundle.getClass().getMethod("getJobDetail");
|
||||
Object jobDetail = ReflectionUtils.invokeMethod(getJobDetail, bundle);
|
||||
Method getJobDataMap = jobDetail.getClass().getMethod("getJobDataMap");
|
||||
return (JobDataMap) ReflectionUtils.invokeMethod(getJobDataMap, jobDetail);
|
||||
}
|
||||
|
||||
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
|
||||
private JobDataMap getTriggerDataMap(TriggerFiredBundle bundle) throws Exception {
|
||||
Method getTrigger = bundle.getClass().getMethod("getTrigger");
|
||||
Object trigger = ReflectionUtils.invokeMethod(getTrigger, bundle);
|
||||
Method getJobDataMap = trigger.getClass().getMethod("getJobDataMap");
|
||||
return (JobDataMap) ReflectionUtils.invokeMethod(getJobDataMap, trigger);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.Date;
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/** @author Rob Harrop */
|
||||
public class CronTriggerBeanTests {
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testInvalidStartDelay() {
|
||||
createTriggerBean().setStartDelay(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartTime() throws Exception {
|
||||
CronTriggerBean bean = createTriggerBean();
|
||||
Date startTime = new Date(System.currentTimeMillis() + 1234L);
|
||||
bean.setStartTime(startTime);
|
||||
bean.afterPropertiesSet();
|
||||
assertTimeEquals(startTime, bean.getStartTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartDelay() throws Exception {
|
||||
CronTriggerBean bean = createTriggerBean();
|
||||
long startDelay = 1234L;
|
||||
Date startTime = new Date(System.currentTimeMillis() + startDelay);
|
||||
bean.setStartDelay(startDelay);
|
||||
bean.afterPropertiesSet();
|
||||
assertTimeEquals(startTime, bean.getStartTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartDelayOverridesStartTime() throws Exception {
|
||||
CronTriggerBean bean = createTriggerBean();
|
||||
long startDelay = 1234L;
|
||||
Date startTime = new Date(System.currentTimeMillis() + startDelay);
|
||||
bean.setStartTime(new Date(System.currentTimeMillis() + 123456789L));
|
||||
bean.setStartDelay(startDelay);
|
||||
bean.afterPropertiesSet();
|
||||
assertTimeEquals(startTime, bean.getStartTime());
|
||||
}
|
||||
|
||||
private CronTriggerBean createTriggerBean() {
|
||||
CronTriggerBean triggerBean = new CronTriggerBean();
|
||||
triggerBean.setName("test");
|
||||
return triggerBean;
|
||||
}
|
||||
|
||||
private void assertTimeEquals(Date a, Date b) {
|
||||
Calendar ca = Calendar.getInstance();
|
||||
ca.setTime(a);
|
||||
ca.set(Calendar.MILLISECOND, 0);
|
||||
Calendar cb = Calendar.getInstance();
|
||||
cb.setTime(b);
|
||||
cb.set(Calendar.MILLISECOND, 0);
|
||||
assertEquals(ca, cb);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,42 +16,29 @@
|
||||
|
||||
package org.springframework.scheduling.quartz;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.quartz.CronTrigger;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.quartz.JobListener;
|
||||
import org.quartz.ObjectAlreadyExistsException;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerContext;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.SchedulerFactory;
|
||||
import org.quartz.SchedulerListener;
|
||||
import org.quartz.SimpleTrigger;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.TriggerListener;
|
||||
import org.quartz.impl.JobDetailImpl;
|
||||
import org.quartz.impl.SchedulerRepository;
|
||||
import org.quartz.spi.JobFactory;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.support.StaticListableBeanFactory;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.tests.Assume;
|
||||
import org.springframework.tests.TestGroup;
|
||||
import org.springframework.tests.context.TestMethodInvokingTask;
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
@@ -68,473 +55,6 @@ import static org.mockito.BDDMockito.*;
|
||||
*/
|
||||
public class QuartzSupportTests {
|
||||
|
||||
@Test
|
||||
public void testSchedulerFactoryBean() throws Exception {
|
||||
doTestSchedulerFactoryBean(false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulerFactoryBeanWithExplicitJobDetail() throws Exception {
|
||||
doTestSchedulerFactoryBean(true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulerFactoryBeanWithPrototypeJob() throws Exception {
|
||||
doTestSchedulerFactoryBean(false, true);
|
||||
}
|
||||
|
||||
private void doTestSchedulerFactoryBean(boolean explicitJobDetail, boolean prototypeJob) throws Exception {
|
||||
TestBean tb = new TestBean("tb", 99);
|
||||
JobDetailBean jobDetail0 = new JobDetailBean();
|
||||
jobDetail0.setJobClass(Job.class);
|
||||
jobDetail0.setBeanName("myJob0");
|
||||
Map<String, Object> jobData = new HashMap<String, Object>();
|
||||
jobData.put("testBean", tb);
|
||||
jobDetail0.setJobDataAsMap(jobData);
|
||||
jobDetail0.afterPropertiesSet();
|
||||
assertEquals(tb, jobDetail0.getJobDataMap().get("testBean"));
|
||||
|
||||
CronTriggerBean trigger0 = new CronTriggerBean();
|
||||
trigger0.setBeanName("myTrigger0");
|
||||
trigger0.setJobDetail(jobDetail0);
|
||||
trigger0.setCronExpression("0/1 * * * * ?");
|
||||
trigger0.afterPropertiesSet();
|
||||
|
||||
TestMethodInvokingTask task1 = new TestMethodInvokingTask();
|
||||
MethodInvokingJobDetailFactoryBean mijdfb = new MethodInvokingJobDetailFactoryBean();
|
||||
mijdfb.setBeanName("myJob1");
|
||||
if (prototypeJob) {
|
||||
StaticListableBeanFactory beanFactory = new StaticListableBeanFactory();
|
||||
beanFactory.addBean("task", task1);
|
||||
mijdfb.setTargetBeanName("task");
|
||||
mijdfb.setBeanFactory(beanFactory);
|
||||
}
|
||||
else {
|
||||
mijdfb.setTargetObject(task1);
|
||||
}
|
||||
mijdfb.setTargetMethod("doSomething");
|
||||
mijdfb.afterPropertiesSet();
|
||||
JobDetail jobDetail1 = mijdfb.getObject();
|
||||
|
||||
SimpleTriggerBean trigger1 = new SimpleTriggerBean();
|
||||
trigger1.setBeanName("myTrigger1");
|
||||
trigger1.setJobDetail(jobDetail1);
|
||||
trigger1.setStartDelay(0);
|
||||
trigger1.setRepeatInterval(20);
|
||||
trigger1.afterPropertiesSet();
|
||||
|
||||
final Scheduler scheduler = mock(Scheduler.class);
|
||||
given(scheduler.getContext()).willReturn(new SchedulerContext());
|
||||
given(scheduler.scheduleJob(trigger0)).willReturn(new Date());
|
||||
given(scheduler.scheduleJob(trigger1)).willReturn(new Date());
|
||||
|
||||
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean() {
|
||||
@Override
|
||||
protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName) {
|
||||
return scheduler;
|
||||
}
|
||||
};
|
||||
schedulerFactoryBean.setJobFactory(null);
|
||||
Map<String, Object> schedulerContext = new HashMap<String, Object>();
|
||||
schedulerContext.put("otherTestBean", tb);
|
||||
schedulerFactoryBean.setSchedulerContextAsMap(schedulerContext);
|
||||
if (explicitJobDetail) {
|
||||
schedulerFactoryBean.setJobDetails(new JobDetail[] {jobDetail0});
|
||||
}
|
||||
schedulerFactoryBean.setTriggers(new Trigger[] {trigger0, trigger1});
|
||||
try {
|
||||
schedulerFactoryBean.afterPropertiesSet();
|
||||
schedulerFactoryBean.start();
|
||||
}
|
||||
finally {
|
||||
schedulerFactoryBean.destroy();
|
||||
}
|
||||
|
||||
verify(scheduler).getJobDetail("myJob0", Scheduler.DEFAULT_GROUP);
|
||||
verify(scheduler).getJobDetail("myJob1", Scheduler.DEFAULT_GROUP);
|
||||
verify(scheduler).getTrigger("myTrigger0", Scheduler.DEFAULT_GROUP);
|
||||
verify(scheduler).getTrigger("myTrigger1", Scheduler.DEFAULT_GROUP);
|
||||
verify(scheduler).addJob(jobDetail0, true);
|
||||
verify(scheduler).addJob(jobDetail1, true);
|
||||
verify(scheduler).start();
|
||||
verify(scheduler).shutdown(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulerFactoryBeanWithExistingJobs() throws Exception {
|
||||
doTestSchedulerFactoryBeanWithExistingJobs(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulerFactoryBeanWithOverwriteExistingJobs() throws Exception {
|
||||
doTestSchedulerFactoryBeanWithExistingJobs(true);
|
||||
}
|
||||
|
||||
private void doTestSchedulerFactoryBeanWithExistingJobs(boolean overwrite) throws Exception {
|
||||
TestBean tb = new TestBean("tb", 99);
|
||||
JobDetailBean jobDetail0 = new JobDetailBean();
|
||||
jobDetail0.setJobClass(Job.class);
|
||||
jobDetail0.setBeanName("myJob0");
|
||||
Map<String, Object> jobData = new HashMap<String, Object>();
|
||||
jobData.put("testBean", tb);
|
||||
jobDetail0.setJobDataAsMap(jobData);
|
||||
jobDetail0.afterPropertiesSet();
|
||||
assertEquals(tb, jobDetail0.getJobDataMap().get("testBean"));
|
||||
|
||||
CronTriggerBean trigger0 = new CronTriggerBean();
|
||||
trigger0.setBeanName("myTrigger0");
|
||||
trigger0.setJobDetail(jobDetail0);
|
||||
trigger0.setCronExpression("0/1 * * * * ?");
|
||||
trigger0.afterPropertiesSet();
|
||||
|
||||
TestMethodInvokingTask task1 = new TestMethodInvokingTask();
|
||||
MethodInvokingJobDetailFactoryBean mijdfb = new MethodInvokingJobDetailFactoryBean();
|
||||
mijdfb.setBeanName("myJob1");
|
||||
mijdfb.setTargetObject(task1);
|
||||
mijdfb.setTargetMethod("doSomething");
|
||||
mijdfb.afterPropertiesSet();
|
||||
JobDetail jobDetail1 = mijdfb.getObject();
|
||||
|
||||
SimpleTriggerBean trigger1 = new SimpleTriggerBean();
|
||||
trigger1.setBeanName("myTrigger1");
|
||||
trigger1.setJobDetail(jobDetail1);
|
||||
trigger1.setStartDelay(0);
|
||||
trigger1.setRepeatInterval(20);
|
||||
trigger1.afterPropertiesSet();
|
||||
|
||||
final Scheduler scheduler = mock(Scheduler.class);
|
||||
given(scheduler.getContext()).willReturn(new SchedulerContext());
|
||||
given(scheduler.rescheduleJob("myTrigger1", Scheduler.DEFAULT_GROUP, trigger1)).willReturn(new Date());
|
||||
given(scheduler.getTrigger("myTrigger1", Scheduler.DEFAULT_GROUP)).willReturn(new SimpleTrigger());
|
||||
given(scheduler.scheduleJob(trigger0)).willReturn(new Date());
|
||||
|
||||
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean() {
|
||||
@Override
|
||||
protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName) {
|
||||
return scheduler;
|
||||
}
|
||||
};
|
||||
schedulerFactoryBean.setJobFactory(null);
|
||||
Map<String, Object> schedulerContext = new HashMap<String, Object>();
|
||||
schedulerContext.put("otherTestBean", tb);
|
||||
schedulerFactoryBean.setSchedulerContextAsMap(schedulerContext);
|
||||
schedulerFactoryBean.setTriggers(new Trigger[] {trigger0, trigger1});
|
||||
if (overwrite) {
|
||||
schedulerFactoryBean.setOverwriteExistingJobs(true);
|
||||
}
|
||||
try {
|
||||
schedulerFactoryBean.afterPropertiesSet();
|
||||
schedulerFactoryBean.start();
|
||||
}
|
||||
finally {
|
||||
schedulerFactoryBean.destroy();
|
||||
}
|
||||
|
||||
verify(scheduler).getTrigger("myTrigger0", Scheduler.DEFAULT_GROUP);
|
||||
verify(scheduler).getTrigger("myTrigger1", Scheduler.DEFAULT_GROUP);
|
||||
if (overwrite) {
|
||||
verify(scheduler).addJob(jobDetail1, true);
|
||||
verify(scheduler).rescheduleJob("myTrigger1", Scheduler.DEFAULT_GROUP, trigger1);
|
||||
}
|
||||
else {
|
||||
verify(scheduler).getJobDetail("myJob0", Scheduler.DEFAULT_GROUP);
|
||||
}
|
||||
verify(scheduler).addJob(jobDetail0, true);
|
||||
verify(scheduler).start();
|
||||
verify(scheduler).shutdown(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulerFactoryBeanWithExistingJobsAndRaceCondition() throws Exception {
|
||||
doTestSchedulerFactoryBeanWithExistingJobsAndRaceCondition(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulerFactoryBeanWithOverwriteExistingJobsAndRaceCondition() throws Exception {
|
||||
doTestSchedulerFactoryBeanWithExistingJobsAndRaceCondition(true);
|
||||
}
|
||||
|
||||
private void doTestSchedulerFactoryBeanWithExistingJobsAndRaceCondition(boolean overwrite) throws Exception {
|
||||
TestBean tb = new TestBean("tb", 99);
|
||||
JobDetailBean jobDetail0 = new JobDetailBean();
|
||||
jobDetail0.setJobClass(Job.class);
|
||||
jobDetail0.setBeanName("myJob0");
|
||||
Map<String, Object> jobData = new HashMap<String, Object>();
|
||||
jobData.put("testBean", tb);
|
||||
jobDetail0.setJobDataAsMap(jobData);
|
||||
jobDetail0.afterPropertiesSet();
|
||||
assertEquals(tb, jobDetail0.getJobDataMap().get("testBean"));
|
||||
|
||||
CronTriggerBean trigger0 = new CronTriggerBean();
|
||||
trigger0.setBeanName("myTrigger0");
|
||||
trigger0.setJobDetail(jobDetail0);
|
||||
trigger0.setCronExpression("0/1 * * * * ?");
|
||||
trigger0.afterPropertiesSet();
|
||||
|
||||
TestMethodInvokingTask task1 = new TestMethodInvokingTask();
|
||||
MethodInvokingJobDetailFactoryBean mijdfb = new MethodInvokingJobDetailFactoryBean();
|
||||
mijdfb.setBeanName("myJob1");
|
||||
mijdfb.setTargetObject(task1);
|
||||
mijdfb.setTargetMethod("doSomething");
|
||||
mijdfb.afterPropertiesSet();
|
||||
JobDetail jobDetail1 = mijdfb.getObject();
|
||||
|
||||
SimpleTriggerBean trigger1 = new SimpleTriggerBean();
|
||||
trigger1.setBeanName("myTrigger1");
|
||||
trigger1.setJobDetail(jobDetail1);
|
||||
trigger1.setStartDelay(0);
|
||||
trigger1.setRepeatInterval(20);
|
||||
trigger1.afterPropertiesSet();
|
||||
|
||||
final Scheduler scheduler = mock(Scheduler.class);
|
||||
given(scheduler.getContext()).willReturn(new SchedulerContext());
|
||||
given(scheduler.getTrigger("myTrigger1", Scheduler.DEFAULT_GROUP)).willReturn(new SimpleTrigger());
|
||||
if (overwrite) {
|
||||
given(scheduler.rescheduleJob("myTrigger1", Scheduler.DEFAULT_GROUP, trigger1)).willReturn(new Date());
|
||||
}
|
||||
given(scheduler.scheduleJob(trigger0)).willThrow(new ObjectAlreadyExistsException(""));
|
||||
if (overwrite) {
|
||||
given(scheduler.rescheduleJob("myTrigger0", Scheduler.DEFAULT_GROUP, trigger0)).willReturn(new Date());
|
||||
}
|
||||
|
||||
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean() {
|
||||
@Override
|
||||
protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName) {
|
||||
return scheduler;
|
||||
}
|
||||
};
|
||||
schedulerFactoryBean.setJobFactory(null);
|
||||
Map<String, Object> schedulerContext = new HashMap<String, Object>();
|
||||
schedulerContext.put("otherTestBean", tb);
|
||||
schedulerFactoryBean.setSchedulerContextAsMap(schedulerContext);
|
||||
schedulerFactoryBean.setTriggers(new Trigger[] {trigger0, trigger1});
|
||||
if (overwrite) {
|
||||
schedulerFactoryBean.setOverwriteExistingJobs(true);
|
||||
}
|
||||
try {
|
||||
schedulerFactoryBean.afterPropertiesSet();
|
||||
schedulerFactoryBean.start();
|
||||
}
|
||||
finally {
|
||||
schedulerFactoryBean.destroy();
|
||||
}
|
||||
|
||||
verify(scheduler).getTrigger("myTrigger0", Scheduler.DEFAULT_GROUP);
|
||||
verify(scheduler).getTrigger("myTrigger1", Scheduler.DEFAULT_GROUP);
|
||||
if (overwrite) {
|
||||
verify(scheduler).addJob(jobDetail1, true);
|
||||
verify(scheduler).rescheduleJob("myTrigger1", Scheduler.DEFAULT_GROUP, trigger1);
|
||||
}
|
||||
else {
|
||||
verify(scheduler).getJobDetail("myJob0", Scheduler.DEFAULT_GROUP);
|
||||
}
|
||||
verify(scheduler).addJob(jobDetail0, true);
|
||||
verify(scheduler).start();
|
||||
verify(scheduler).shutdown(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testSchedulerFactoryBeanWithListeners() throws Exception {
|
||||
JobFactory jobFactory = new AdaptableJobFactory();
|
||||
|
||||
final Scheduler scheduler = mock(Scheduler.class);
|
||||
|
||||
SchedulerListener schedulerListener = new TestSchedulerListener();
|
||||
JobListener globalJobListener = new TestJobListener();
|
||||
JobListener jobListener = new TestJobListener();
|
||||
TriggerListener globalTriggerListener = new TestTriggerListener();
|
||||
TriggerListener triggerListener = new TestTriggerListener();
|
||||
|
||||
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean() {
|
||||
@Override
|
||||
protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName) {
|
||||
return scheduler;
|
||||
}
|
||||
};
|
||||
schedulerFactoryBean.setJobFactory(jobFactory);
|
||||
schedulerFactoryBean.setSchedulerListeners(new SchedulerListener[] {schedulerListener});
|
||||
schedulerFactoryBean.setGlobalJobListeners(new JobListener[] {globalJobListener});
|
||||
schedulerFactoryBean.setJobListeners(new JobListener[] {jobListener});
|
||||
schedulerFactoryBean.setGlobalTriggerListeners(new TriggerListener[] {globalTriggerListener});
|
||||
schedulerFactoryBean.setTriggerListeners(new TriggerListener[] {triggerListener});
|
||||
try {
|
||||
schedulerFactoryBean.afterPropertiesSet();
|
||||
schedulerFactoryBean.start();
|
||||
}
|
||||
finally {
|
||||
schedulerFactoryBean.destroy();
|
||||
}
|
||||
|
||||
verify(scheduler).setJobFactory(jobFactory);
|
||||
verify(scheduler).addSchedulerListener(schedulerListener);
|
||||
verify(scheduler).addGlobalJobListener(globalJobListener);
|
||||
verify(scheduler).addJobListener(jobListener);
|
||||
verify(scheduler).addGlobalTriggerListener(globalTriggerListener);
|
||||
verify(scheduler).addTriggerListener(triggerListener);
|
||||
verify(scheduler).start();
|
||||
verify(scheduler).shutdown(false);
|
||||
}
|
||||
|
||||
@Ignore @Test
|
||||
public void testMethodInvocationWithConcurrency() throws Exception {
|
||||
Assume.group(TestGroup.PERFORMANCE);
|
||||
methodInvokingConcurrency(true);
|
||||
}
|
||||
|
||||
// We can't test both since Quartz somehow seems to keep things in memory
|
||||
// enable both and one of them will fail (order doesn't matter).
|
||||
@Ignore @Test
|
||||
public void testMethodInvocationWithoutConcurrency() throws Exception {
|
||||
Assume.group(TestGroup.PERFORMANCE);
|
||||
methodInvokingConcurrency(false);
|
||||
}
|
||||
|
||||
private void methodInvokingConcurrency(boolean concurrent) throws Exception {
|
||||
// Test the concurrency flag.
|
||||
// Method invoking job with two triggers.
|
||||
// If the concurrent flag is false, the triggers are NOT allowed
|
||||
// to interfere with each other.
|
||||
|
||||
TestMethodInvokingTask task1 = new TestMethodInvokingTask();
|
||||
MethodInvokingJobDetailFactoryBean mijdfb = new MethodInvokingJobDetailFactoryBean();
|
||||
// set the concurrency flag!
|
||||
mijdfb.setConcurrent(concurrent);
|
||||
mijdfb.setBeanName("myJob1");
|
||||
mijdfb.setTargetObject(task1);
|
||||
mijdfb.setTargetMethod("doWait");
|
||||
mijdfb.afterPropertiesSet();
|
||||
JobDetail jobDetail1 = mijdfb.getObject();
|
||||
|
||||
SimpleTriggerBean trigger0 = new SimpleTriggerBean();
|
||||
trigger0.setBeanName("myTrigger1");
|
||||
trigger0.setJobDetail(jobDetail1);
|
||||
trigger0.setStartDelay(0);
|
||||
trigger0.setRepeatInterval(1);
|
||||
trigger0.setRepeatCount(1);
|
||||
trigger0.afterPropertiesSet();
|
||||
|
||||
SimpleTriggerBean trigger1 = new SimpleTriggerBean();
|
||||
trigger1.setBeanName("myTrigger1");
|
||||
trigger1.setJobDetail(jobDetail1);
|
||||
trigger1.setStartDelay(1000L);
|
||||
trigger1.setRepeatInterval(1);
|
||||
trigger1.setRepeatCount(1);
|
||||
trigger1.afterPropertiesSet();
|
||||
|
||||
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
|
||||
schedulerFactoryBean.setJobDetails(new JobDetail[] {jobDetail1});
|
||||
schedulerFactoryBean.setTriggers(new Trigger[] {trigger1, trigger0});
|
||||
schedulerFactoryBean.afterPropertiesSet();
|
||||
|
||||
// ok scheduler is set up... let's wait for like 4 seconds
|
||||
try {
|
||||
Thread.sleep(4000);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
// fall through
|
||||
}
|
||||
|
||||
if (concurrent) {
|
||||
assertEquals(2, task1.counter);
|
||||
task1.stop();
|
||||
// we're done, both jobs have ran, let's call it a day
|
||||
return;
|
||||
}
|
||||
else {
|
||||
assertEquals(1, task1.counter);
|
||||
task1.stop();
|
||||
// we need to check whether or not the test succeed with non-concurrent jobs
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(4000);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
// fall through
|
||||
}
|
||||
|
||||
task1.stop();
|
||||
assertEquals(2, task1.counter);
|
||||
|
||||
// Although we're destroying the scheduler, it does seem to keep things in memory:
|
||||
// When executing both tests (concurrent and non-concurrent), the second test always
|
||||
// fails.
|
||||
schedulerFactoryBean.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulerFactoryBeanWithPlainQuartzObjects() throws Exception {
|
||||
JobFactory jobFactory = new AdaptableJobFactory();
|
||||
|
||||
TestBean tb = new TestBean("tb", 99);
|
||||
JobDetail jobDetail0 = new JobDetail();
|
||||
jobDetail0.setJobClass(Job.class);
|
||||
jobDetail0.setName("myJob0");
|
||||
jobDetail0.setGroup(Scheduler.DEFAULT_GROUP);
|
||||
jobDetail0.getJobDataMap().put("testBean", tb);
|
||||
assertEquals(tb, jobDetail0.getJobDataMap().get("testBean"));
|
||||
|
||||
CronTrigger trigger0 = new CronTrigger();
|
||||
trigger0.setName("myTrigger0");
|
||||
trigger0.setGroup(Scheduler.DEFAULT_GROUP);
|
||||
trigger0.setJobName("myJob0");
|
||||
trigger0.setJobGroup(Scheduler.DEFAULT_GROUP);
|
||||
trigger0.setStartTime(new Date());
|
||||
trigger0.setCronExpression("0/1 * * * * ?");
|
||||
|
||||
TestMethodInvokingTask task1 = new TestMethodInvokingTask();
|
||||
MethodInvokingJobDetailFactoryBean mijdfb = new MethodInvokingJobDetailFactoryBean();
|
||||
mijdfb.setName("myJob1");
|
||||
mijdfb.setGroup(Scheduler.DEFAULT_GROUP);
|
||||
mijdfb.setTargetObject(task1);
|
||||
mijdfb.setTargetMethod("doSomething");
|
||||
mijdfb.afterPropertiesSet();
|
||||
JobDetail jobDetail1 = mijdfb.getObject();
|
||||
|
||||
SimpleTrigger trigger1 = new SimpleTrigger();
|
||||
trigger1.setName("myTrigger1");
|
||||
trigger1.setGroup(Scheduler.DEFAULT_GROUP);
|
||||
trigger1.setJobName("myJob1");
|
||||
trigger1.setJobGroup(Scheduler.DEFAULT_GROUP);
|
||||
trigger1.setStartTime(new Date());
|
||||
trigger1.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
|
||||
trigger1.setRepeatInterval(20);
|
||||
|
||||
final Scheduler scheduler = mock(Scheduler.class);
|
||||
given(scheduler.scheduleJob(trigger0)).willReturn(new Date());
|
||||
given(scheduler.scheduleJob(trigger1)).willReturn(new Date());
|
||||
|
||||
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean() {
|
||||
@Override
|
||||
protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName) {
|
||||
return scheduler;
|
||||
}
|
||||
};
|
||||
schedulerFactoryBean.setJobFactory(jobFactory);
|
||||
schedulerFactoryBean.setJobDetails(new JobDetail[] {jobDetail0, jobDetail1});
|
||||
schedulerFactoryBean.setTriggers(new Trigger[] {trigger0, trigger1});
|
||||
try {
|
||||
schedulerFactoryBean.afterPropertiesSet();
|
||||
schedulerFactoryBean.start();
|
||||
}
|
||||
finally {
|
||||
schedulerFactoryBean.destroy();
|
||||
}
|
||||
|
||||
verify(scheduler).setJobFactory(jobFactory);
|
||||
verify(scheduler).getJobDetail("myJob0", Scheduler.DEFAULT_GROUP);
|
||||
verify(scheduler).getJobDetail("myJob1", Scheduler.DEFAULT_GROUP);
|
||||
verify(scheduler).getTrigger("myTrigger0", Scheduler.DEFAULT_GROUP);
|
||||
verify(scheduler).getTrigger("myTrigger1", Scheduler.DEFAULT_GROUP);
|
||||
verify(scheduler).addJob(jobDetail0, true);
|
||||
verify(scheduler).addJob(jobDetail1, true);
|
||||
verify(scheduler).scheduleJob(trigger0);
|
||||
verify(scheduler).scheduleJob(trigger1);
|
||||
verify(scheduler).start();
|
||||
verify(scheduler).shutdown(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulerFactoryBeanWithApplicationContext() throws Exception {
|
||||
TestBean tb = new TestBean("tb", 99);
|
||||
@@ -571,68 +91,6 @@ public class QuartzSupportTests {
|
||||
verify(scheduler).shutdown(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJobDetailBeanWithApplicationContext() throws Exception {
|
||||
TestBean tb = new TestBean("tb", 99);
|
||||
StaticApplicationContext ac = new StaticApplicationContext();
|
||||
|
||||
JobDetailBean jobDetail = new JobDetailBean();
|
||||
jobDetail.setJobClass(Job.class);
|
||||
jobDetail.setBeanName("myJob0");
|
||||
Map<String, Object> jobData = new HashMap<String, Object>();
|
||||
jobData.put("testBean", tb);
|
||||
jobDetail.setJobDataAsMap(jobData);
|
||||
jobDetail.setApplicationContext(ac);
|
||||
jobDetail.setApplicationContextJobDataKey("appCtx");
|
||||
jobDetail.afterPropertiesSet();
|
||||
|
||||
assertEquals(tb, jobDetail.getJobDataMap().get("testBean"));
|
||||
assertEquals(ac, jobDetail.getJobDataMap().get("appCtx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testMethodInvokingJobDetailFactoryBeanWithListenerNames() throws Exception {
|
||||
TestMethodInvokingTask task = new TestMethodInvokingTask();
|
||||
MethodInvokingJobDetailFactoryBean mijdfb = new MethodInvokingJobDetailFactoryBean();
|
||||
String[] names = new String[] {"test1", "test2"};
|
||||
mijdfb.setName("myJob1");
|
||||
mijdfb.setGroup(Scheduler.DEFAULT_GROUP);
|
||||
mijdfb.setTargetObject(task);
|
||||
mijdfb.setTargetMethod("doSomething");
|
||||
mijdfb.setJobListenerNames(names);
|
||||
mijdfb.afterPropertiesSet();
|
||||
JobDetail jobDetail = mijdfb.getObject();
|
||||
assertArrayEquals(names, jobDetail.getJobListenerNames());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testJobDetailBeanWithListenerNames() {
|
||||
JobDetailBean jobDetail = new JobDetailBean();
|
||||
String[] names = new String[] {"test1", "test2"};
|
||||
jobDetail.setJobListenerNames(names);
|
||||
assertArrayEquals(names, jobDetail.getJobListenerNames());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testCronTriggerBeanWithListenerNames() {
|
||||
CronTriggerBean trigger = new CronTriggerBean();
|
||||
String[] names = new String[] {"test1", "test2"};
|
||||
trigger.setTriggerListenerNames(names);
|
||||
assertArrayEquals(names, trigger.getTriggerListenerNames());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testSimpleTriggerBeanWithListenerNames() {
|
||||
SimpleTriggerBean trigger = new SimpleTriggerBean();
|
||||
String[] names = new String[] {"test1", "test2"};
|
||||
trigger.setTriggerListenerNames(names);
|
||||
assertArrayEquals(names, trigger.getTriggerListenerNames());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulerWithTaskExecutor() throws Exception {
|
||||
Assume.group(TestGroup.PERFORMANCE);
|
||||
@@ -640,11 +98,11 @@ public class QuartzSupportTests {
|
||||
CountingTaskExecutor taskExecutor = new CountingTaskExecutor();
|
||||
DummyJob.count = 0;
|
||||
|
||||
JobDetail jobDetail = new JobDetail();
|
||||
JobDetailImpl jobDetail = new JobDetailImpl();
|
||||
jobDetail.setJobClass(DummyJob.class);
|
||||
jobDetail.setName("myJob");
|
||||
|
||||
SimpleTriggerBean trigger = new SimpleTriggerBean();
|
||||
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
|
||||
trigger.setName("myTrigger");
|
||||
trigger.setJobDetail(jobDetail);
|
||||
trigger.setStartDelay(1);
|
||||
@@ -654,8 +112,8 @@ public class QuartzSupportTests {
|
||||
|
||||
SchedulerFactoryBean bean = new SchedulerFactoryBean();
|
||||
bean.setTaskExecutor(taskExecutor);
|
||||
bean.setTriggers(new Trigger[] {trigger});
|
||||
bean.setJobDetails(new JobDetail[] {jobDetail});
|
||||
bean.setTriggers(trigger.getObject());
|
||||
bean.setJobDetails(jobDetail);
|
||||
bean.afterPropertiesSet();
|
||||
bean.start();
|
||||
|
||||
@@ -672,11 +130,11 @@ public class QuartzSupportTests {
|
||||
|
||||
DummyRunnable.count = 0;
|
||||
|
||||
JobDetail jobDetail = new JobDetailBean();
|
||||
jobDetail.setJobClass(DummyRunnable.class);
|
||||
JobDetailImpl jobDetail = new JobDetailImpl();
|
||||
jobDetail.setJobClass((Class) DummyRunnable.class);
|
||||
jobDetail.setName("myJob");
|
||||
|
||||
SimpleTriggerBean trigger = new SimpleTriggerBean();
|
||||
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
|
||||
trigger.setName("myTrigger");
|
||||
trigger.setJobDetail(jobDetail);
|
||||
trigger.setStartDelay(1);
|
||||
@@ -685,8 +143,8 @@ public class QuartzSupportTests {
|
||||
trigger.afterPropertiesSet();
|
||||
|
||||
SchedulerFactoryBean bean = new SchedulerFactoryBean();
|
||||
bean.setTriggers(new Trigger[] {trigger});
|
||||
bean.setJobDetails(new JobDetail[] {jobDetail});
|
||||
bean.setTriggers(trigger.getObject());
|
||||
bean.setJobDetails(jobDetail);
|
||||
bean.afterPropertiesSet();
|
||||
bean.start();
|
||||
|
||||
@@ -703,12 +161,12 @@ public class QuartzSupportTests {
|
||||
DummyJob.param = 0;
|
||||
DummyJob.count = 0;
|
||||
|
||||
JobDetail jobDetail = new JobDetail();
|
||||
JobDetailImpl jobDetail = new JobDetailImpl();
|
||||
jobDetail.setJobClass(DummyJobBean.class);
|
||||
jobDetail.setName("myJob");
|
||||
jobDetail.getJobDataMap().put("param", "10");
|
||||
|
||||
SimpleTriggerBean trigger = new SimpleTriggerBean();
|
||||
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
|
||||
trigger.setName("myTrigger");
|
||||
trigger.setJobDetail(jobDetail);
|
||||
trigger.setStartDelay(1);
|
||||
@@ -717,8 +175,8 @@ public class QuartzSupportTests {
|
||||
trigger.afterPropertiesSet();
|
||||
|
||||
SchedulerFactoryBean bean = new SchedulerFactoryBean();
|
||||
bean.setTriggers(new Trigger[] {trigger});
|
||||
bean.setJobDetails(new JobDetail[] {jobDetail});
|
||||
bean.setTriggers(trigger.getObject());
|
||||
bean.setJobDetails(jobDetail);
|
||||
bean.afterPropertiesSet();
|
||||
bean.start();
|
||||
|
||||
@@ -736,13 +194,13 @@ public class QuartzSupportTests {
|
||||
DummyJob.param = 0;
|
||||
DummyJob.count = 0;
|
||||
|
||||
JobDetail jobDetail = new JobDetail();
|
||||
JobDetailImpl jobDetail = new JobDetailImpl();
|
||||
jobDetail.setJobClass(DummyJob.class);
|
||||
jobDetail.setName("myJob");
|
||||
jobDetail.getJobDataMap().put("param", "10");
|
||||
jobDetail.getJobDataMap().put("ignoredParam", "10");
|
||||
|
||||
SimpleTriggerBean trigger = new SimpleTriggerBean();
|
||||
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
|
||||
trigger.setName("myTrigger");
|
||||
trigger.setJobDetail(jobDetail);
|
||||
trigger.setStartDelay(1);
|
||||
@@ -752,8 +210,8 @@ public class QuartzSupportTests {
|
||||
|
||||
SchedulerFactoryBean bean = new SchedulerFactoryBean();
|
||||
bean.setJobFactory(new SpringBeanJobFactory());
|
||||
bean.setTriggers(new Trigger[] {trigger});
|
||||
bean.setJobDetails(new JobDetail[] {jobDetail});
|
||||
bean.setTriggers(trigger.getObject());
|
||||
bean.setJobDetails(jobDetail);
|
||||
bean.afterPropertiesSet();
|
||||
bean.start();
|
||||
|
||||
@@ -767,16 +225,17 @@ public class QuartzSupportTests {
|
||||
@Test
|
||||
public void testSchedulerWithSpringBeanJobFactoryAndParamMismatchNotIgnored() throws Exception {
|
||||
Assume.group(TestGroup.PERFORMANCE);
|
||||
|
||||
DummyJob.param = 0;
|
||||
DummyJob.count = 0;
|
||||
|
||||
JobDetail jobDetail = new JobDetail();
|
||||
JobDetailImpl jobDetail = new JobDetailImpl();
|
||||
jobDetail.setJobClass(DummyJob.class);
|
||||
jobDetail.setName("myJob");
|
||||
jobDetail.getJobDataMap().put("para", "10");
|
||||
jobDetail.getJobDataMap().put("ignoredParam", "10");
|
||||
|
||||
SimpleTriggerBean trigger = new SimpleTriggerBean();
|
||||
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
|
||||
trigger.setName("myTrigger");
|
||||
trigger.setJobDetail(jobDetail);
|
||||
trigger.setStartDelay(1);
|
||||
@@ -786,10 +245,10 @@ public class QuartzSupportTests {
|
||||
|
||||
SchedulerFactoryBean bean = new SchedulerFactoryBean();
|
||||
SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
|
||||
jobFactory.setIgnoredUnknownProperties(new String[] {"ignoredParam"});
|
||||
jobFactory.setIgnoredUnknownProperties("ignoredParam");
|
||||
bean.setJobFactory(jobFactory);
|
||||
bean.setTriggers(new Trigger[] {trigger});
|
||||
bean.setJobDetails(new JobDetail[] {jobDetail});
|
||||
bean.setTriggers(trigger.getObject());
|
||||
bean.setJobDetails(jobDetail);
|
||||
bean.afterPropertiesSet();
|
||||
|
||||
Thread.sleep(500);
|
||||
@@ -806,12 +265,12 @@ public class QuartzSupportTests {
|
||||
DummyRunnable.param = 0;
|
||||
DummyRunnable.count = 0;
|
||||
|
||||
JobDetail jobDetail = new JobDetailBean();
|
||||
jobDetail.setJobClass(DummyRunnable.class);
|
||||
JobDetailImpl jobDetail = new JobDetailImpl();
|
||||
jobDetail.setJobClass((Class) DummyRunnable.class);
|
||||
jobDetail.setName("myJob");
|
||||
jobDetail.getJobDataMap().put("param", "10");
|
||||
|
||||
SimpleTriggerBean trigger = new SimpleTriggerBean();
|
||||
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
|
||||
trigger.setName("myTrigger");
|
||||
trigger.setJobDetail(jobDetail);
|
||||
trigger.setStartDelay(1);
|
||||
@@ -821,8 +280,8 @@ public class QuartzSupportTests {
|
||||
|
||||
SchedulerFactoryBean bean = new SchedulerFactoryBean();
|
||||
bean.setJobFactory(new SpringBeanJobFactory());
|
||||
bean.setTriggers(new Trigger[] {trigger});
|
||||
bean.setJobDetails(new JobDetail[] {jobDetail});
|
||||
bean.setTriggers(trigger.getObject());
|
||||
bean.setJobDetails(jobDetail);
|
||||
bean.afterPropertiesSet();
|
||||
bean.start();
|
||||
|
||||
@@ -839,12 +298,12 @@ public class QuartzSupportTests {
|
||||
DummyJobBean.param = 0;
|
||||
DummyJobBean.count = 0;
|
||||
|
||||
JobDetail jobDetail = new JobDetail();
|
||||
JobDetailImpl jobDetail = new JobDetailImpl();
|
||||
jobDetail.setJobClass(DummyJobBean.class);
|
||||
jobDetail.setName("myJob");
|
||||
jobDetail.getJobDataMap().put("param", "10");
|
||||
|
||||
SimpleTriggerBean trigger = new SimpleTriggerBean();
|
||||
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
|
||||
trigger.setName("myTrigger");
|
||||
trigger.setJobDetail(jobDetail);
|
||||
trigger.setStartDelay(1);
|
||||
@@ -854,8 +313,8 @@ public class QuartzSupportTests {
|
||||
|
||||
SchedulerFactoryBean bean = new SchedulerFactoryBean();
|
||||
bean.setJobFactory(new SpringBeanJobFactory());
|
||||
bean.setTriggers(new Trigger[] {trigger});
|
||||
bean.setJobDetails(new JobDetail[] {jobDetail});
|
||||
bean.setTriggers(trigger.getObject());
|
||||
bean.setJobDetails(jobDetail);
|
||||
bean.afterPropertiesSet();
|
||||
bean.start();
|
||||
|
||||
@@ -992,116 +451,10 @@ public class QuartzSupportTests {
|
||||
try {
|
||||
// assertEquals(10, DummyJob.param);
|
||||
assertTrue(DummyJob.count > 0);
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestSchedulerListener implements SchedulerListener {
|
||||
|
||||
@Override
|
||||
public void jobScheduled(Trigger trigger) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jobUnscheduled(String triggerName, String triggerGroup) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerFinalized(Trigger trigger) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggersPaused(String triggerName, String triggerGroup) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggersResumed(String triggerName, String triggerGroup) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jobsPaused(String jobName, String jobGroup) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jobsResumed(String jobName, String jobGroup) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedulerError(String msg, SchedulerException cause) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedulerShutdown() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jobAdded(JobDetail jobDetail) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jobDeleted(String s, String s1) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedulerInStandbyMode() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedulerStarted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedulerShuttingdown() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestJobListener implements JobListener {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jobToBeExecuted(JobExecutionContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jobExecutionVetoed(JobExecutionContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestTriggerListener implements TriggerListener {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerFired(Trigger trigger, JobExecutionContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerMisfired(Trigger trigger) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerComplete(Trigger trigger, JobExecutionContext context, int triggerInstructionCode) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<bean id="trigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
|
||||
<bean id="trigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
||||
<property name="repeatInterval" value="1000" />
|
||||
<property name="repeatCount" value="1" />
|
||||
<property name="jobDetail">
|
||||
<bean class="org.springframework.scheduling.quartz.JobDetailBean">
|
||||
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
|
||||
<property name="jobDataAsMap">
|
||||
<map>
|
||||
<entry key="param" value="10" />
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="exportTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
|
||||
<bean id="exportTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
||||
<property name="jobDetail">
|
||||
<bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="targetObject" ref="exportService"/>
|
||||
@@ -23,7 +23,7 @@
|
||||
<property name="repeatCount" value="1"/>
|
||||
</bean>
|
||||
|
||||
<bean id="importTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
|
||||
<bean id="importTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
||||
<property name="jobDetail">
|
||||
<bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="targetObject" ref="importService"/>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="exportTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
|
||||
<bean id="exportTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
||||
<property name="jobDetail">
|
||||
<bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="targetObject" ref="exportService"/>
|
||||
@@ -26,7 +26,7 @@
|
||||
<property name="repeatCount" value="1"/>
|
||||
</bean>
|
||||
|
||||
<bean id="importTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
|
||||
<bean id="importTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
||||
<property name="jobDetail">
|
||||
<bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="targetObject" ref="importService"/>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="exportTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
|
||||
<bean id="exportTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
||||
<property name="jobDetail">
|
||||
<bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="targetObject" ref="exportService"/>
|
||||
@@ -29,7 +29,7 @@
|
||||
<property name="repeatCount" value="1"/>
|
||||
</bean>
|
||||
|
||||
<bean id="importTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
|
||||
<bean id="importTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
||||
<property name="jobDetail">
|
||||
<bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="targetObject" ref="importService"/>
|
||||
|
||||
@@ -1,383 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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.jms.support.converter;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.jms.BytesMessage;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TextMessage;
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.type.JavaType;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Message converter that uses Jackson 1.x to convert messages to and from JSON.
|
||||
* Maps an object to a {@link BytesMessage}, or to a {@link TextMessage} if the
|
||||
* {@link #setTargetType targetType} is set to {@link MessageType#TEXT}.
|
||||
* Converts from a {@link TextMessage} or {@link BytesMessage} to an object.
|
||||
*
|
||||
* <p><b>NOTE:</b> Requires Jackson 1.8 or higher, as of Spring 4.0.
|
||||
* At the same time, we strongly recommend a migration to Jackson 2.x!
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Dave Syer
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
* @deprecated Please migrate to {@link MappingJackson2MessageConverter} for Jackson 2.x.
|
||||
*/
|
||||
@Deprecated
|
||||
public class MappingJacksonMessageConverter implements MessageConverter, BeanClassLoaderAware {
|
||||
|
||||
/**
|
||||
* The default encoding used for writing to text messages: UTF-8.
|
||||
*/
|
||||
public static final String DEFAULT_ENCODING = "UTF-8";
|
||||
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private MessageType targetType = MessageType.BYTES;
|
||||
|
||||
private String encoding = DEFAULT_ENCODING;
|
||||
|
||||
private String encodingPropertyName;
|
||||
|
||||
private String typeIdPropertyName;
|
||||
|
||||
private Map<String, Class<?>> idClassMappings = new HashMap<String, Class<?>>();
|
||||
|
||||
private Map<Class<?>, String> classIdMappings = new HashMap<Class<?>, String>();
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
|
||||
/**
|
||||
* Specify the {@link ObjectMapper} to use instead of using the default.
|
||||
*/
|
||||
public void setObjectMapper(ObjectMapper objectMapper) {
|
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether {@link #toMessage(Object, Session)} should marshal to a
|
||||
* {@link BytesMessage} or a {@link TextMessage}.
|
||||
* <p>The default is {@link MessageType#BYTES}, i.e. this converter marshals to
|
||||
* a {@link BytesMessage}. Note that the default version of this converter
|
||||
* supports {@link MessageType#BYTES} and {@link MessageType#TEXT} only.
|
||||
* @see MessageType#BYTES
|
||||
* @see MessageType#TEXT
|
||||
*/
|
||||
public void setTargetType(MessageType targetType) {
|
||||
Assert.notNull(targetType, "MessageType must not be null");
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the encoding to use when converting to and from text-based
|
||||
* message body content. The default encoding will be "UTF-8".
|
||||
* <p>When reading from a a text-based message, an encoding may have been
|
||||
* suggested through a special JMS property which will then be preferred
|
||||
* over the encoding set on this MessageConverter instance.
|
||||
* @see #setEncodingPropertyName
|
||||
*/
|
||||
public void setEncoding(String encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the name of the JMS message property that carries the encoding from
|
||||
* bytes to String and back is BytesMessage is used during the conversion process.
|
||||
* <p>Default is none. Setting this property is optional; if not set, UTF-8 will
|
||||
* be used for decoding any incoming bytes message.
|
||||
* @see #setEncoding
|
||||
*/
|
||||
public void setEncodingPropertyName(String encodingPropertyName) {
|
||||
this.encodingPropertyName = encodingPropertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the name of the JMS message property that carries the type id for the
|
||||
* contained object: either a mapped id value or a raw Java class name.
|
||||
* <p>Default is none. <b>NOTE: This property needs to be set in order to allow
|
||||
* for converting from an incoming message to a Java object.</b>
|
||||
* @see #setTypeIdMappings
|
||||
*/
|
||||
public void setTypeIdPropertyName(String typeIdPropertyName) {
|
||||
this.typeIdPropertyName = typeIdPropertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify mappings from type ids to Java classes, if desired.
|
||||
* This allows for synthetic ids in the type id message property,
|
||||
* instead of transferring Java class names.
|
||||
* <p>Default is no custom mappings, i.e. transferring raw Java class names.
|
||||
* @param typeIdMappings a Map with type id values as keys and Java classes as values
|
||||
*/
|
||||
public void setTypeIdMappings(Map<String, Class<?>> typeIdMappings) {
|
||||
this.idClassMappings = new HashMap<String, Class<?>>();
|
||||
for (Map.Entry<String, Class<?>> entry : typeIdMappings.entrySet()) {
|
||||
String id = entry.getKey();
|
||||
Class<?> clazz = entry.getValue();
|
||||
this.idClassMappings.put(id, clazz);
|
||||
this.classIdMappings.put(clazz, id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
|
||||
Message message;
|
||||
try {
|
||||
switch (this.targetType) {
|
||||
case TEXT:
|
||||
message = mapToTextMessage(object, session, this.objectMapper);
|
||||
break;
|
||||
case BYTES:
|
||||
message = mapToBytesMessage(object, session, this.objectMapper);
|
||||
break;
|
||||
default:
|
||||
message = mapToMessage(object, session, this.objectMapper, this.targetType);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new MessageConversionException("Could not map JSON object [" + object + "]", ex);
|
||||
}
|
||||
setTypeIdOnMessage(object, message);
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
|
||||
try {
|
||||
JavaType targetJavaType = getJavaTypeForMessage(message);
|
||||
return convertToObject(message, targetJavaType);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new MessageConversionException("Failed to convert JSON message content", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map the given object to a {@link TextMessage}.
|
||||
* @param object the object to be mapped
|
||||
* @param session current JMS session
|
||||
* @param objectMapper the mapper to use
|
||||
* @return the resulting message
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @throws IOException in case of I/O errors
|
||||
* @see Session#createBytesMessage
|
||||
*/
|
||||
protected TextMessage mapToTextMessage(Object object, Session session, ObjectMapper objectMapper)
|
||||
throws JMSException, IOException {
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
objectMapper.writeValue(writer, object);
|
||||
return session.createTextMessage(writer.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the given object to a {@link BytesMessage}.
|
||||
* @param object the object to be mapped
|
||||
* @param session current JMS session
|
||||
* @param objectMapper the mapper to use
|
||||
* @return the resulting message
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @throws IOException in case of I/O errors
|
||||
* @see Session#createBytesMessage
|
||||
*/
|
||||
protected BytesMessage mapToBytesMessage(Object object, Session session, ObjectMapper objectMapper)
|
||||
throws JMSException, IOException {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
|
||||
OutputStreamWriter writer = new OutputStreamWriter(bos, this.encoding);
|
||||
objectMapper.writeValue(writer, object);
|
||||
|
||||
BytesMessage message = session.createBytesMessage();
|
||||
message.writeBytes(bos.toByteArray());
|
||||
if (this.encodingPropertyName != null) {
|
||||
message.setStringProperty(this.encodingPropertyName, this.encoding);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method that allows for custom message mapping.
|
||||
* Invoked when {@link #setTargetType} is not {@link MessageType#TEXT} or
|
||||
* {@link MessageType#BYTES}.
|
||||
* <p>The default implementation throws an {@link IllegalArgumentException}.
|
||||
* @param object the object to marshal
|
||||
* @param session the JMS Session
|
||||
* @param objectMapper the mapper to use
|
||||
* @param targetType the target message type (other than TEXT or BYTES)
|
||||
* @return the resulting message
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
protected Message mapToMessage(Object object, Session session, ObjectMapper objectMapper, MessageType targetType)
|
||||
throws JMSException, IOException {
|
||||
|
||||
throw new IllegalArgumentException("Unsupported message type [" + targetType +
|
||||
"]. MappingJacksonMessageConverter by default only supports TextMessages and BytesMessages.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a type id for the given payload object on the given JMS Message.
|
||||
* <p>The default implementation consults the configured type id mapping and
|
||||
* sets the resulting value (either a mapped id or the raw Java class name)
|
||||
* into the configured type id message property.
|
||||
* @param object the payload object to set a type id for
|
||||
* @param message the JMS Message to set the type id on
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @see #getJavaTypeForMessage(javax.jms.Message)
|
||||
* @see #setTypeIdPropertyName(String)
|
||||
* @see #setTypeIdMappings(java.util.Map)
|
||||
*/
|
||||
protected void setTypeIdOnMessage(Object object, Message message) throws JMSException {
|
||||
if (this.typeIdPropertyName != null) {
|
||||
String typeId = this.classIdMappings.get(object.getClass());
|
||||
if (typeId == null) {
|
||||
typeId = object.getClass().getName();
|
||||
}
|
||||
message.setStringProperty(this.typeIdPropertyName, typeId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method to dispatch to converters for individual message types.
|
||||
*/
|
||||
private Object convertToObject(Message message, JavaType targetJavaType) throws JMSException, IOException {
|
||||
if (message instanceof TextMessage) {
|
||||
return convertFromTextMessage((TextMessage) message, targetJavaType);
|
||||
}
|
||||
else if (message instanceof BytesMessage) {
|
||||
return convertFromBytesMessage((BytesMessage) message, targetJavaType);
|
||||
}
|
||||
else {
|
||||
return convertFromMessage(message, targetJavaType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a TextMessage to a Java Object with the specified type.
|
||||
* @param message the input message
|
||||
* @param targetJavaType the target type
|
||||
* @return the message converted to an object
|
||||
* @throws JMSException if thrown by JMS
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
protected Object convertFromTextMessage(TextMessage message, JavaType targetJavaType)
|
||||
throws JMSException, IOException {
|
||||
|
||||
String body = message.getText();
|
||||
return this.objectMapper.readValue(body, targetJavaType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a BytesMessage to a Java Object with the specified type.
|
||||
* @param message the input message
|
||||
* @param targetJavaType the target type
|
||||
* @return the message converted to an object
|
||||
* @throws JMSException if thrown by JMS
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
protected Object convertFromBytesMessage(BytesMessage message, JavaType targetJavaType)
|
||||
throws JMSException, IOException {
|
||||
|
||||
String encoding = this.encoding;
|
||||
if (this.encodingPropertyName != null && message.propertyExists(this.encodingPropertyName)) {
|
||||
encoding = message.getStringProperty(this.encodingPropertyName);
|
||||
}
|
||||
byte[] bytes = new byte[(int) message.getBodyLength()];
|
||||
message.readBytes(bytes);
|
||||
try {
|
||||
String body = new String(bytes, encoding);
|
||||
return this.objectMapper.readValue(body, targetJavaType);
|
||||
}
|
||||
catch (UnsupportedEncodingException ex) {
|
||||
throw new MessageConversionException("Cannot convert bytes to String", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method that allows for custom message mapping.
|
||||
* Invoked when {@link #setTargetType} is not {@link MessageType#TEXT} or
|
||||
* {@link MessageType#BYTES}.
|
||||
* <p>The default implementation throws an {@link IllegalArgumentException}.
|
||||
* @param message the input message
|
||||
* @param targetJavaType the target type
|
||||
* @return the message converted to an object
|
||||
* @throws JMSException if thrown by JMS
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
protected Object convertFromMessage(Message message, JavaType targetJavaType)
|
||||
throws JMSException, IOException {
|
||||
|
||||
throw new IllegalArgumentException("Unsupported message type [" + message.getClass() +
|
||||
"]. MappingJacksonMessageConverter by default only supports TextMessages and BytesMessages.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a Jackson JavaType for the given JMS Message,
|
||||
* typically parsing a type id message property.
|
||||
* <p>The default implementation parses the configured type id property name
|
||||
* and consults the configured type id mapping. This can be overridden with
|
||||
* a different strategy, e.g. doing some heuristics based on message origin.
|
||||
* @param message the JMS Message to set the type id on
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @see #setTypeIdOnMessage(Object, javax.jms.Message)
|
||||
* @see #setTypeIdPropertyName(String)
|
||||
* @see #setTypeIdMappings(java.util.Map)
|
||||
*/
|
||||
protected JavaType getJavaTypeForMessage(Message message) throws JMSException {
|
||||
String typeId = message.getStringProperty(this.typeIdPropertyName);
|
||||
if (typeId == null) {
|
||||
throw new MessageConversionException("Could not find type id property [" + this.typeIdPropertyName + "]");
|
||||
}
|
||||
Class<?> mappedClass = this.idClassMappings.get(typeId);
|
||||
if (mappedClass != null) {
|
||||
return this.objectMapper.getTypeFactory().constructType(mappedClass);
|
||||
}
|
||||
try {
|
||||
Class<?> typeClass = ClassUtils.forName(typeId, this.beanClassLoader);
|
||||
return this.objectMapper.getTypeFactory().constructType(typeClass);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new MessageConversionException("Failed to resolve type id [" + typeId + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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.jms.support.converter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.jms.BytesMessage;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TextMessage;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.BDDMockito.*;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class MappingJacksonMessageConverterTests {
|
||||
|
||||
private MappingJacksonMessageConverter converter;
|
||||
|
||||
private Session sessionMock;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
sessionMock = mock(Session.class);
|
||||
converter = new MappingJacksonMessageConverter();
|
||||
converter.setEncodingPropertyName("__encoding__");
|
||||
converter.setTypeIdPropertyName("__typeid__");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void toBytesMessage() throws Exception {
|
||||
BytesMessage bytesMessageMock = mock(BytesMessage.class);
|
||||
Date toBeMarshalled = new Date();
|
||||
|
||||
given(sessionMock.createBytesMessage()).willReturn(bytesMessageMock);
|
||||
|
||||
converter.toMessage(toBeMarshalled, sessionMock);
|
||||
|
||||
verify(bytesMessageMock).setStringProperty("__encoding__", "UTF-8");
|
||||
verify(bytesMessageMock).setStringProperty("__typeid__", Date.class.getName());
|
||||
verify(bytesMessageMock).writeBytes(isA(byte[].class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromBytesMessage() throws Exception {
|
||||
BytesMessage bytesMessageMock = mock(BytesMessage.class);
|
||||
Map<String, String> unmarshalled = Collections.singletonMap("foo", "bar");
|
||||
|
||||
byte[] bytes = "{\"foo\":\"bar\"}".getBytes();
|
||||
final ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
|
||||
|
||||
given(bytesMessageMock.getStringProperty("__typeid__")).willReturn(Object.class.getName());
|
||||
given(bytesMessageMock.propertyExists("__encoding__")).willReturn(false);
|
||||
given(bytesMessageMock.getBodyLength()).willReturn(new Long(bytes.length));
|
||||
given(bytesMessageMock.readBytes(any(byte[].class))).willAnswer(
|
||||
new Answer<Integer>() {
|
||||
@Override
|
||||
public Integer answer(InvocationOnMock invocation) throws Throwable {
|
||||
return byteStream.read((byte[]) invocation.getArguments()[0]);
|
||||
}
|
||||
});
|
||||
|
||||
Object result = converter.fromMessage(bytesMessageMock);
|
||||
assertEquals("Invalid result", result, unmarshalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toTextMessageWithObject() throws Exception {
|
||||
converter.setTargetType(MessageType.TEXT);
|
||||
TextMessage textMessageMock = mock(TextMessage.class);
|
||||
Date toBeMarshalled = new Date();
|
||||
|
||||
given(sessionMock.createTextMessage(isA(String.class))).willReturn(textMessageMock);
|
||||
|
||||
converter.toMessage(toBeMarshalled, sessionMock);
|
||||
verify(textMessageMock).setStringProperty("__typeid__", Date.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toTextMessageWithMap() throws Exception {
|
||||
converter.setTargetType(MessageType.TEXT);
|
||||
TextMessage textMessageMock = mock(TextMessage.class);
|
||||
Map<String, String> toBeMarshalled = new HashMap<String, String>();
|
||||
toBeMarshalled.put("foo", "bar");
|
||||
|
||||
given(sessionMock.createTextMessage(isA(String.class))).willReturn(textMessageMock);
|
||||
|
||||
converter.toMessage(toBeMarshalled, sessionMock);
|
||||
verify(textMessageMock).setStringProperty("__typeid__", HashMap.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromTextMessageAsObject() throws Exception {
|
||||
TextMessage textMessageMock = mock(TextMessage.class);
|
||||
Map<String, String> unmarshalled = Collections.singletonMap("foo", "bar");
|
||||
|
||||
String text = "{\"foo\":\"bar\"}";
|
||||
given(textMessageMock.getStringProperty("__typeid__")).willReturn(Object.class.getName());
|
||||
given(textMessageMock.getText()).willReturn(text);
|
||||
|
||||
Object result = converter.fromMessage(textMessageMock);
|
||||
assertEquals("Invalid result", result, unmarshalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromTextMessageAsMap() throws Exception {
|
||||
TextMessage textMessageMock = mock(TextMessage.class);
|
||||
Map<String, String> unmarshalled = Collections.singletonMap("foo", "bar");
|
||||
|
||||
String text = "{\"foo\":\"bar\"}";
|
||||
given(textMessageMock.getStringProperty("__typeid__")).willReturn(HashMap.class.getName());
|
||||
given(textMessageMock.getText()).willReturn(text);
|
||||
|
||||
Object result = converter.fromMessage(textMessageMock);
|
||||
assertEquals("Invalid result", result, unmarshalled);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.http.converter.json;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.JsonParser;
|
||||
import org.codehaus.jackson.map.AnnotationIntrospector;
|
||||
import org.codehaus.jackson.map.DeserializationConfig;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* A {@link FactoryBean} for creating a Jackson 1.x {@link ObjectMapper} with setters
|
||||
* to enable or disable Jackson features from within XML configuration.
|
||||
*
|
||||
* <p>Example usage with MappingJacksonHttpMessageConverter:
|
||||
* <pre class="code">
|
||||
* <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
|
||||
* <property name="objectMapper">
|
||||
* <bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean"
|
||||
* p:autoDetectFields="false"
|
||||
* p:autoDetectGettersSetters="false"
|
||||
* p:annotationIntrospector-ref="jaxbAnnotationIntrospector" />
|
||||
* </property>
|
||||
* </bean>
|
||||
* </pre>
|
||||
*
|
||||
* <p>Example usage with MappingJacksonJsonView:
|
||||
* <pre class="code">
|
||||
* <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
|
||||
* <property name="objectMapper">
|
||||
* <bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean"
|
||||
* p:autoDetectFields="false"
|
||||
* p:autoDetectGettersSetters="false"
|
||||
* p:annotationIntrospector-ref="jaxbAnnotationIntrospector" />
|
||||
* </property>
|
||||
* </bean>
|
||||
* </pre>
|
||||
*
|
||||
* <p>In case there are no specific setters provided (for some rarely used
|
||||
* options), you can still use the more general methods
|
||||
* {@link #setFeaturesToEnable(Object[])} and {@link #setFeaturesToDisable(Object[])}.
|
||||
*
|
||||
* <pre class="code">
|
||||
* <bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean">
|
||||
* <property name="featuresToEnable">
|
||||
* <array>
|
||||
* <util:constant static-field="org.codehaus.jackson.map.SerializationConfig$Feature.WRAP_ROOT_VALUE"/>
|
||||
* <util:constant static-field="org.codehaus.jackson.map.SerializationConfig$Feature.CLOSE_CLOSEABLE"/>
|
||||
* </array>
|
||||
* </property>
|
||||
* <property name="featuresToDisable">
|
||||
* <array>
|
||||
* <util:constant static-field="org.codehaus.jackson.map.DeserializationConfig$Feature.USE_ANNOTATIONS"/>
|
||||
* </array>
|
||||
* </property>
|
||||
* </bean>
|
||||
* </pre>
|
||||
*
|
||||
* <p><b>NOTE:</b> Requires Jackson 1.8 or higher, as of Spring 4.0.
|
||||
* At the same time, we strongly recommend a migration to Jackson 2.x!
|
||||
*
|
||||
* @author <a href="mailto:dmitry.katsubo@gmail.com">Dmitry Katsubo</a>
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
* @deprecated Please migrate to {@link Jackson2ObjectMapperFactoryBean} for Jackson 2.x.
|
||||
*/
|
||||
@Deprecated
|
||||
public class JacksonObjectMapperFactoryBean implements FactoryBean<ObjectMapper>, InitializingBean {
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private Map<Object, Boolean> features = new HashMap<Object, Boolean>();
|
||||
|
||||
private DateFormat dateFormat;
|
||||
|
||||
private AnnotationIntrospector annotationIntrospector;
|
||||
|
||||
|
||||
/**
|
||||
* Set the ObjectMapper instance to use.
|
||||
* If not set an instance will be created using the default constructor.
|
||||
*/
|
||||
public void setObjectMapper(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the format for date/time with the given {@link DateFormat}.
|
||||
* <p>Note: Setting this property makes the exposed {@link ObjectMapper}
|
||||
* non-thread-safe, according to Jackson's thread safety rules.
|
||||
* @see #setSimpleDateFormat(String)
|
||||
*/
|
||||
public void setDateFormat(DateFormat dateFormat) {
|
||||
this.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the date/time format with a {@link SimpleDateFormat}.
|
||||
* <p>Note: Setting this property makes the exposed {@link ObjectMapper}
|
||||
* non-thread-safe, according to Jackson's thread safety rules.
|
||||
* @see #setDateFormat(DateFormat)
|
||||
*/
|
||||
public void setSimpleDateFormat(String format) {
|
||||
this.dateFormat = new SimpleDateFormat(format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link AnnotationIntrospector} for serialization and deserialization.
|
||||
* @see SerializationConfig#setAnnotationIntrospector(AnnotationIntrospector)
|
||||
* @see DeserializationConfig#setAnnotationIntrospector(AnnotationIntrospector)
|
||||
*/
|
||||
public void setAnnotationIntrospector(AnnotationIntrospector annotationIntrospector) {
|
||||
this.annotationIntrospector = annotationIntrospector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for {@link org.codehaus.jackson.map.SerializationConfig.Feature#AUTO_DETECT_FIELDS} and
|
||||
* {@link org.codehaus.jackson.map.DeserializationConfig.Feature#AUTO_DETECT_FIELDS}.
|
||||
*/
|
||||
public void setAutoDetectFields(boolean autoDetectFields) {
|
||||
this.features.put(SerializationConfig.Feature.AUTO_DETECT_FIELDS, autoDetectFields);
|
||||
this.features.put(DeserializationConfig.Feature.AUTO_DETECT_FIELDS, autoDetectFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for {@link org.codehaus.jackson.map.SerializationConfig.Feature#AUTO_DETECT_GETTERS} and
|
||||
* {@link org.codehaus.jackson.map.DeserializationConfig.Feature#AUTO_DETECT_SETTERS}.
|
||||
*/
|
||||
public void setAutoDetectGettersSetters(boolean autoDetectGettersSetters) {
|
||||
this.features.put(SerializationConfig.Feature.AUTO_DETECT_GETTERS, autoDetectGettersSetters);
|
||||
this.features.put(DeserializationConfig.Feature.AUTO_DETECT_SETTERS, autoDetectGettersSetters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for {@link org.codehaus.jackson.map.SerializationConfig.Feature#FAIL_ON_EMPTY_BEANS}.
|
||||
*/
|
||||
public void setFailOnEmptyBeans(boolean failOnEmptyBeans) {
|
||||
this.features.put(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, failOnEmptyBeans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for {@link org.codehaus.jackson.map.SerializationConfig.Feature#INDENT_OUTPUT}.
|
||||
*/
|
||||
public void setIndentOutput(boolean indentOutput) {
|
||||
this.features.put(SerializationConfig.Feature.INDENT_OUTPUT, indentOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify features to enable.
|
||||
* @see org.codehaus.jackson.JsonParser.Feature
|
||||
* @see org.codehaus.jackson.JsonGenerator.Feature
|
||||
* @see org.codehaus.jackson.map.SerializationConfig.Feature
|
||||
* @see org.codehaus.jackson.map.DeserializationConfig.Feature
|
||||
*/
|
||||
public void setFeaturesToEnable(Object[] featuresToEnable) {
|
||||
if (featuresToEnable != null) {
|
||||
for (Object feature : featuresToEnable) {
|
||||
this.features.put(feature, Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify features to disable.
|
||||
* @see org.codehaus.jackson.JsonParser.Feature
|
||||
* @see org.codehaus.jackson.JsonGenerator.Feature
|
||||
* @see org.codehaus.jackson.map.SerializationConfig.Feature
|
||||
* @see org.codehaus.jackson.map.DeserializationConfig.Feature
|
||||
*/
|
||||
public void setFeaturesToDisable(Object[] featuresToDisable) {
|
||||
if (featuresToDisable != null) {
|
||||
for (Object feature : featuresToDisable) {
|
||||
this.features.put(feature, Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (this.objectMapper == null) {
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
if (this.annotationIntrospector != null) {
|
||||
this.objectMapper.setSerializationConfig(
|
||||
this.objectMapper.getSerializationConfig().withAnnotationIntrospector(this.annotationIntrospector));
|
||||
this.objectMapper.setDeserializationConfig(
|
||||
this.objectMapper.getDeserializationConfig().withAnnotationIntrospector(this.annotationIntrospector));
|
||||
}
|
||||
if (this.dateFormat != null) {
|
||||
this.objectMapper.setDateFormat(this.dateFormat);
|
||||
}
|
||||
for (Map.Entry<Object, Boolean> entry : this.features.entrySet()) {
|
||||
configureFeature(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void configureFeature(Object feature, boolean enabled) {
|
||||
if (feature instanceof JsonParser.Feature) {
|
||||
this.objectMapper.configure((JsonParser.Feature) feature, enabled);
|
||||
}
|
||||
else if (feature instanceof JsonGenerator.Feature) {
|
||||
this.objectMapper.configure((JsonGenerator.Feature) feature, enabled);
|
||||
}
|
||||
else if (feature instanceof SerializationConfig.Feature) {
|
||||
this.objectMapper.configure((SerializationConfig.Feature) feature, enabled);
|
||||
}
|
||||
else if (feature instanceof DeserializationConfig.Feature) {
|
||||
this.objectMapper.configure((DeserializationConfig.Feature) feature, enabled);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown feature class: " + feature.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the singleton ObjectMapper.
|
||||
*/
|
||||
@Override
|
||||
public ObjectMapper getObject() {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return ObjectMapper.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.http.converter.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.codehaus.jackson.JsonEncoding;
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.JsonProcessingException;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
import org.codehaus.jackson.type.JavaType;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that
|
||||
* can read and write JSON using <a href="http://jackson.codehaus.org/">Jackson 1.x's</a> {@link ObjectMapper}.
|
||||
*
|
||||
* <p>This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
|
||||
*
|
||||
* <p>By default, this converter supports {@code application/json}. This can be overridden by setting the
|
||||
* {@link #setSupportedMediaTypes supportedMediaTypes} property.
|
||||
*
|
||||
* <p><b>NOTE:</b> Requires Jackson 1.8 or higher, as of Spring 4.0.
|
||||
* At the same time, we strongly recommend a migration to Jackson 2.x!
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @deprecated Please migrate to {@link MappingJackson2HttpMessageConverter} for Jackson 2.x.
|
||||
*/
|
||||
@Deprecated
|
||||
public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter<Object>
|
||||
implements GenericHttpMessageConverter<Object> {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private String jsonPrefix;
|
||||
|
||||
private Boolean prettyPrint;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@code MappingJacksonHttpMessageConverter}.
|
||||
*/
|
||||
public MappingJacksonHttpMessageConverter() {
|
||||
super(new MediaType("application", "json", DEFAULT_CHARSET),
|
||||
new MediaType("application", "*+json", DEFAULT_CHARSET));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@code ObjectMapper} for this view.
|
||||
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used.
|
||||
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control of the JSON
|
||||
* serialization process. For example, an extended {@link org.codehaus.jackson.map.SerializerFactory}
|
||||
* can be configured that provides custom serializers for specific types. The other option for refining
|
||||
* the serialization process is to use Jackson's provided annotations on the types to be serialized,
|
||||
* in which case a custom-configured ObjectMapper is unnecessary.
|
||||
*/
|
||||
public void setObjectMapper(ObjectMapper objectMapper) {
|
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
||||
this.objectMapper = objectMapper;
|
||||
configurePrettyPrint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying {@code ObjectMapper} for this view.
|
||||
*/
|
||||
public ObjectMapper getObjectMapper() {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a custom prefix to use for this view's JSON output.
|
||||
* Default is none.
|
||||
* @see #setPrefixJson
|
||||
*/
|
||||
public void setJsonPrefix(String jsonPrefix) {
|
||||
this.jsonPrefix = jsonPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the JSON output by this view should be prefixed with "{} &&". Default is false.
|
||||
* <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
|
||||
* The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
|
||||
* This prefix does not affect the evaluation of JSON, but if JSON validation is performed on the
|
||||
* string, the prefix would need to be ignored.
|
||||
* @see #setJsonPrefix
|
||||
*/
|
||||
public void setPrefixJson(boolean prefixJson) {
|
||||
this.jsonPrefix = (prefixJson ? "{} && " : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use the {@link org.codehaus.jackson.util.DefaultPrettyPrinter} when writing JSON.
|
||||
* This is a shortcut for setting up an {@code ObjectMapper} as follows:
|
||||
* <pre class="code">
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
|
||||
* converter.setObjectMapper(mapper);
|
||||
* </pre>
|
||||
* <p>The default value is {@code false}.
|
||||
*/
|
||||
public void setPrettyPrint(boolean prettyPrint) {
|
||||
this.prettyPrint = prettyPrint;
|
||||
configurePrettyPrint();
|
||||
}
|
||||
|
||||
private void configurePrettyPrint() {
|
||||
if (this.prettyPrint != null) {
|
||||
this.objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, this.prettyPrint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) {
|
||||
return canRead(clazz, null, mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
|
||||
JavaType javaType = getJavaType(type, contextClass);
|
||||
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
|
||||
return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
// should not be called, since we override canRead/Write instead
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
JavaType javaType = getJavaType(clazz, null);
|
||||
return readJavaType(javaType, inputMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
JavaType javaType = getJavaType(type, contextClass);
|
||||
return readJavaType(javaType, inputMessage);
|
||||
}
|
||||
|
||||
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
|
||||
try {
|
||||
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
|
||||
JsonGenerator jsonGenerator =
|
||||
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
|
||||
|
||||
// A workaround for JsonGenerators not applying serialization features
|
||||
// https://github.com/FasterXML/jackson-databind/issues/12
|
||||
if (this.objectMapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.INDENT_OUTPUT)) {
|
||||
jsonGenerator.useDefaultPrettyPrinter();
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.jsonPrefix != null) {
|
||||
jsonGenerator.writeRaw(this.jsonPrefix);
|
||||
}
|
||||
this.objectMapper.writeValue(jsonGenerator, object);
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Jackson {@link JavaType} for the specified type and context class.
|
||||
* <p>The default implementation returns {@code typeFactory.constructType(type, contextClass)},
|
||||
* but this can be overridden in subclasses, to allow for custom generic collection handling.
|
||||
* For instance:
|
||||
* <pre class="code">
|
||||
* protected JavaType getJavaType(Type type) {
|
||||
* if (type instanceof Class && List.class.isAssignableFrom((Class)type)) {
|
||||
* return TypeFactory.collectionType(ArrayList.class, MyBean.class);
|
||||
* } else {
|
||||
* return super.getJavaType(type);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* @param type the type to return the java type for
|
||||
* @param contextClass a context class for the target type, for example a class
|
||||
* in which the target type appears in a method signature, can be {@code null}
|
||||
* @return the java type
|
||||
*/
|
||||
protected JavaType getJavaType(Type type, Class<?> contextClass) {
|
||||
return this.objectMapper.getTypeFactory().constructType(type, contextClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the JSON encoding to use for the given content type.
|
||||
* @param contentType the media type as requested by the caller
|
||||
* @return the JSON encoding to use (never {@code null})
|
||||
*/
|
||||
protected JsonEncoding getJsonEncoding(MediaType contentType) {
|
||||
if (contentType != null && contentType.getCharSet() != null) {
|
||||
Charset charset = contentType.getCharSet();
|
||||
for (JsonEncoding encoding : JsonEncoding.values()) {
|
||||
if (charset.name().equals(encoding.getJavaName())) {
|
||||
return encoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
return JsonEncoding.UTF8;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.converter.support;
|
||||
|
||||
import javax.xml.transform.Source;
|
||||
@@ -39,10 +40,6 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AllEncompassingFormHttpMessageConverter.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AllEncompassingFormHttpMessageConverter.class.getClassLoader());
|
||||
|
||||
private static final boolean jacksonPresent =
|
||||
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AllEncompassingFormHttpMessageConverter.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AllEncompassingFormHttpMessageConverter.class.getClassLoader());
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public AllEncompassingFormHttpMessageConverter() {
|
||||
@@ -53,9 +50,6 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv
|
||||
if (jackson2Present) {
|
||||
addPartConverter(new MappingJackson2HttpMessageConverter());
|
||||
}
|
||||
else if (jacksonPresent) {
|
||||
addPartConverter(new org.springframework.http.converter.json.MappingJacksonHttpMessageConverter());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -134,10 +134,6 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", RestTemplate.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", RestTemplate.class.getClassLoader());
|
||||
|
||||
private static final boolean jacksonPresent =
|
||||
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", RestTemplate.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", RestTemplate.class.getClassLoader());
|
||||
|
||||
|
||||
private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
|
||||
|
||||
@@ -157,6 +153,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
||||
this.messageConverters.add(new ResourceHttpMessageConverter());
|
||||
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
|
||||
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
|
||||
|
||||
if (romePresent) {
|
||||
this.messageConverters.add(new AtomFeedHttpMessageConverter());
|
||||
this.messageConverters.add(new RssChannelHttpMessageConverter());
|
||||
@@ -167,9 +164,6 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
||||
if (jackson2Present) {
|
||||
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
|
||||
}
|
||||
else if (jacksonPresent) {
|
||||
this.messageConverters.add(new org.springframework.http.converter.json.MappingJacksonHttpMessageConverter());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.http.converter.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MockHttpInputMessage;
|
||||
import org.springframework.http.MockHttpOutputMessage;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Base class for Jackson and Jackson 2 converter tests.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public abstract class AbstractMappingJacksonHttpMessageConverterTests<T extends HttpMessageConverter<Object>> {
|
||||
|
||||
protected static final String NEWLINE_SYSTEM_PROPERTY = System.getProperty("line.separator");
|
||||
|
||||
private T converter;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.converter = createConverter();
|
||||
}
|
||||
|
||||
protected T getConverter() {
|
||||
return this.converter;
|
||||
}
|
||||
|
||||
protected abstract T createConverter();
|
||||
|
||||
@Test
|
||||
public void canRead() {
|
||||
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json")));
|
||||
assertTrue(converter.canRead(Map.class, new MediaType("application", "json")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canWrite() {
|
||||
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json")));
|
||||
assertTrue(converter.canWrite(Map.class, new MediaType("application", "json")));
|
||||
}
|
||||
|
||||
// SPR-7905
|
||||
|
||||
@Test
|
||||
public void canReadAndWriteMicroformats() {
|
||||
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "vnd.test-micro-type+json")));
|
||||
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "vnd.test-micro-type+json")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readTyped() throws IOException {
|
||||
String body =
|
||||
"{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertEquals("Foo", result.getString());
|
||||
assertEquals(42, result.getNumber());
|
||||
assertEquals(42F, result.getFraction(), 0F);
|
||||
assertArrayEquals(new String[]{"Foo", "Bar"}, result.getArray());
|
||||
assertTrue(result.isBool());
|
||||
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readUntyped() throws IOException {
|
||||
String body =
|
||||
"{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
HashMap<String, Object> result = (HashMap<String, Object>) converter.read(HashMap.class, inputMessage);
|
||||
assertEquals("Foo", result.get("string"));
|
||||
assertEquals(42, result.get("number"));
|
||||
assertEquals(42D, (Double) result.get("fraction"), 0D);
|
||||
List<String> array = new ArrayList<String>();
|
||||
array.add("Foo");
|
||||
array.add("Bar");
|
||||
assertEquals(array, result.get("array"));
|
||||
assertEquals(Boolean.TRUE, result.get("bool"));
|
||||
assertEquals("AQI=", result.get("bytes"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
Charset utf8 = Charset.forName("UTF-8");
|
||||
String result = outputMessage.getBodyAsString(utf8);
|
||||
assertTrue(result.contains("\"string\":\"Foo\""));
|
||||
assertTrue(result.contains("\"number\":42"));
|
||||
assertTrue(result.contains("fraction\":42.0"));
|
||||
assertTrue(result.contains("\"array\":[\"Foo\",\"Bar\"]"));
|
||||
assertTrue(result.contains("\"bool\":true"));
|
||||
assertTrue(result.contains("\"bytes\":\"AQI=\""));
|
||||
assertEquals("Invalid content-type", new MediaType("application", "json", utf8),
|
||||
outputMessage.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeUTF16() throws IOException {
|
||||
Charset utf16 = Charset.forName("UTF-16BE");
|
||||
MediaType contentType = new MediaType("application", "json", utf16);
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
String body = "H\u00e9llo W\u00f6rld";
|
||||
converter.write(body, contentType, outputMessage);
|
||||
assertEquals("Invalid result", "\"" + body + "\"", outputMessage.getBodyAsString(utf16));
|
||||
assertEquals("Invalid content-type", contentType, outputMessage.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
@Test(expected = HttpMessageNotReadableException.class)
|
||||
public void readInvalidJson() throws IOException {
|
||||
String body = "FooBar";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
converter.read(MyBean.class, inputMessage);
|
||||
}
|
||||
|
||||
@Test(expected = HttpMessageNotReadableException.class)
|
||||
public void readValidJsonWithUnknownProperty() throws IOException {
|
||||
String body = "{\"string\":\"string\",\"unknownProperty\":\"value\"}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
converter.read(MyBean.class, inputMessage);
|
||||
}
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.http.converter.json;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import org.codehaus.jackson.JsonFactory;
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.JsonParser;
|
||||
import org.codehaus.jackson.map.DeserializationConfig;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
import org.codehaus.jackson.map.introspect.NopAnnotationIntrospector;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:dmitry.katsubo@gmail.com">Dmitry Katsubo</a>
|
||||
*/
|
||||
public class JacksonObjectMapperFactoryBeanTests {
|
||||
|
||||
private static final String DATE_FORMAT = "yyyy-MM-dd";
|
||||
|
||||
private JacksonObjectMapperFactoryBean factory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
factory = new JacksonObjectMapperFactoryBean();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetFeaturesToEnableEmpty() {
|
||||
factory.setFeaturesToEnable(new Object[0]);
|
||||
factory.setFeaturesToDisable(new Object[0]);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testUnknownFeature() {
|
||||
factory.setFeaturesToEnable(new Object[] { Boolean.TRUE });
|
||||
factory.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanSetters() {
|
||||
factory.setAutoDetectFields(false);
|
||||
factory.setAutoDetectGettersSetters(false);
|
||||
factory.setFailOnEmptyBeans(false);
|
||||
factory.setIndentOutput(true);
|
||||
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
ObjectMapper objectMapper = factory.getObject();
|
||||
|
||||
SerializationConfig serializeConfig = objectMapper.getSerializationConfig();
|
||||
DeserializationConfig deserializeConfig = objectMapper.getDeserializationConfig();
|
||||
|
||||
assertFalse(serializeConfig.isEnabled(SerializationConfig.Feature.AUTO_DETECT_FIELDS));
|
||||
assertFalse(deserializeConfig.isEnabled(DeserializationConfig.Feature.AUTO_DETECT_FIELDS));
|
||||
assertFalse(serializeConfig.isEnabled(SerializationConfig.Feature.AUTO_DETECT_GETTERS));
|
||||
assertFalse(deserializeConfig.isEnabled(DeserializationConfig.Feature.AUTO_DETECT_SETTERS));
|
||||
assertFalse(serializeConfig.isEnabled(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS));
|
||||
assertTrue(serializeConfig.isEnabled(SerializationConfig.Feature.INDENT_OUTPUT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDateTimeFormatSetter() {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
|
||||
|
||||
factory.setDateFormat(dateFormat);
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
assertEquals(dateFormat, factory.getObject().getSerializationConfig().getDateFormat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleDateFormatStringSetter() {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
|
||||
|
||||
factory.setSimpleDateFormat(DATE_FORMAT);
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
assertEquals(dateFormat, factory.getObject().getSerializationConfig().getDateFormat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleSetup() {
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
assertNotNull(factory.getObject());
|
||||
assertTrue(factory.isSingleton());
|
||||
assertEquals(ObjectMapper.class, factory.getObjectType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompleteSetup() {
|
||||
NopAnnotationIntrospector annotationIntrospector = new NopAnnotationIntrospector();
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
assertTrue(factory.isSingleton());
|
||||
assertEquals(ObjectMapper.class, factory.getObjectType());
|
||||
|
||||
factory.setObjectMapper(objectMapper);
|
||||
factory.setAnnotationIntrospector(annotationIntrospector);
|
||||
factory.setFeaturesToEnable(new Object[] {
|
||||
SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,
|
||||
DeserializationConfig.Feature.USE_ANNOTATIONS,
|
||||
JsonParser.Feature.ALLOW_SINGLE_QUOTES,
|
||||
JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS
|
||||
});
|
||||
factory.setFeaturesToDisable(new Object[] {
|
||||
SerializationConfig.Feature.AUTO_DETECT_GETTERS,
|
||||
DeserializationConfig.Feature.AUTO_DETECT_FIELDS,
|
||||
JsonParser.Feature.AUTO_CLOSE_SOURCE,
|
||||
JsonGenerator.Feature.QUOTE_FIELD_NAMES
|
||||
});
|
||||
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
assertTrue(objectMapper == factory.getObject());
|
||||
|
||||
SerializationConfig serializeConfig = objectMapper.getSerializationConfig();
|
||||
DeserializationConfig deserializeConfig = objectMapper.getDeserializationConfig();
|
||||
JsonFactory jsonFactory = objectMapper.getJsonFactory();
|
||||
|
||||
assertTrue(annotationIntrospector == serializeConfig.getAnnotationIntrospector());
|
||||
assertTrue(annotationIntrospector == deserializeConfig.getAnnotationIntrospector());
|
||||
|
||||
assertTrue(serializeConfig.isEnabled(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS));
|
||||
assertTrue(deserializeConfig.isEnabled(DeserializationConfig.Feature.USE_ANNOTATIONS));
|
||||
assertTrue(jsonFactory.isEnabled(JsonParser.Feature.ALLOW_SINGLE_QUOTES));
|
||||
assertTrue(jsonFactory.isEnabled(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS));
|
||||
|
||||
assertFalse(serializeConfig.isEnabled(SerializationConfig.Feature.AUTO_DETECT_GETTERS));
|
||||
assertFalse(deserializeConfig.isEnabled(DeserializationConfig.Feature.AUTO_DETECT_FIELDS));
|
||||
assertFalse(jsonFactory.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
|
||||
assertFalse(jsonFactory.isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -20,16 +20,19 @@ import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MockHttpInputMessage;
|
||||
import org.springframework.http.MockHttpOutputMessage;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -38,12 +41,115 @@ import static org.junit.Assert.*;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class MappingJackson2HttpMessageConverterTests extends AbstractMappingJacksonHttpMessageConverterTests<MappingJackson2HttpMessageConverter> {
|
||||
public class MappingJackson2HttpMessageConverterTests {
|
||||
|
||||
protected static final String NEWLINE_SYSTEM_PROPERTY = System.getProperty("line.separator");
|
||||
|
||||
private final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
|
||||
|
||||
@Override
|
||||
protected MappingJackson2HttpMessageConverter createConverter() {
|
||||
return new MappingJackson2HttpMessageConverter();
|
||||
@Test
|
||||
public void canRead() {
|
||||
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json")));
|
||||
assertTrue(converter.canRead(Map.class, new MediaType("application", "json")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canWrite() {
|
||||
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json")));
|
||||
assertTrue(converter.canWrite(Map.class, new MediaType("application", "json")));
|
||||
}
|
||||
|
||||
// SPR-7905
|
||||
|
||||
@Test
|
||||
public void canReadAndWriteMicroformats() {
|
||||
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "vnd.test-micro-type+json")));
|
||||
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "vnd.test-micro-type+json")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readTyped() throws IOException {
|
||||
String body =
|
||||
"{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertEquals("Foo", result.getString());
|
||||
assertEquals(42, result.getNumber());
|
||||
assertEquals(42F, result.getFraction(), 0F);
|
||||
assertArrayEquals(new String[]{"Foo", "Bar"}, result.getArray());
|
||||
assertTrue(result.isBool());
|
||||
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readUntyped() throws IOException {
|
||||
String body =
|
||||
"{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
HashMap<String, Object> result = (HashMap<String, Object>) converter.read(HashMap.class, inputMessage);
|
||||
assertEquals("Foo", result.get("string"));
|
||||
assertEquals(42, result.get("number"));
|
||||
assertEquals(42D, (Double) result.get("fraction"), 0D);
|
||||
List<String> array = new ArrayList<String>();
|
||||
array.add("Foo");
|
||||
array.add("Bar");
|
||||
assertEquals(array, result.get("array"));
|
||||
assertEquals(Boolean.TRUE, result.get("bool"));
|
||||
assertEquals("AQI=", result.get("bytes"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
Charset utf8 = Charset.forName("UTF-8");
|
||||
String result = outputMessage.getBodyAsString(utf8);
|
||||
assertTrue(result.contains("\"string\":\"Foo\""));
|
||||
assertTrue(result.contains("\"number\":42"));
|
||||
assertTrue(result.contains("fraction\":42.0"));
|
||||
assertTrue(result.contains("\"array\":[\"Foo\",\"Bar\"]"));
|
||||
assertTrue(result.contains("\"bool\":true"));
|
||||
assertTrue(result.contains("\"bytes\":\"AQI=\""));
|
||||
assertEquals("Invalid content-type", new MediaType("application", "json", utf8),
|
||||
outputMessage.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeUTF16() throws IOException {
|
||||
Charset utf16 = Charset.forName("UTF-16BE");
|
||||
MediaType contentType = new MediaType("application", "json", utf16);
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
String body = "H\u00e9llo W\u00f6rld";
|
||||
converter.write(body, contentType, outputMessage);
|
||||
assertEquals("Invalid result", "\"" + body + "\"", outputMessage.getBodyAsString(utf16));
|
||||
assertEquals("Invalid content-type", contentType, outputMessage.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
@Test(expected = HttpMessageNotReadableException.class)
|
||||
public void readInvalidJson() throws IOException {
|
||||
String body = "FooBar";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
converter.read(MyBean.class, inputMessage);
|
||||
}
|
||||
|
||||
@Test(expected = HttpMessageNotReadableException.class)
|
||||
public void readValidJsonWithUnknownProperty() throws IOException {
|
||||
String body = "{\"string\":\"string\",\"unknownProperty\":\"value\"}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
converter.read(MyBean.class, inputMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -106,8 +212,8 @@ public class MappingJackson2HttpMessageConverterTests extends AbstractMappingJac
|
||||
PrettyPrintBean bean = new PrettyPrintBean();
|
||||
bean.setName("Jason");
|
||||
|
||||
getConverter().setPrettyPrint(true);
|
||||
getConverter().writeInternal(bean, outputMessage);
|
||||
this.converter.setPrettyPrint(true);
|
||||
this.converter.writeInternal(bean, outputMessage);
|
||||
String result = outputMessage.getBodyAsString(Charset.forName("UTF-8"));
|
||||
|
||||
assertEquals("{" + NEWLINE_SYSTEM_PROPERTY + " \"name\" : \"Jason\"" + NEWLINE_SYSTEM_PROPERTY + "}", result);
|
||||
@@ -116,8 +222,8 @@ public class MappingJackson2HttpMessageConverterTests extends AbstractMappingJac
|
||||
@Test
|
||||
public void prefixJson() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
getConverter().setPrefixJson(true);
|
||||
getConverter().writeInternal("foo", outputMessage);
|
||||
this.converter.setPrefixJson(true);
|
||||
this.converter.writeInternal("foo", outputMessage);
|
||||
|
||||
assertEquals("{} && \"foo\"", outputMessage.getBodyAsString(Charset.forName("UTF-8")));
|
||||
}
|
||||
@@ -125,13 +231,77 @@ public class MappingJackson2HttpMessageConverterTests extends AbstractMappingJac
|
||||
@Test
|
||||
public void prefixJsonCustom() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
getConverter().setJsonPrefix(")]}',");
|
||||
getConverter().writeInternal("foo", outputMessage);
|
||||
this.converter.setJsonPrefix(")]}',");
|
||||
this.converter.writeInternal("foo", outputMessage);
|
||||
|
||||
assertEquals(")]}',\"foo\"", outputMessage.getBodyAsString(Charset.forName("UTF-8")));
|
||||
}
|
||||
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class PrettyPrintBean {
|
||||
|
||||
private String name;
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.http.converter.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.jackson.map.type.TypeFactory;
|
||||
import org.codehaus.jackson.type.JavaType;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MockHttpInputMessage;
|
||||
import org.springframework.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Jackson 1.x converter tests.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class MappingJacksonHttpMessageConverterTests extends AbstractMappingJacksonHttpMessageConverterTests<MappingJacksonHttpMessageConverter> {
|
||||
|
||||
@Override
|
||||
protected MappingJacksonHttpMessageConverter createConverter() {
|
||||
return new MappingJacksonHttpMessageConverter();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readGenerics() throws IOException {
|
||||
MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter() {
|
||||
@Override
|
||||
protected JavaType getJavaType(Type type, Class<?> contextClass) {
|
||||
if (type instanceof Class && List.class.isAssignableFrom((Class<?>)type)) {
|
||||
return TypeFactory.collectionType(ArrayList.class, MyBean.class);
|
||||
}
|
||||
else {
|
||||
return super.getJavaType(type, contextClass);
|
||||
}
|
||||
}
|
||||
};
|
||||
String body =
|
||||
"[{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
|
||||
List<MyBean> results = (List<MyBean>) converter.read(List.class, inputMessage);
|
||||
assertEquals(1, results.size());
|
||||
MyBean result = results.get(0);
|
||||
assertEquals("Foo", result.getString());
|
||||
assertEquals(42, result.getNumber());
|
||||
assertEquals(42F, result.getFraction(), 0F);
|
||||
assertArrayEquals(new String[]{"Foo", "Bar"}, result.getArray());
|
||||
assertTrue(result.isBool());
|
||||
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readParameterizedType() throws IOException {
|
||||
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<List<MyBean>>() {};
|
||||
|
||||
String body =
|
||||
"[{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
|
||||
MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
|
||||
List<MyBean> results = (List<MyBean>) converter.read(beansList.getType(), null, inputMessage);
|
||||
assertEquals(1, results.size());
|
||||
MyBean result = results.get(0);
|
||||
assertEquals("Foo", result.getString());
|
||||
assertEquals(42, result.getNumber());
|
||||
assertEquals(42F, result.getFraction(), 0F);
|
||||
assertArrayEquals(new String[]{"Foo", "Bar"}, result.getArray());
|
||||
assertTrue(result.isBool());
|
||||
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prettyPrint() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
PrettyPrintBean bean = new PrettyPrintBean();
|
||||
bean.setName("Jason");
|
||||
|
||||
getConverter().setPrettyPrint(true);
|
||||
getConverter().writeInternal(bean, outputMessage);
|
||||
String result = outputMessage.getBodyAsString(Charset.forName("UTF-8"));
|
||||
|
||||
assertEquals("{" + NEWLINE_SYSTEM_PROPERTY + " \"name\" : \"Jason\"" + NEWLINE_SYSTEM_PROPERTY + "}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefixJson() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
getConverter().setPrefixJson(true);
|
||||
getConverter().writeInternal("foo", outputMessage);
|
||||
|
||||
assertEquals("{} && \"foo\"", outputMessage.getBodyAsString(Charset.forName("UTF-8")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefixJsonCustom() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
getConverter().setJsonPrefix(")]}',");
|
||||
getConverter().writeInternal("foo", outputMessage);
|
||||
|
||||
assertEquals(")]}',\"foo\"", outputMessage.getBodyAsString(Charset.forName("UTF-8")));
|
||||
}
|
||||
|
||||
|
||||
public static class PrettyPrintBean {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -133,6 +133,9 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
private static final boolean javaxValidationPresent = ClassUtils.isPresent(
|
||||
"javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
|
||||
|
||||
private static boolean romePresent =
|
||||
ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
|
||||
|
||||
private static final boolean jaxb2Present =
|
||||
ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
|
||||
|
||||
@@ -140,13 +143,6 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
|
||||
|
||||
private static final boolean jacksonPresent =
|
||||
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
|
||||
|
||||
private static boolean romePresent =
|
||||
ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
|
||||
|
||||
|
||||
@Override
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
@@ -338,7 +334,6 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
}
|
||||
|
||||
private void configurePathMatchingProperties(RootBeanDefinition handlerMappingDef, Element element) {
|
||||
|
||||
Element pathMatchingElement = DomUtils.getChildElementByTagName(element, "path-matching");
|
||||
if(pathMatchingElement != null) {
|
||||
if (pathMatchingElement.hasAttribute("suffix-pattern")) {
|
||||
@@ -370,12 +365,12 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
props.put("atom", MediaType.APPLICATION_ATOM_XML_VALUE);
|
||||
props.put("rss", "application/rss+xml");
|
||||
}
|
||||
if (jackson2Present || jacksonPresent) {
|
||||
props.put("json", MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
if (jaxb2Present) {
|
||||
props.put("xml", MediaType.APPLICATION_XML_VALUE);
|
||||
}
|
||||
if (jackson2Present) {
|
||||
props.put("json", MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
@@ -485,14 +480,9 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
if (jaxb2Present) {
|
||||
messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
|
||||
}
|
||||
|
||||
if (jackson2Present) {
|
||||
messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));
|
||||
}
|
||||
else if (jacksonPresent) {
|
||||
messageConverters.add(createConverterDefinition(
|
||||
org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));
|
||||
}
|
||||
}
|
||||
return messageConverters;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.xml.transform.Source;
|
||||
@@ -143,6 +142,9 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
|
||||
*/
|
||||
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
|
||||
|
||||
private static boolean romePresent =
|
||||
ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", WebMvcConfigurationSupport.class.getClassLoader());
|
||||
|
||||
private static final boolean jaxb2Present =
|
||||
ClassUtils.isPresent("javax.xml.bind.Binder", WebMvcConfigurationSupport.class.getClassLoader());
|
||||
|
||||
@@ -150,12 +152,6 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", WebMvcConfigurationSupport.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", WebMvcConfigurationSupport.class.getClassLoader());
|
||||
|
||||
private static final boolean jacksonPresent =
|
||||
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", WebMvcConfigurationSupport.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", WebMvcConfigurationSupport.class.getClassLoader());
|
||||
|
||||
private static boolean romePresent =
|
||||
ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", WebMvcConfigurationSupport.class.getClassLoader());
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
@@ -225,9 +221,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
InterceptorRegistry registry = new InterceptorRegistry();
|
||||
addInterceptors(registry);
|
||||
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
|
||||
interceptors = registry.getInterceptors();
|
||||
this.interceptors = registry.getInterceptors();
|
||||
}
|
||||
return interceptors.toArray();
|
||||
return this.interceptors.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,12 +260,12 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
map.put("atom", MediaType.APPLICATION_ATOM_XML);
|
||||
map.put("rss", MediaType.valueOf("application/rss+xml"));
|
||||
}
|
||||
if (jackson2Present || jacksonPresent) {
|
||||
map.put("json", MediaType.APPLICATION_JSON);
|
||||
}
|
||||
if (jaxb2Present) {
|
||||
map.put("xml", MediaType.APPLICATION_XML);
|
||||
}
|
||||
if (jackson2Present) {
|
||||
map.put("json", MediaType.APPLICATION_JSON);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -552,6 +548,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
messageConverters.add(new ResourceHttpMessageConverter());
|
||||
messageConverters.add(new SourceHttpMessageConverter<Source>());
|
||||
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
|
||||
|
||||
if (romePresent) {
|
||||
messageConverters.add(new AtomFeedHttpMessageConverter());
|
||||
messageConverters.add(new RssChannelHttpMessageConverter());
|
||||
@@ -562,9 +559,6 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
if (jackson2Present) {
|
||||
messageConverters.add(new MappingJackson2HttpMessageConverter());
|
||||
}
|
||||
else if (jacksonPresent) {
|
||||
messageConverters.add(new org.springframework.http.converter.json.MappingJacksonHttpMessageConverter());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -209,7 +209,7 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
/**
|
||||
* Set whether to serialize models containing a single attribute as a map or whether to
|
||||
* extract the single value from the model and serialize it directly.
|
||||
* <p>The effect of setting this flag is similar to using {@code MappingJacksonHttpMessageConverter}
|
||||
* <p>The effect of setting this flag is similar to using {@code MappingJackson2HttpMessageConverter}
|
||||
* with an {@code @ResponseBody} request-handling method.
|
||||
* <p>Default is {@code false}.
|
||||
*/
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.web.servlet.view.json;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.codehaus.jackson.JsonEncoding;
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
/**
|
||||
* Spring MVC {@link View} that renders JSON content by serializing the model for the current request
|
||||
* using <a href="http://jackson.codehaus.org/">Jackson 1.x's</a> {@link ObjectMapper}.
|
||||
*
|
||||
* <p>By default, the entire contents of the model map (with the exception of framework-specific classes)
|
||||
* will be encoded as JSON. If the model contains only one key, you can have it extracted encoded as JSON
|
||||
* alone via {@link #setExtractValueFromSingleKeyModel}.
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @deprecated Please migrate to {@link MappingJackson2JsonView} for Jackson 2.x.
|
||||
*/
|
||||
@Deprecated
|
||||
public class MappingJacksonJsonView extends AbstractView {
|
||||
|
||||
/**
|
||||
* Default content type: "application/json".
|
||||
* Overridable through {@link #setContentType}.
|
||||
*/
|
||||
public static final String DEFAULT_CONTENT_TYPE = "application/json";
|
||||
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private JsonEncoding encoding = JsonEncoding.UTF8;
|
||||
|
||||
private String jsonPrefix;
|
||||
|
||||
private Boolean prettyPrint;
|
||||
|
||||
private Set<String> modelKeys;
|
||||
|
||||
private boolean extractValueFromSingleKeyModel = false;
|
||||
|
||||
private boolean disableCaching = true;
|
||||
|
||||
private boolean updateContentLength = false;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@code MappingJacksonJsonView}, setting the content type to {@code application/json}.
|
||||
*/
|
||||
public MappingJacksonJsonView() {
|
||||
setContentType(DEFAULT_CONTENT_TYPE);
|
||||
setExposePathVariables(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@code ObjectMapper} for this view.
|
||||
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} will be used.
|
||||
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control of
|
||||
* the JSON serialization process. The other option is to use Jackson's provided annotations
|
||||
* on the types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
|
||||
*/
|
||||
public void setObjectMapper(ObjectMapper objectMapper) {
|
||||
Assert.notNull(objectMapper, "'objectMapper' must not be null");
|
||||
this.objectMapper = objectMapper;
|
||||
configurePrettyPrint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code ObjectMapper} for this view.
|
||||
*/
|
||||
public final ObjectMapper getObjectMapper() {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@code JsonEncoding} for this view.
|
||||
* By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used.
|
||||
*/
|
||||
public void setEncoding(JsonEncoding encoding) {
|
||||
Assert.notNull(encoding, "'encoding' must not be null");
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code JsonEncoding} for this view.
|
||||
*/
|
||||
public final JsonEncoding getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a custom prefix to use for this view's JSON output.
|
||||
* Default is none.
|
||||
* @see #setPrefixJson
|
||||
*/
|
||||
public void setJsonPrefix(String jsonPrefix) {
|
||||
this.jsonPrefix = jsonPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the JSON output by this view should be prefixed with <tt>"{} && "</tt>.
|
||||
* Default is {@code false}.
|
||||
* <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
|
||||
* The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
|
||||
* This prefix does not affect the evaluation of JSON, but if JSON validation is performed
|
||||
* on the string, the prefix would need to be ignored.
|
||||
* @see #setJsonPrefix
|
||||
*/
|
||||
public void setPrefixJson(boolean prefixJson) {
|
||||
this.jsonPrefix = (prefixJson ? "{} && " : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use the default pretty printer when writing JSON.
|
||||
* This is a shortcut for setting up an {@code ObjectMapper} as follows:
|
||||
* <pre class="code">
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
|
||||
* </pre>
|
||||
* <p>The default value is {@code false}.
|
||||
*/
|
||||
public void setPrettyPrint(boolean prettyPrint) {
|
||||
this.prettyPrint = prettyPrint;
|
||||
configurePrettyPrint();
|
||||
}
|
||||
|
||||
private void configurePrettyPrint() {
|
||||
if (this.prettyPrint != null) {
|
||||
this.objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, this.prettyPrint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attribute in the model that should be rendered by this view.
|
||||
* When set, all other model attributes will be ignored.
|
||||
*/
|
||||
public void setModelKey(String modelKey) {
|
||||
this.modelKeys = Collections.singleton(modelKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attributes in the model that should be rendered by this view.
|
||||
* When set, all other model attributes will be ignored.
|
||||
*/
|
||||
public void setModelKeys(Set<String> modelKeys) {
|
||||
this.modelKeys = modelKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attributes in the model that should be rendered by this view.
|
||||
*/
|
||||
public final Set<String> getModelKeys() {
|
||||
return this.modelKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attributes in the model that should be rendered by this view.
|
||||
* When set, all other model attributes will be ignored.
|
||||
* @deprecated use {@link #setModelKeys(Set)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRenderedAttributes(Set<String> renderedAttributes) {
|
||||
this.modelKeys = renderedAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attributes in the model that should be rendered by this view.
|
||||
* @deprecated use {@link #getModelKeys()} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public final Set<String> getRenderedAttributes() {
|
||||
return this.modelKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to serialize models containing a single attribute as a map or whether to
|
||||
* extract the single value from the model and serialize it directly.
|
||||
* <p>The effect of setting this flag is similar to using {@code MappingJacksonHttpMessageConverter}
|
||||
* with an {@code @ResponseBody} request-handling method.
|
||||
* <p>Default is {@code false}.
|
||||
*/
|
||||
public void setExtractValueFromSingleKeyModel(boolean extractValueFromSingleKeyModel) {
|
||||
this.extractValueFromSingleKeyModel = extractValueFromSingleKeyModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables caching of the generated JSON.
|
||||
* <p>Default is {@code true}, which will prevent the client from caching the generated JSON.
|
||||
*/
|
||||
public void setDisableCaching(boolean disableCaching) {
|
||||
this.disableCaching = disableCaching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to update the 'Content-Length' header of the response. When set to
|
||||
* {@code true}, the response is buffered in order to determine the content
|
||||
* length and set the 'Content-Length' header of the response.
|
||||
* <p>The default setting is {@code false}.
|
||||
*/
|
||||
public void setUpdateContentLength(boolean updateContentLength) {
|
||||
this.updateContentLength = updateContentLength;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
|
||||
setResponseContentType(request, response);
|
||||
response.setCharacterEncoding(this.encoding.getJavaName());
|
||||
if (this.disableCaching) {
|
||||
response.addHeader("Pragma", "no-cache");
|
||||
response.addHeader("Cache-Control", "no-cache, no-store, max-age=0");
|
||||
response.addDateHeader("Expires", 1L);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
|
||||
OutputStream stream = (this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream());
|
||||
Object value = filterModel(model);
|
||||
writeContent(stream, value, this.jsonPrefix);
|
||||
if (this.updateContentLength) {
|
||||
writeToResponse(response, (ByteArrayOutputStream) stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out undesired attributes from the given model.
|
||||
* The return value can be either another {@link Map} or a single value object.
|
||||
* <p>The default implementation removes {@link BindingResult} instances and entries
|
||||
* not included in the {@link #setRenderedAttributes renderedAttributes} property.
|
||||
* @param model the model, as passed on to {@link #renderMergedOutputModel}
|
||||
* @return the value to be rendered
|
||||
*/
|
||||
protected Object filterModel(Map<String, Object> model) {
|
||||
Map<String, Object> result = new HashMap<String, Object>(model.size());
|
||||
Set<String> renderedAttributes = (!CollectionUtils.isEmpty(this.modelKeys) ? this.modelKeys : model.keySet());
|
||||
for (Map.Entry<String, Object> entry : model.entrySet()) {
|
||||
if (!(entry.getValue() instanceof BindingResult) && renderedAttributes.contains(entry.getKey())) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the actual JSON content to the stream.
|
||||
* @param stream the output stream to use
|
||||
* @param value the value to be rendered, as returned from {@link #filterModel}
|
||||
* @param jsonPrefix the prefix for this view's JSON output
|
||||
* (as indicated through {@link #setJsonPrefix}/{@link #setPrefixJson})
|
||||
* @throws IOException if writing failed
|
||||
*/
|
||||
protected void writeContent(OutputStream stream, Object value, String jsonPrefix) throws IOException {
|
||||
JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding);
|
||||
|
||||
// A workaround for JsonGenerators not applying serialization features
|
||||
// https://github.com/FasterXML/jackson-databind/issues/12
|
||||
if (this.objectMapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.INDENT_OUTPUT)) {
|
||||
generator.useDefaultPrettyPrinter();
|
||||
}
|
||||
|
||||
if (jsonPrefix != null) {
|
||||
generator.writeRaw(jsonPrefix);
|
||||
}
|
||||
this.objectMapper.writeValue(generator, value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.view.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.map.BeanProperty;
|
||||
import org.codehaus.jackson.map.JsonMappingException;
|
||||
import org.codehaus.jackson.map.JsonSerializer;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
import org.codehaus.jackson.map.SerializerFactory;
|
||||
import org.codehaus.jackson.map.SerializerProvider;
|
||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
||||
import org.codehaus.jackson.map.ser.BeanSerializerFactory;
|
||||
import org.codehaus.jackson.type.JavaType;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mozilla.javascript.Context;
|
||||
import org.mozilla.javascript.ContextFactory;
|
||||
import org.mozilla.javascript.ScriptableObject;
|
||||
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.validation.BindingResult;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author Jeremy Grelle
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class MappingJacksonJsonViewTests {
|
||||
|
||||
private MappingJacksonJsonView view;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private Context jsContext;
|
||||
|
||||
private ScriptableObject jsScope;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
jsContext = ContextFactory.getGlobal().enterContext();
|
||||
jsScope = jsContext.initStandardObjects();
|
||||
view = new MappingJacksonJsonView();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void isExposePathVars() {
|
||||
assertEquals("Must not expose path variables", false, view.isExposePathVariables());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderSimpleMap() throws Exception {
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("bindingResult", mock(BindingResult.class, "binding_result"));
|
||||
model.put("foo", "bar");
|
||||
|
||||
view.setUpdateContentLength(true);
|
||||
view.render(model, request, response);
|
||||
|
||||
assertEquals("no-cache", response.getHeader("Pragma"));
|
||||
assertEquals("no-cache, no-store, max-age=0", response.getHeader("Cache-Control"));
|
||||
assertNotNull(response.getHeader("Expires"));
|
||||
assertEquals(MappingJacksonJsonView.DEFAULT_CONTENT_TYPE, response.getContentType());
|
||||
|
||||
String jsonResult = response.getContentAsString();
|
||||
assertTrue(jsonResult.length() > 0);
|
||||
assertEquals(jsonResult.length(), response.getContentLength());
|
||||
validateResult();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderCaching() throws Exception {
|
||||
view.setDisableCaching(false);
|
||||
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("bindingResult", mock(BindingResult.class, "binding_result"));
|
||||
model.put("foo", "bar");
|
||||
view.render(model, request, response);
|
||||
|
||||
assertNull(response.getHeader("Pragma"));
|
||||
assertNull(response.getHeader("Cache-Control"));
|
||||
assertNull(response.getHeader("Expires"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderSimpleMapPrefixed() throws Exception {
|
||||
view.setPrefixJson(true);
|
||||
renderSimpleMap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderSimpleBean() throws Exception {
|
||||
Object bean = new TestBeanSimple();
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("bindingResult", mock(BindingResult.class, "binding_result"));
|
||||
model.put("foo", bean);
|
||||
view.setUpdateContentLength(true);
|
||||
view.render(model, request, response);
|
||||
|
||||
assertTrue(response.getContentAsString().length() > 0);
|
||||
assertEquals(response.getContentAsString().length(), response.getContentLength());
|
||||
validateResult();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderWithPrettyPrint() throws Exception {
|
||||
ModelMap model = new ModelMap("foo", new TestBeanSimple());
|
||||
view.setPrettyPrint(true);
|
||||
view.render(model, request, response);
|
||||
|
||||
String result = response.getContentAsString().replace("\r\n", "\n");
|
||||
assertTrue("Pretty printing not applied:\n" + result, result.startsWith("{\n \"foo\" : {\n "));
|
||||
validateResult();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderSimpleBeanPrefixed() throws Exception {
|
||||
view.setPrefixJson(true);
|
||||
renderSimpleBean();
|
||||
assertTrue(response.getContentAsString().startsWith("{} && "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderSimpleBeanNotPrefixed() throws Exception {
|
||||
view.setPrefixJson(false);
|
||||
renderSimpleBean();
|
||||
assertFalse(response.getContentAsString().startsWith("{} && "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderWithCustomSerializerLocatedByAnnotation() throws Exception {
|
||||
Object bean = new TestBeanSimpleAnnotated();
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("foo", bean);
|
||||
view.render(model, request, response);
|
||||
|
||||
assertTrue(response.getContentAsString().length() > 0);
|
||||
assertEquals("{\"foo\":{\"testBeanSimple\":\"custom\"}}", response.getContentAsString());
|
||||
validateResult();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderWithCustomSerializerLocatedByFactory() throws Exception {
|
||||
SerializerFactory factory = new DelegatingSerializerFactory(null);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setSerializerFactory(factory);
|
||||
view.setObjectMapper(mapper);
|
||||
|
||||
Object bean = new TestBeanSimple();
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("foo", bean);
|
||||
model.put("bar", new TestChildBean());
|
||||
view.render(model, request, response);
|
||||
|
||||
String result = response.getContentAsString();
|
||||
assertTrue(result.length() > 0);
|
||||
assertTrue(result.contains("\"foo\":{\"testBeanSimple\":\"custom\"}"));
|
||||
validateResult();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderOnlyIncludedAttributes() throws Exception {
|
||||
Set<String> attrs = new HashSet<String>();
|
||||
attrs.add("foo");
|
||||
attrs.add("baz");
|
||||
attrs.add("nil");
|
||||
|
||||
view.setModelKeys(attrs);
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("foo", "foo");
|
||||
model.put("bar", "bar");
|
||||
model.put("baz", "baz");
|
||||
view.render(model, request, response);
|
||||
|
||||
String result = response.getContentAsString();
|
||||
assertTrue(result.length() > 0);
|
||||
assertTrue(result.contains("\"foo\":\"foo\""));
|
||||
assertTrue(result.contains("\"baz\":\"baz\""));
|
||||
validateResult();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterSingleKeyModel() throws Exception {
|
||||
view.setExtractValueFromSingleKeyModel(true);
|
||||
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
TestBeanSimple bean = new TestBeanSimple();
|
||||
model.put("foo", bean);
|
||||
Object actual = view.filterModel(model);
|
||||
|
||||
assertSame(bean, actual);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void filterTwoKeyModel() throws Exception {
|
||||
view.setExtractValueFromSingleKeyModel(true);
|
||||
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
TestBeanSimple bean1 = new TestBeanSimple();
|
||||
TestBeanSimple bean2 = new TestBeanSimple();
|
||||
model.put("foo1", bean1);
|
||||
model.put("foo2", bean2);
|
||||
|
||||
Object actual = view.filterModel(model);
|
||||
|
||||
assertTrue(actual instanceof Map);
|
||||
assertSame(bean1, ((Map) actual).get("foo1"));
|
||||
assertSame(bean2, ((Map) actual).get("foo2"));
|
||||
}
|
||||
|
||||
private void validateResult() throws Exception {
|
||||
Object jsResult =
|
||||
jsContext.evaluateString(jsScope, "(" + response.getContentAsString() + ")", "JSON Stream", 1, null);
|
||||
assertNotNull("Json Result did not eval as valid JavaScript", jsResult);
|
||||
}
|
||||
|
||||
|
||||
public static class TestBeanSimple {
|
||||
|
||||
private String value = "foo";
|
||||
|
||||
private boolean test = false;
|
||||
|
||||
private long number = 42;
|
||||
|
||||
private TestChildBean child = new TestChildBean();
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean getTest() {
|
||||
return test;
|
||||
}
|
||||
|
||||
public long getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public Date getNow() {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
public TestChildBean getChild() {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JsonSerialize(using = TestBeanSimpleSerializer.class)
|
||||
public static class TestBeanSimpleAnnotated extends TestBeanSimple {
|
||||
}
|
||||
|
||||
|
||||
public static class TestChildBean {
|
||||
|
||||
private String value = "bar";
|
||||
|
||||
private String baz = null;
|
||||
|
||||
private TestBeanSimple parent = null;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getBaz() {
|
||||
return baz;
|
||||
}
|
||||
|
||||
public TestBeanSimple getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(TestBeanSimple parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class TestBeanSimpleSerializer extends JsonSerializer<Object> {
|
||||
|
||||
@Override
|
||||
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
|
||||
jgen.writeStartObject();
|
||||
jgen.writeFieldName("testBeanSimple");
|
||||
jgen.writeString("custom");
|
||||
jgen.writeEndObject();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class DelegatingSerializerFactory extends BeanSerializerFactory {
|
||||
|
||||
public DelegatingSerializerFactory(Config config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<Object> createSerializer(SerializationConfig config, JavaType baseType, BeanProperty property)
|
||||
throws JsonMappingException {
|
||||
|
||||
if (baseType.getRawClass() == TestBeanSimple.class) {
|
||||
return new TestBeanSimpleSerializer();
|
||||
}
|
||||
else {
|
||||
return super.createSerializer(config, baseType, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user