001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.scheduling.quartz;
018
019import org.quartz.SchedulerContext;
020import org.quartz.spi.TriggerFiredBundle;
021
022import org.springframework.beans.BeanWrapper;
023import org.springframework.beans.MutablePropertyValues;
024import org.springframework.beans.PropertyAccessorFactory;
025
026/**
027 * Subclass of {@link AdaptableJobFactory} that also supports Spring-style
028 * dependency injection on bean properties. This is essentially the direct
029 * equivalent of Spring's {@link QuartzJobBean} in the shape of a Quartz
030 * {@link org.quartz.spi.JobFactory}.
031 *
032 * <p>Applies scheduler context, job data map and trigger data map entries
033 * as bean property values. If no matching bean property is found, the entry
034 * is by default simply ignored. This is analogous to QuartzJobBean's behavior.
035 *
036 * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
037 *
038 * @author Juergen Hoeller
039 * @since 2.0
040 * @see SchedulerFactoryBean#setJobFactory
041 * @see QuartzJobBean
042 */
043public class SpringBeanJobFactory extends AdaptableJobFactory implements SchedulerContextAware {
044
045        private String[] ignoredUnknownProperties;
046
047        private SchedulerContext schedulerContext;
048
049
050        /**
051         * Specify the unknown properties (not found in the bean) that should be ignored.
052         * <p>Default is {@code null}, indicating that all unknown properties
053         * should be ignored. Specify an empty array to throw an exception in case
054         * of any unknown properties, or a list of property names that should be
055         * ignored if there is no corresponding property found on the particular
056         * job class (all other unknown properties will still trigger an exception).
057         */
058        public void setIgnoredUnknownProperties(String... ignoredUnknownProperties) {
059                this.ignoredUnknownProperties = ignoredUnknownProperties;
060        }
061
062        @Override
063        public void setSchedulerContext(SchedulerContext schedulerContext) {
064                this.schedulerContext = schedulerContext;
065        }
066
067
068        /**
069         * Create the job instance, populating it with property values taken
070         * from the scheduler context, job data map and trigger data map.
071         */
072        @Override
073        protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
074                Object job = super.createJobInstance(bundle);
075                if (isEligibleForPropertyPopulation(job)) {
076                        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
077                        MutablePropertyValues pvs = new MutablePropertyValues();
078                        if (this.schedulerContext != null) {
079                                pvs.addPropertyValues(this.schedulerContext);
080                        }
081                        pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
082                        pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
083                        if (this.ignoredUnknownProperties != null) {
084                                for (String propName : this.ignoredUnknownProperties) {
085                                        if (pvs.contains(propName) && !bw.isWritableProperty(propName)) {
086                                                pvs.removePropertyValue(propName);
087                                        }
088                                }
089                                bw.setPropertyValues(pvs);
090                        }
091                        else {
092                                bw.setPropertyValues(pvs, true);
093                        }
094                }
095                return job;
096        }
097
098        /**
099         * Return whether the given job object is eligible for having
100         * its bean properties populated.
101         * <p>The default implementation ignores {@link QuartzJobBean} instances,
102         * which will inject bean properties themselves.
103         * @param jobObject the job object to introspect
104         * @see QuartzJobBean
105         */
106        protected boolean isEligibleForPropertyPopulation(Object jobObject) {
107                return (!(jobObject instanceof QuartzJobBean));
108        }
109
110}