Browse Source

Require Jackson 2.0+, EhCache 2.5+, Quartz 2.1.4+

Issue: SPR-11262
pull/501/head
Juergen Hoeller 12 years ago
parent
commit
ea1e27efa2
  1. 9
      build.gradle
  2. 11
      spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java
  3. 5
      spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java
  4. 29
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/AdaptableJobFactory.java
  5. 175
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerBean.java
  6. 55
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerFactoryBean.java
  7. 56
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailAwareTrigger.java
  8. 169
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailBean.java
  9. 34
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java
  10. 4
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java
  11. 104
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java
  12. 48
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/QuartzJobBean.java
  13. 241
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java
  14. 5
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessorBean.java
  15. 5
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java
  16. 176
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerBean.java
  17. 53
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBean.java
  18. 33
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java
  19. 79
      spring-context-support/src/test/java/org/springframework/scheduling/quartz/CronTriggerBeanTests.java
  20. 719
      spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java
  21. 4
      spring-context-support/src/test/resources/org/springframework/scheduling/quartz/databasePersistence.xml
  22. 4
      spring-context-support/src/test/resources/org/springframework/scheduling/quartz/multipleAnonymousMethodInvokingJobDetailFB.xml
  23. 4
      spring-context-support/src/test/resources/org/springframework/scheduling/quartz/schedulerAccessorBean.xml
  24. 4
      spring-context-support/src/test/resources/org/springframework/scheduling/quartz/schedulerRepositoryExposure.xml
  25. 383
      spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJacksonMessageConverter.java
  26. 147
      spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJacksonMessageConverterTests.java
  27. 258
      spring-web/src/main/java/org/springframework/http/converter/json/JacksonObjectMapperFactoryBean.java
  28. 256
      spring-web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java
  29. 10
      spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java
  30. 8
      spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
  31. 226
      spring-web/src/test/java/org/springframework/http/converter/json/AbstractMappingJacksonHttpMessageConverterTests.java
  32. 158
      spring-web/src/test/java/org/springframework/http/converter/json/JacksonObjectMapperFactoryBeanTests.java
  33. 198
      spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java
  34. 144
      spring-web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java
  35. 22
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
  36. 24
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
  37. 4
      spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
  38. 306
      spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java
  39. 346
      spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJacksonJsonViewTests.java

9
build.gradle

@ -19,7 +19,6 @@ configure(allprojects) { project -> @@ -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") { @@ -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") { @@ -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") { @@ -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") { @@ -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") { @@ -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}")

11
spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java vendored

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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);

5
spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java vendored

@ -1,5 +1,5 @@ @@ -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; @@ -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

