001/*
002 * Copyright 2002-2013 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.config;
018
019import org.w3c.dom.Element;
020import org.w3c.dom.Node;
021import org.w3c.dom.NodeList;
022
023import org.springframework.beans.factory.config.RuntimeBeanReference;
024import org.springframework.beans.factory.parsing.BeanComponentDefinition;
025import org.springframework.beans.factory.support.BeanDefinitionBuilder;
026import org.springframework.beans.factory.support.ManagedList;
027import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
028import org.springframework.beans.factory.xml.ParserContext;
029import org.springframework.util.StringUtils;
030
031/**
032 * Parser for the 'scheduled-tasks' element of the scheduling namespace.
033 *
034 * @author Mark Fisher
035 * @author Chris Beams
036 * @since 3.0
037 */
038public class ScheduledTasksBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
039
040        private static final String ELEMENT_SCHEDULED = "scheduled";
041
042        private static final long ZERO_INITIAL_DELAY = 0;
043
044
045        @Override
046        protected boolean shouldGenerateId() {
047                return true;
048        }
049
050        @Override
051        protected String getBeanClassName(Element element) {
052                return "org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar";
053        }
054
055        @Override
056        protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
057                builder.setLazyInit(false); // lazy scheduled tasks are a contradiction in terms -> force to false
058                ManagedList<RuntimeBeanReference> cronTaskList = new ManagedList<RuntimeBeanReference>();
059                ManagedList<RuntimeBeanReference> fixedDelayTaskList = new ManagedList<RuntimeBeanReference>();
060                ManagedList<RuntimeBeanReference> fixedRateTaskList = new ManagedList<RuntimeBeanReference>();
061                ManagedList<RuntimeBeanReference> triggerTaskList = new ManagedList<RuntimeBeanReference>();
062                NodeList childNodes = element.getChildNodes();
063                for (int i = 0; i < childNodes.getLength(); i++) {
064                        Node child = childNodes.item(i);
065                        if (!isScheduledElement(child, parserContext)) {
066                                continue;
067                        }
068                        Element taskElement = (Element) child;
069                        String ref = taskElement.getAttribute("ref");
070                        String method = taskElement.getAttribute("method");
071
072                        // Check that 'ref' and 'method' are specified
073                        if (!StringUtils.hasText(ref) || !StringUtils.hasText(method)) {
074                                parserContext.getReaderContext().error("Both 'ref' and 'method' are required", taskElement);
075                                // Continue with the possible next task element
076                                continue;
077                        }
078
079                        String cronAttribute = taskElement.getAttribute("cron");
080                        String fixedDelayAttribute = taskElement.getAttribute("fixed-delay");
081                        String fixedRateAttribute = taskElement.getAttribute("fixed-rate");
082                        String triggerAttribute = taskElement.getAttribute("trigger");
083                        String initialDelayAttribute = taskElement.getAttribute("initial-delay");
084
085                        boolean hasCronAttribute = StringUtils.hasText(cronAttribute);
086                        boolean hasFixedDelayAttribute = StringUtils.hasText(fixedDelayAttribute);
087                        boolean hasFixedRateAttribute = StringUtils.hasText(fixedRateAttribute);
088                        boolean hasTriggerAttribute = StringUtils.hasText(triggerAttribute);
089                        boolean hasInitialDelayAttribute = StringUtils.hasText(initialDelayAttribute);
090
091                        if (!(hasCronAttribute || hasFixedDelayAttribute || hasFixedRateAttribute || hasTriggerAttribute)) {
092                                parserContext.getReaderContext().error(
093                                                "one of the 'cron', 'fixed-delay', 'fixed-rate', or 'trigger' attributes is required", taskElement);
094                                continue; // with the possible next task element
095                        }
096
097                        if (hasInitialDelayAttribute && (hasCronAttribute || hasTriggerAttribute)) {
098                                parserContext.getReaderContext().error(
099                                                "the 'initial-delay' attribute may not be used with cron and trigger tasks", taskElement);
100                                continue; // with the possible next task element
101                        }
102
103                        String runnableName =
104                                        runnableReference(ref, method, taskElement, parserContext).getBeanName();
105
106                        if (hasFixedDelayAttribute) {
107                                fixedDelayTaskList.add(intervalTaskReference(runnableName,
108                                                initialDelayAttribute, fixedDelayAttribute, taskElement, parserContext));
109                        }
110                        if (hasFixedRateAttribute) {
111                                fixedRateTaskList.add(intervalTaskReference(runnableName,
112                                                initialDelayAttribute, fixedRateAttribute, taskElement, parserContext));
113                        }
114                        if (hasCronAttribute) {
115                                cronTaskList.add(cronTaskReference(runnableName, cronAttribute,
116                                                taskElement, parserContext));
117                        }
118                        if (hasTriggerAttribute) {
119                                String triggerName = new RuntimeBeanReference(triggerAttribute).getBeanName();
120                                triggerTaskList.add(triggerTaskReference(runnableName, triggerName,
121                                                taskElement, parserContext));
122                        }
123                }
124                String schedulerRef = element.getAttribute("scheduler");
125                if (StringUtils.hasText(schedulerRef)) {
126                        builder.addPropertyReference("taskScheduler", schedulerRef);
127                }
128                builder.addPropertyValue("cronTasksList", cronTaskList);
129                builder.addPropertyValue("fixedDelayTasksList", fixedDelayTaskList);
130                builder.addPropertyValue("fixedRateTasksList", fixedRateTaskList);
131                builder.addPropertyValue("triggerTasksList", triggerTaskList);
132        }
133
134        private boolean isScheduledElement(Node node, ParserContext parserContext) {
135                return node.getNodeType() == Node.ELEMENT_NODE &&
136                                ELEMENT_SCHEDULED.equals(parserContext.getDelegate().getLocalName(node));
137        }
138
139        private RuntimeBeanReference runnableReference(String ref, String method, Element taskElement, ParserContext parserContext) {
140                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
141                                "org.springframework.scheduling.support.ScheduledMethodRunnable");
142                builder.addConstructorArgReference(ref);
143                builder.addConstructorArgValue(method);
144                return beanReference(taskElement, parserContext, builder);
145        }
146
147        private RuntimeBeanReference intervalTaskReference(String runnableBeanName,
148                        String initialDelay, String interval, Element taskElement, ParserContext parserContext) {
149                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
150                                "org.springframework.scheduling.config.IntervalTask");
151                builder.addConstructorArgReference(runnableBeanName);
152                builder.addConstructorArgValue(interval);
153                builder.addConstructorArgValue(StringUtils.hasLength(initialDelay) ? initialDelay : ZERO_INITIAL_DELAY);
154                return beanReference(taskElement, parserContext, builder);
155        }
156
157        private RuntimeBeanReference cronTaskReference(String runnableBeanName,
158                        String cronExpression, Element taskElement, ParserContext parserContext) {
159                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
160                                "org.springframework.scheduling.config.CronTask");
161                builder.addConstructorArgReference(runnableBeanName);
162                builder.addConstructorArgValue(cronExpression);
163                return beanReference(taskElement, parserContext, builder);
164        }
165
166        private RuntimeBeanReference triggerTaskReference(String runnableBeanName,
167                        String triggerBeanName, Element taskElement, ParserContext parserContext) {
168                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
169                                "org.springframework.scheduling.config.TriggerTask");
170                builder.addConstructorArgReference(runnableBeanName);
171                builder.addConstructorArgReference(triggerBeanName);
172                return beanReference(taskElement, parserContext, builder);
173        }
174
175        private RuntimeBeanReference beanReference(Element taskElement,
176                        ParserContext parserContext, BeanDefinitionBuilder builder) {
177                // Extract the source of the current task
178                builder.getRawBeanDefinition().setSource(parserContext.extractSource(taskElement));
179                String generatedName = parserContext.getReaderContext().generateBeanName(builder.getRawBeanDefinition());
180                parserContext.registerBeanComponent(new BeanComponentDefinition(builder.getBeanDefinition(), generatedName));
181                return new RuntimeBeanReference(generatedName);
182        }
183
184}