001/*
002 * Copyright 2009-2014 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 */
016package org.springframework.batch.core.configuration.xml;
017
018import java.util.ArrayList;
019import java.util.List;
020
021import org.springframework.batch.core.listener.AbstractListenerFactoryBean;
022import org.springframework.batch.core.listener.ListenerMetaData;
023import org.springframework.beans.BeanMetadataElement;
024import org.springframework.beans.factory.config.BeanDefinition;
025import org.springframework.beans.factory.config.BeanDefinitionHolder;
026import org.springframework.beans.factory.config.RuntimeBeanReference;
027import org.springframework.beans.factory.support.AbstractBeanDefinition;
028import org.springframework.beans.factory.support.BeanDefinitionBuilder;
029import org.springframework.beans.factory.support.ManagedMap;
030import org.springframework.beans.factory.xml.ParserContext;
031import org.springframework.util.StringUtils;
032import org.springframework.util.xml.DomUtils;
033import org.w3c.dom.Element;
034
035/**
036 * @author Dan Garrette
037 * @since 2.0
038 * @see StepListenerParser
039 * @see JobExecutionListenerParser
040 */
041public abstract class AbstractListenerParser {
042
043        private static final String ID_ATTR = "id";
044
045        private static final String REF_ATTR = "ref";
046
047        private static final String BEAN_ELE = "bean";
048
049        private static final String REF_ELE = "ref";
050
051        public AbstractBeanDefinition parse(Element element, ParserContext parserContext) {
052                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(getBeanClass());
053                doParse(element, parserContext, builder);
054                return builder.getBeanDefinition();
055        }
056
057        public void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
058                builder.addPropertyValue("delegate", parseListenerElement(element, parserContext, builder.getRawBeanDefinition()));
059
060                ManagedMap<String, String> metaDataMap = new ManagedMap<String, String>();
061                for (String metaDataPropertyName : getMethodNameAttributes()) {
062                        String listenerMethod = element.getAttribute(metaDataPropertyName);
063                        if (StringUtils.hasText(listenerMethod)) {
064                                metaDataMap.put(metaDataPropertyName, listenerMethod);
065                        }
066                }
067                builder.addPropertyValue("metaDataMap", metaDataMap);
068        }
069
070        public static BeanMetadataElement parseListenerElement(Element element, ParserContext parserContext, BeanDefinition enclosing) {
071                String listenerRef = element.getAttribute(REF_ATTR);
072                List<Element> beanElements = DomUtils.getChildElementsByTagName(element, BEAN_ELE);
073                List<Element> refElements = DomUtils.getChildElementsByTagName(element, REF_ELE);
074
075                verifyListenerAttributesAndSubelements(listenerRef, beanElements, refElements, element, parserContext);
076
077                if (StringUtils.hasText(listenerRef)) {
078                        return new RuntimeBeanReference(listenerRef);
079                }
080                else if (beanElements.size() == 1) {
081                        Element beanElement = beanElements.get(0);
082                        BeanDefinitionHolder beanDefinitionHolder = parserContext.getDelegate().parseBeanDefinitionElement(
083                                        beanElement, enclosing);
084                        parserContext.getDelegate().decorateBeanDefinitionIfRequired(beanElement, beanDefinitionHolder);
085                        return beanDefinitionHolder;
086                }
087                else {
088                        return (BeanMetadataElement) parserContext.getDelegate().parsePropertySubElement(refElements.get(0), null);
089                }
090        }
091
092        private static void verifyListenerAttributesAndSubelements(String listenerRef, List<Element> beanElements,
093                        List<Element> refElements, Element element, ParserContext parserContext) {
094                int total = (StringUtils.hasText(listenerRef) ? 1 : 0) + beanElements.size() + refElements.size();
095                if (total != 1) {
096                        StringBuilder found = new StringBuilder();
097                        if (total == 0) {
098                                found.append("None");
099                        }
100                        else {
101                                if (StringUtils.hasText(listenerRef)) {
102                                        found.append("'" + REF_ATTR + "' attribute, ");
103                                }
104                                if (beanElements.size() == 1) {
105                                        found.append("<" + BEAN_ELE + "/> element, ");
106                                }
107                                else if (beanElements.size() > 1) {
108                                        found.append(beanElements.size() + " <" + BEAN_ELE + "/> elements, ");
109                                }
110                                if (refElements.size() == 1) {
111                                        found.append("<" + REF_ELE + "/> element, ");
112                                }
113                                else if (refElements.size() > 1) {
114                                        found.append(refElements.size() + " <" + REF_ELE + "/> elements, ");
115                                }
116                                found.delete(found.length() - 2, found.length());
117                        }
118
119                        String id = element.getAttribute(ID_ATTR);
120                        parserContext.getReaderContext().error(
121                                        "The <" + element.getTagName() + (StringUtils.hasText(id) ? " id=\"" + id + "\"" : "")
122                                                        + "/> element must have exactly one of: '" + REF_ATTR + "' attribute, <" + BEAN_ELE
123                                                        + "/> attribute, or <" + REF_ELE + "/> element.  Found: " + found + ".", element);
124                }
125        }
126
127        private List<String> getMethodNameAttributes() {
128                List<String> methodNameAttributes = new ArrayList<String>();
129                for (ListenerMetaData metaData : getMetaDataValues()) {
130                        methodNameAttributes.add(metaData.getPropertyName());
131                }
132                return methodNameAttributes;
133        }
134
135        protected abstract Class<? extends AbstractListenerFactoryBean<?>> getBeanClass();
136
137        protected abstract ListenerMetaData[] getMetaDataValues();
138
139}