29
spring-context-support/src/main/java/org/springframework/scheduling/quartz/AdaptableJobFactory.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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 { @@ -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();
}
/**

175
spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerBean.java

@ -1,175 +0,0 @@ @@ -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());
}
}
}

55
spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerFactoryBean.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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; @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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();
}

56
spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailAwareTrigger.java

@ -1,56 +0,0 @@ @@ -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();
}

169
spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailBean.java

@ -1,169 +0,0 @@ @@ -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);
}
}
}

34
spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 @@ -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 @@ -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();
}

4
spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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()}

104
spring-context-support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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; @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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.

48
spring-context-support/src/main/java/org/springframework/scheduling/quartz/QuartzJobBean.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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; @@ -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 { @@ -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) {

241
spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java

@ -16,7 +16,6 @@ @@ -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; @@ -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; @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);
}
ListenerManager listenerManager = getScheduler().getListenerManager();
if (this.schedulerListeners != null) {
for (SchedulerListener listener : this.schedulerListeners) {
listenerManager.addSchedulerListener(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);
}
}
if (this.globalJobListeners != null) {
for (JobListener listener : this.globalJobListeners) {
listenerManager.addJobListener(listener);
}
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("Expected Quartz API not present: " + ex);
if (this.globalTriggerListeners != null) {
for (TriggerListener listener : this.globalTriggerListeners) {
listenerManager.addTriggerListener(listener);
}
}
}

5
spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessorBean.java

@ -1,5 +1,5 @@ @@ -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; @@ -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

5
spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java

@ -1,5 +1,5 @@ @@ -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; @@ -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

176
spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerBean.java

@ -1,176 +0,0 @@ @@ -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());
}
}
}

53
spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBean.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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; @@ -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; @@ -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 @@ -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 @@ -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 @@ -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();
}

33
spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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 @@ -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 @@ -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);
}
}

79
spring-context-support/src/test/java/org/springframework/scheduling/quartz/CronTriggerBeanTests.java

@ -1,79 +0,0 @@ @@ -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);
}
}

719
spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java

@ -16,42 +16,29 @@ @@ -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.*; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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,115 +451,9 @@ public class QuartzSupportTests { @@ -992,115 +451,9 @@ public class QuartzSupportTests {
try {
// assertEquals(10, DummyJob.param);
assertTrue(DummyJob.count > 0);
} 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) {
finally {
ctx.close();
}
}

4
spring-context-support/src/test/resources/org/springframework/scheduling/quartz/databasePersistence.xml

@ -10,11 +10,11 @@ @@ -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" />

4
spring-context-support/src/test/resources/org/springframework/scheduling/quartz/multipleAnonymousMethodInvokingJobDetailFB.xml

@ -12,7 +12,7 @@ @@ -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 @@ @@ -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"/>

4
spring-context-support/src/test/resources/org/springframework/scheduling/quartz/schedulerAccessorBean.xml

@ -15,7 +15,7 @@ @@ -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 @@ @@ -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"/>

4
spring-context-support/src/test/resources/org/springframework/scheduling/quartz/schedulerRepositoryExposure.xml

@ -18,7 +18,7 @@ @@ -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 @@ @@ -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"/>

383
spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJacksonMessageConverter.java

@ -1,383 +0,0 @@ @@ -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);
}
}
}

147
spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJacksonMessageConverterTests.java

@ -1,147 +0,0 @@ @@ -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);
}
}

258
spring-web/src/main/java/org/springframework/http/converter/json/JacksonObjectMapperFactoryBean.java

@ -1,258 +0,0 @@ @@ -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">
* &lt;bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
* &lt;property name="objectMapper">
* &lt;bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean"
* p:autoDetectFields="false"
* p:autoDetectGettersSetters="false"
* p:annotationIntrospector-ref="jaxbAnnotationIntrospector" />
* &lt;/property>
* &lt;/bean>
* </pre>
*
* <p>Example usage with MappingJacksonJsonView:
* <pre class="code">
* &lt;bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
* &lt;property name="objectMapper">
* &lt;bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean"
* p:autoDetectFields="false"
* p:autoDetectGettersSetters="false"
* p:annotationIntrospector-ref="jaxbAnnotationIntrospector" />
* &lt;/property>
* &lt;/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">
* &lt;bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean">
* &lt;property name="featuresToEnable">
* &lt;array>
* &lt;util:constant static-field="org.codehaus.jackson.map.SerializationConfig$Feature.WRAP_ROOT_VALUE"/>
* &lt;util:constant static-field="org.codehaus.jackson.map.SerializationConfig$Feature.CLOSE_CLOSEABLE"/>
* &lt;/array>
* &lt;/property>
* &lt;property name="featuresToDisable">
* &lt;array>
* &lt;util:constant static-field="org.codehaus.jackson.map.DeserializationConfig$Feature.USE_ANNOTATIONS"/>
* &lt;/array>
* &lt;/property>
* &lt;/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;
}
}

256
spring-web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java

@ -1,256 +0,0 @@ @@ -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;
}
}

10
spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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 @@ -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 @@ -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());
}
}
}

8
spring-web/src/main/java/org/springframework/web/client/RestTemplate.java

@ -134,10 +134,6 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat @@ -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 @@ -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 @@ -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());
}
}
/**

226
spring-web/src/test/java/org/springframework/http/converter/json/AbstractMappingJacksonHttpMessageConverterTests.java

@ -1,226 +0,0 @@ @@ -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;
}
}
}

158
spring-web/src/test/java/org/springframework/http/converter/json/JacksonObjectMapperFactoryBeanTests.java

@ -1,158 +0,0 @@ @@ -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));
}
}

198
spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java

@ -1,5 +1,5 @@ @@ -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; @@ -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.*; @@ -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();
@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());
}
@Override
protected MappingJackson2HttpMessageConverter createConverter() {
return new MappingJackson2HttpMessageConverter();
@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 @@ -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 @@ -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 @@ -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;

144
spring-web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java

@ -1,144 +0,0 @@ @@ -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;
}
}
}

22
spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java

@ -133,6 +133,9 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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;
}

24
spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java

@ -20,7 +20,6 @@ import java.util.ArrayList; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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());
}
}
/**

4
spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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}.
*/

306
spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java

@ -1,306 +0,0 @@ @@ -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);
}
}

346
spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJacksonJsonViewTests.java

@ -1,346 +0,0 @@ @@ -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);
}
}
}
}
Loading…
Cancel
Save