39 changed files with 331 additions and 4185 deletions
@ -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()); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -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(); |
||||
|
||||
} |
||||
@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -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()); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -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"> |
||||
* <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"> |
||||
* <property name="objectMapper"> |
||||
* <bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean" |
||||
* p:autoDetectFields="false" |
||||
* p:autoDetectGettersSetters="false" |
||||
* p:annotationIntrospector-ref="jaxbAnnotationIntrospector" /> |
||||
* </property> |
||||
* </bean> |
||||
* </pre> |
||||
* |
||||
* <p>Example usage with MappingJacksonJsonView: |
||||
* <pre class="code"> |
||||
* <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"> |
||||
* <property name="objectMapper"> |
||||
* <bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean" |
||||
* p:autoDetectFields="false" |
||||
* p:autoDetectGettersSetters="false" |
||||
* p:annotationIntrospector-ref="jaxbAnnotationIntrospector" /> |
||||
* </property> |
||||
* </bean> |
||||
* </pre> |
||||
* |
||||
* <p>In case there are no specific setters provided (for some rarely used |
||||
* options), you can still use the more general methods |
||||
* {@link #setFeaturesToEnable(Object[])} and {@link #setFeaturesToDisable(Object[])}. |
||||
* |
||||
* <pre class="code"> |
||||
* <bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean"> |
||||
* <property name="featuresToEnable"> |
||||
* <array> |
||||
* <util:constant static-field="org.codehaus.jackson.map.SerializationConfig$Feature.WRAP_ROOT_VALUE"/> |
||||
* <util:constant static-field="org.codehaus.jackson.map.SerializationConfig$Feature.CLOSE_CLOSEABLE"/> |
||||
* </array> |
||||
* </property> |
||||
* <property name="featuresToDisable"> |
||||
* <array> |
||||
* <util:constant static-field="org.codehaus.jackson.map.DeserializationConfig$Feature.USE_ANNOTATIONS"/> |
||||
* </array> |
||||
* </property> |
||||
* </bean> |
||||
* </pre> |
||||
* |
||||
* <p><b>NOTE:</b> Requires Jackson 1.8 or higher, as of Spring 4.0. |
||||
* At the same time, we strongly recommend a migration to Jackson 2.x! |
||||
* |
||||
* @author <a href="mailto:dmitry.katsubo@gmail.com">Dmitry Katsubo</a> |
||||
* @author Rossen Stoyanchev |
||||
* @since 3.2 |
||||
* @deprecated Please migrate to {@link Jackson2ObjectMapperFactoryBean} for Jackson 2.x. |
||||
*/ |
||||
@Deprecated |
||||
public class JacksonObjectMapperFactoryBean implements FactoryBean<ObjectMapper>, InitializingBean { |
||||
|
||||
private ObjectMapper objectMapper; |
||||
|
||||
private Map<Object, Boolean> features = new HashMap<Object, Boolean>(); |
||||
|
||||
private DateFormat dateFormat; |
||||
|
||||
private AnnotationIntrospector annotationIntrospector; |
||||
|
||||
|
||||
/** |
||||
* Set the ObjectMapper instance to use. |
||||
* If not set an instance will be created using the default constructor. |
||||
*/ |
||||
public void setObjectMapper(ObjectMapper objectMapper) { |
||||
this.objectMapper = objectMapper; |
||||
} |
||||
|
||||
/** |
||||
* Define the format for date/time with the given {@link DateFormat}. |
||||
* <p>Note: Setting this property makes the exposed {@link ObjectMapper} |
||||
* non-thread-safe, according to Jackson's thread safety rules. |
||||
* @see #setSimpleDateFormat(String) |
||||
*/ |
||||
public void setDateFormat(DateFormat dateFormat) { |
||||
this.dateFormat = dateFormat; |
||||
} |
||||
|
||||
/** |
||||
* Define the date/time format with a {@link SimpleDateFormat}. |
||||
* <p>Note: Setting this property makes the exposed {@link ObjectMapper} |
||||
* non-thread-safe, according to Jackson's thread safety rules. |
||||
* @see #setDateFormat(DateFormat) |
||||
*/ |
||||
public void setSimpleDateFormat(String format) { |
||||
this.dateFormat = new SimpleDateFormat(format); |
||||
} |
||||
|
||||
/** |
||||
* Set the {@link AnnotationIntrospector} for serialization and deserialization. |
||||
* @see SerializationConfig#setAnnotationIntrospector(AnnotationIntrospector) |
||||
* @see DeserializationConfig#setAnnotationIntrospector(AnnotationIntrospector) |
||||
*/ |
||||
public void setAnnotationIntrospector(AnnotationIntrospector annotationIntrospector) { |
||||
this.annotationIntrospector = annotationIntrospector; |
||||
} |
||||
|
||||
/** |
||||
* Shortcut for {@link org.codehaus.jackson.map.SerializationConfig.Feature#AUTO_DETECT_FIELDS} and |
||||
* {@link org.codehaus.jackson.map.DeserializationConfig.Feature#AUTO_DETECT_FIELDS}. |
||||
*/ |
||||
public void setAutoDetectFields(boolean autoDetectFields) { |
||||
this.features.put(SerializationConfig.Feature.AUTO_DETECT_FIELDS, autoDetectFields); |
||||
this.features.put(DeserializationConfig.Feature.AUTO_DETECT_FIELDS, autoDetectFields); |
||||
} |
||||
|
||||
/** |
||||
* Shortcut for {@link org.codehaus.jackson.map.SerializationConfig.Feature#AUTO_DETECT_GETTERS} and |
||||
* {@link org.codehaus.jackson.map.DeserializationConfig.Feature#AUTO_DETECT_SETTERS}. |
||||
*/ |
||||
public void setAutoDetectGettersSetters(boolean autoDetectGettersSetters) { |
||||
this.features.put(SerializationConfig.Feature.AUTO_DETECT_GETTERS, autoDetectGettersSetters); |
||||
this.features.put(DeserializationConfig.Feature.AUTO_DETECT_SETTERS, autoDetectGettersSetters); |
||||
} |
||||
|
||||
/** |
||||
* Shortcut for {@link org.codehaus.jackson.map.SerializationConfig.Feature#FAIL_ON_EMPTY_BEANS}. |
||||
*/ |
||||
public void setFailOnEmptyBeans(boolean failOnEmptyBeans) { |
||||
this.features.put(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, failOnEmptyBeans); |
||||
} |
||||
|
||||
/** |
||||
* Shortcut for {@link org.codehaus.jackson.map.SerializationConfig.Feature#INDENT_OUTPUT}. |
||||
*/ |
||||
public void setIndentOutput(boolean indentOutput) { |
||||
this.features.put(SerializationConfig.Feature.INDENT_OUTPUT, indentOutput); |
||||
} |
||||
|
||||
/** |
||||
* Specify features to enable. |
||||
* @see org.codehaus.jackson.JsonParser.Feature |
||||
* @see org.codehaus.jackson.JsonGenerator.Feature |
||||
* @see org.codehaus.jackson.map.SerializationConfig.Feature |
||||
* @see org.codehaus.jackson.map.DeserializationConfig.Feature |
||||
*/ |
||||
public void setFeaturesToEnable(Object[] featuresToEnable) { |
||||
if (featuresToEnable != null) { |
||||
for (Object feature : featuresToEnable) { |
||||
this.features.put(feature, Boolean.TRUE); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Specify features to disable. |
||||
* @see org.codehaus.jackson.JsonParser.Feature |
||||
* @see org.codehaus.jackson.JsonGenerator.Feature |
||||
* @see org.codehaus.jackson.map.SerializationConfig.Feature |
||||
* @see org.codehaus.jackson.map.DeserializationConfig.Feature |
||||
*/ |
||||
public void setFeaturesToDisable(Object[] featuresToDisable) { |
||||
if (featuresToDisable != null) { |
||||
for (Object feature : featuresToDisable) { |
||||
this.features.put(feature, Boolean.FALSE); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void afterPropertiesSet() { |
||||
if (this.objectMapper == null) { |
||||
this.objectMapper = new ObjectMapper(); |
||||
} |
||||
if (this.annotationIntrospector != null) { |
||||
this.objectMapper.setSerializationConfig( |
||||
this.objectMapper.getSerializationConfig().withAnnotationIntrospector(this.annotationIntrospector)); |
||||
this.objectMapper.setDeserializationConfig( |
||||
this.objectMapper.getDeserializationConfig().withAnnotationIntrospector(this.annotationIntrospector)); |
||||
} |
||||
if (this.dateFormat != null) { |
||||
this.objectMapper.setDateFormat(this.dateFormat); |
||||
} |
||||
for (Map.Entry<Object, Boolean> entry : this.features.entrySet()) { |
||||
configureFeature(entry.getKey(), entry.getValue()); |
||||
} |
||||
} |
||||
|
||||
private void configureFeature(Object feature, boolean enabled) { |
||||
if (feature instanceof JsonParser.Feature) { |
||||
this.objectMapper.configure((JsonParser.Feature) feature, enabled); |
||||
} |
||||
else if (feature instanceof JsonGenerator.Feature) { |
||||
this.objectMapper.configure((JsonGenerator.Feature) feature, enabled); |
||||
} |
||||
else if (feature instanceof SerializationConfig.Feature) { |
||||
this.objectMapper.configure((SerializationConfig.Feature) feature, enabled); |
||||
} |
||||
else if (feature instanceof DeserializationConfig.Feature) { |
||||
this.objectMapper.configure((DeserializationConfig.Feature) feature, enabled); |
||||
} |
||||
else { |
||||
throw new IllegalArgumentException("Unknown feature class: " + feature.getClass().getName()); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Return the singleton ObjectMapper. |
||||
*/ |
||||
@Override |
||||
public ObjectMapper getObject() { |
||||
return this.objectMapper; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> getObjectType() { |
||||
return ObjectMapper.class; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isSingleton() { |
||||
return true; |
||||
} |
||||
|
||||
} |
||||
@ -1,256 +0,0 @@
@@ -1,256 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2013 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.http.converter.json; |
||||
|
||||
import java.io.IOException; |
||||
import java.lang.reflect.Type; |
||||
import java.nio.charset.Charset; |
||||
|
||||
import org.codehaus.jackson.JsonEncoding; |
||||
import org.codehaus.jackson.JsonGenerator; |
||||
import org.codehaus.jackson.JsonProcessingException; |
||||
import org.codehaus.jackson.map.ObjectMapper; |
||||
import org.codehaus.jackson.map.SerializationConfig; |
||||
import org.codehaus.jackson.type.JavaType; |
||||
|
||||
import org.springframework.http.HttpInputMessage; |
||||
import org.springframework.http.HttpOutputMessage; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.converter.AbstractHttpMessageConverter; |
||||
import org.springframework.http.converter.GenericHttpMessageConverter; |
||||
import org.springframework.http.converter.HttpMessageNotReadableException; |
||||
import org.springframework.http.converter.HttpMessageNotWritableException; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that |
||||
* can read and write JSON using <a href="http://jackson.codehaus.org/">Jackson 1.x's</a> {@link ObjectMapper}. |
||||
* |
||||
* <p>This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances. |
||||
* |
||||
* <p>By default, this converter supports {@code application/json}. This can be overridden by setting the |
||||
* {@link #setSupportedMediaTypes supportedMediaTypes} property. |
||||
* |
||||
* <p><b>NOTE:</b> Requires Jackson 1.8 or higher, as of Spring 4.0. |
||||
* At the same time, we strongly recommend a migration to Jackson 2.x! |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @author Juergen Hoeller |
||||
* @since 3.0 |
||||
* @deprecated Please migrate to {@link MappingJackson2HttpMessageConverter} for Jackson 2.x. |
||||
*/ |
||||
@Deprecated |
||||
public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter<Object> |
||||
implements GenericHttpMessageConverter<Object> { |
||||
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); |
||||
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
||||
private String jsonPrefix; |
||||
|
||||
private Boolean prettyPrint; |
||||
|
||||
|
||||
/** |
||||
* Construct a new {@code MappingJacksonHttpMessageConverter}. |
||||
*/ |
||||
public MappingJacksonHttpMessageConverter() { |
||||
super(new MediaType("application", "json", DEFAULT_CHARSET), |
||||
new MediaType("application", "*+json", DEFAULT_CHARSET)); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set the {@code ObjectMapper} for this view. |
||||
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used. |
||||
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control of the JSON |
||||
* serialization process. For example, an extended {@link org.codehaus.jackson.map.SerializerFactory} |
||||
* can be configured that provides custom serializers for specific types. The other option for refining |
||||
* the serialization process is to use Jackson's provided annotations on the types to be serialized, |
||||
* in which case a custom-configured ObjectMapper is unnecessary. |
||||
*/ |
||||
public void setObjectMapper(ObjectMapper objectMapper) { |
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null"); |
||||
this.objectMapper = objectMapper; |
||||
configurePrettyPrint(); |
||||
} |
||||
|
||||
/** |
||||
* Return the underlying {@code ObjectMapper} for this view. |
||||
*/ |
||||
public ObjectMapper getObjectMapper() { |
||||
return this.objectMapper; |
||||
} |
||||
|
||||
/** |
||||
* Specify a custom prefix to use for this view's JSON output. |
||||
* Default is none. |
||||
* @see #setPrefixJson |
||||
*/ |
||||
public void setJsonPrefix(String jsonPrefix) { |
||||
this.jsonPrefix = jsonPrefix; |
||||
} |
||||
|
||||
/** |
||||
* Indicate whether the JSON output by this view should be prefixed with "{} &&". Default is false. |
||||
* <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking. |
||||
* The prefix renders the string syntactically invalid as a script so that it cannot be hijacked. |
||||
* This prefix does not affect the evaluation of JSON, but if JSON validation is performed on the |
||||
* string, the prefix would need to be ignored. |
||||
* @see #setJsonPrefix |
||||
*/ |
||||
public void setPrefixJson(boolean prefixJson) { |
||||
this.jsonPrefix = (prefixJson ? "{} && " : null); |
||||
} |
||||
|
||||
/** |
||||
* Whether to use the {@link org.codehaus.jackson.util.DefaultPrettyPrinter} when writing JSON. |
||||
* This is a shortcut for setting up an {@code ObjectMapper} as follows: |
||||
* <pre class="code"> |
||||
* ObjectMapper mapper = new ObjectMapper(); |
||||
* mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); |
||||
* converter.setObjectMapper(mapper); |
||||
* </pre> |
||||
* <p>The default value is {@code false}. |
||||
*/ |
||||
public void setPrettyPrint(boolean prettyPrint) { |
||||
this.prettyPrint = prettyPrint; |
||||
configurePrettyPrint(); |
||||
} |
||||
|
||||
private void configurePrettyPrint() { |
||||
if (this.prettyPrint != null) { |
||||
this.objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, this.prettyPrint); |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) { |
||||
return canRead(clazz, null, mediaType); |
||||
} |
||||
|
||||
@Override |
||||
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) { |
||||
JavaType javaType = getJavaType(type, contextClass); |
||||
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType)); |
||||
} |
||||
|
||||
@Override |
||||
public boolean canWrite(Class<?> clazz, MediaType mediaType) { |
||||
return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType)); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean supports(Class<?> clazz) { |
||||
// should not be called, since we override canRead/Write instead
|
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) |
||||
throws IOException, HttpMessageNotReadableException { |
||||
|
||||
JavaType javaType = getJavaType(clazz, null); |
||||
return readJavaType(javaType, inputMessage); |
||||
} |
||||
|
||||
@Override |
||||
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) |
||||
throws IOException, HttpMessageNotReadableException { |
||||
|
||||
JavaType javaType = getJavaType(type, contextClass); |
||||
return readJavaType(javaType, inputMessage); |
||||
} |
||||
|
||||
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) { |
||||
try { |
||||
return this.objectMapper.readValue(inputMessage.getBody(), javaType); |
||||
} |
||||
catch (IOException ex) { |
||||
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void writeInternal(Object object, HttpOutputMessage outputMessage) |
||||
throws IOException, HttpMessageNotWritableException { |
||||
|
||||
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType()); |
||||
JsonGenerator jsonGenerator = |
||||
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding); |
||||
|
||||
// A workaround for JsonGenerators not applying serialization features
|
||||
// https://github.com/FasterXML/jackson-databind/issues/12
|
||||
if (this.objectMapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.INDENT_OUTPUT)) { |
||||
jsonGenerator.useDefaultPrettyPrinter(); |
||||
} |
||||
|
||||
try { |
||||
if (this.jsonPrefix != null) { |
||||
jsonGenerator.writeRaw(this.jsonPrefix); |
||||
} |
||||
this.objectMapper.writeValue(jsonGenerator, object); |
||||
} |
||||
catch (JsonProcessingException ex) { |
||||
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Return the Jackson {@link JavaType} for the specified type and context class. |
||||
* <p>The default implementation returns {@code typeFactory.constructType(type, contextClass)}, |
||||
* but this can be overridden in subclasses, to allow for custom generic collection handling. |
||||
* For instance: |
||||
* <pre class="code"> |
||||
* protected JavaType getJavaType(Type type) { |
||||
* if (type instanceof Class && List.class.isAssignableFrom((Class)type)) { |
||||
* return TypeFactory.collectionType(ArrayList.class, MyBean.class); |
||||
* } else { |
||||
* return super.getJavaType(type); |
||||
* } |
||||
* } |
||||
* </pre> |
||||
* @param type the type to return the java type for |
||||
* @param contextClass a context class for the target type, for example a class
|
||||
* in which the target type appears in a method signature, can be {@code null} |
||||
* @return the java type |
||||
*/ |
||||
protected JavaType getJavaType(Type type, Class<?> contextClass) { |
||||
return this.objectMapper.getTypeFactory().constructType(type, contextClass); |
||||
} |
||||
|
||||
/** |
||||
* Determine the JSON encoding to use for the given content type. |
||||
* @param contentType the media type as requested by the caller |
||||
* @return the JSON encoding to use (never {@code null}) |
||||
*/ |
||||
protected JsonEncoding getJsonEncoding(MediaType contentType) { |
||||
if (contentType != null && contentType.getCharSet() != null) { |
||||
Charset charset = contentType.getCharSet(); |
||||
for (JsonEncoding encoding : JsonEncoding.values()) { |
||||
if (charset.name().equals(encoding.getJavaName())) { |
||||
return encoding; |
||||
} |
||||
} |
||||
} |
||||
return JsonEncoding.UTF8; |
||||
} |
||||
|
||||
} |
||||
@ -1,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; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -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)); |
||||
} |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -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…
Reference in new issue