001/*
002 * Copyright 2002-2018 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;
025import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
026import org.springframework.context.ApplicationContext;
027import org.springframework.context.ApplicationContextAware;
028import org.springframework.lang.Nullable;
029
030/**
031 * Subclass of {@link AdaptableJobFactory} that also supports Spring-style
032 * dependency injection on bean properties. This is essentially the direct
033 * equivalent of Spring's {@link QuartzJobBean} in the shape of a Quartz
034 * {@link org.quartz.spi.JobFactory}.
035 *
036 * <p>Applies scheduler context, job data map and trigger data map entries
037 * as bean property values. If no matching bean property is found, the entry
038 * is by default simply ignored. This is analogous to QuartzJobBean's behavior.
039 *
040 * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
041 *
042 * @author Juergen Hoeller
043 * @since 2.0
044 * @see SchedulerFactoryBean#setJobFactory
045 * @see QuartzJobBean
046 */
047public class SpringBeanJobFactory extends AdaptableJobFactory
048                implements ApplicationContextAware, SchedulerContextAware {
049
050        @Nullable
051        private String[] ignoredUnknownProperties;
052
053        @Nullable
054        private ApplicationContext applicationContext;
055
056        @Nullable
057        private SchedulerContext schedulerContext;
058
059
060        /**
061         * Specify the unknown properties (not found in the bean) that should be ignored.
062         * <p>Default is {@code null}, indicating that all unknown properties
063         * should be ignored. Specify an empty array to throw an exception in case
064         * of any unknown properties, or a list of property names that should be
065         * ignored if there is no corresponding property found on the particular
066         * job class (all other unknown properties will still trigger an exception).
067         */
068        public void setIgnoredUnknownProperties(String... ignoredUnknownProperties) {
069                this.ignoredUnknownProperties = ignoredUnknownProperties;
070        }
071
072        @Override
073        public void setApplicationContext(ApplicationContext applicationContext) {
074                this.applicationContext = applicationContext;
075        }
076
077        @Override
078        public void setSchedulerContext(SchedulerContext schedulerContext) {
079                this.schedulerContext = schedulerContext;
080        }
081
082
083        /**
084         * Create the job instance, populating it with property values taken
085         * from the scheduler context, job data map and trigger data map.
086         */
087        @Override
088        protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
089                Object job = (this.applicationContext != null ?
090                                this.applicationContext.getAutowireCapableBeanFactory().createBean(
091                                                bundle.getJobDetail().getJobClass(), AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false) :
092                                super.createJobInstance(bundle));
093
094                if (isEligibleForPropertyPopulation(job)) {
095                        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
096                        MutablePropertyValues pvs = new MutablePropertyValues();
097                        if (this.schedulerContext != null) {
098                                pvs.addPropertyValues(this.schedulerContext);
099                        }
100                        pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
101                        pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
102                        if (this.ignoredUnknownProperties != null) {
103                                for (String propName : this.ignoredUnknownProperties) {
104                                        if (pvs.contains(propName) && !bw.isWritableProperty(propName)) {
105                                                pvs.removePropertyValue(propName);
106                                        }
107                                }
108                                bw.setPropertyValues(pvs);
109                        }
110                        else {
111                                bw.setPropertyValues(pvs, true);
112                        }
113                }
114
115                return job;
116        }
117
118        /**
119         * Return whether the given job object is eligible for having
120         * its bean properties populated.
121         * <p>The default implementation ignores {@link QuartzJobBean} instances,
122         * which will inject bean properties themselves.
123         * @param jobObject the job object to introspect
124         * @see QuartzJobBean
125         */
126        protected boolean isEligibleForPropertyPopulation(Object jobObject) {
127                return (!(jobObject instanceof QuartzJobBean));
128        }
129
130}