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.beans.factory.xml;
018
019import org.w3c.dom.Element;
020
021import org.springframework.beans.factory.BeanDefinitionStoreException;
022import org.springframework.beans.factory.config.BeanDefinition;
023import org.springframework.beans.factory.config.BeanDefinitionHolder;
024import org.springframework.beans.factory.parsing.BeanComponentDefinition;
025import org.springframework.beans.factory.support.AbstractBeanDefinition;
026import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
027import org.springframework.beans.factory.support.BeanDefinitionRegistry;
028import org.springframework.lang.Nullable;
029import org.springframework.util.StringUtils;
030
031/**
032 * Abstract {@link BeanDefinitionParser} implementation providing
033 * a number of convenience methods and a
034 * {@link AbstractBeanDefinitionParser#parseInternal template method}
035 * that subclasses must override to provide the actual parsing logic.
036 *
037 * <p>Use this {@link BeanDefinitionParser} implementation when you want
038 * to parse some arbitrarily complex XML into one or more
039 * {@link BeanDefinition BeanDefinitions}. If you just want to parse some
040 * XML into a single {@code BeanDefinition}, you may wish to consider
041 * the simpler convenience extensions of this class, namely
042 * {@link AbstractSingleBeanDefinitionParser} and
043 * {@link AbstractSimpleBeanDefinitionParser}.
044 *
045 * @author Rob Harrop
046 * @author Juergen Hoeller
047 * @author Rick Evans
048 * @author Dave Syer
049 * @since 2.0
050 */
051public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser {
052
053        /** Constant for the "id" attribute. */
054        public static final String ID_ATTRIBUTE = "id";
055
056        /** Constant for the "name" attribute. */
057        public static final String NAME_ATTRIBUTE = "name";
058
059
060        @Override
061        @Nullable
062        public final BeanDefinition parse(Element element, ParserContext parserContext) {
063                AbstractBeanDefinition definition = parseInternal(element, parserContext);
064                if (definition != null && !parserContext.isNested()) {
065                        try {
066                                String id = resolveId(element, definition, parserContext);
067                                if (!StringUtils.hasText(id)) {
068                                        parserContext.getReaderContext().error(
069                                                        "Id is required for element '" + parserContext.getDelegate().getLocalName(element)
070                                                                        + "' when used as a top-level tag", element);
071                                }
072                                String[] aliases = null;
073                                if (shouldParseNameAsAliases()) {
074                                        String name = element.getAttribute(NAME_ATTRIBUTE);
075                                        if (StringUtils.hasLength(name)) {
076                                                aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
077                                        }
078                                }
079                                BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
080                                registerBeanDefinition(holder, parserContext.getRegistry());
081                                if (shouldFireEvents()) {
082                                        BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
083                                        postProcessComponentDefinition(componentDefinition);
084                                        parserContext.registerComponent(componentDefinition);
085                                }
086                        }
087                        catch (BeanDefinitionStoreException ex) {
088                                String msg = ex.getMessage();
089                                parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
090                                return null;
091                        }
092                }
093                return definition;
094        }
095
096        /**
097         * Resolve the ID for the supplied {@link BeanDefinition}.
098         * <p>When using {@link #shouldGenerateId generation}, a name is generated automatically.
099         * Otherwise, the ID is extracted from the "id" attribute, potentially with a
100         * {@link #shouldGenerateIdAsFallback() fallback} to a generated id.
101         * @param element the element that the bean definition has been built from
102         * @param definition the bean definition to be registered
103         * @param parserContext the object encapsulating the current state of the parsing process;
104         * provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
105         * @return the resolved id
106         * @throws BeanDefinitionStoreException if no unique name could be generated
107         * for the given bean definition
108         */
109        protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
110                        throws BeanDefinitionStoreException {
111
112                if (shouldGenerateId()) {
113                        return parserContext.getReaderContext().generateBeanName(definition);
114                }
115                else {
116                        String id = element.getAttribute(ID_ATTRIBUTE);
117                        if (!StringUtils.hasText(id) && shouldGenerateIdAsFallback()) {
118                                id = parserContext.getReaderContext().generateBeanName(definition);
119                        }
120                        return id;
121                }
122        }
123
124        /**
125         * Register the supplied {@link BeanDefinitionHolder bean} with the supplied
126         * {@link BeanDefinitionRegistry registry}.
127         * <p>Subclasses can override this method to control whether or not the supplied
128         * {@link BeanDefinitionHolder bean} is actually even registered, or to
129         * register even more beans.
130         * <p>The default implementation registers the supplied {@link BeanDefinitionHolder bean}
131         * with the supplied {@link BeanDefinitionRegistry registry} only if the {@code isNested}
132         * parameter is {@code false}, because one typically does not want inner beans
133         * to be registered as top level beans.
134         * @param definition the bean definition to be registered
135         * @param registry the registry that the bean is to be registered with
136         * @see BeanDefinitionReaderUtils#registerBeanDefinition(BeanDefinitionHolder, BeanDefinitionRegistry)
137         */
138        protected void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
139                BeanDefinitionReaderUtils.registerBeanDefinition(definition, registry);
140        }
141
142
143        /**
144         * Central template method to actually parse the supplied {@link Element}
145         * into one or more {@link BeanDefinition BeanDefinitions}.
146         * @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions}
147         * @param parserContext the object encapsulating the current state of the parsing process;
148         * provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
149         * @return the primary {@link BeanDefinition} resulting from the parsing of the supplied {@link Element}
150         * @see #parse(org.w3c.dom.Element, ParserContext)
151         * @see #postProcessComponentDefinition(org.springframework.beans.factory.parsing.BeanComponentDefinition)
152         */
153        @Nullable
154        protected abstract AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext);
155
156        /**
157         * Should an ID be generated instead of read from the passed in {@link Element}?
158         * <p>Disabled by default; subclasses can override this to enable ID generation.
159         * Note that this flag is about <i>always</i> generating an ID; the parser
160         * won't even check for an "id" attribute in this case.
161         * @return whether the parser should always generate an id
162         */
163        protected boolean shouldGenerateId() {
164                return false;
165        }
166
167        /**
168         * Should an ID be generated instead if the passed in {@link Element} does not
169         * specify an "id" attribute explicitly?
170         * <p>Disabled by default; subclasses can override this to enable ID generation
171         * as fallback: The parser will first check for an "id" attribute in this case,
172         * only falling back to a generated ID if no value was specified.
173         * @return whether the parser should generate an id if no id was specified
174         */
175        protected boolean shouldGenerateIdAsFallback() {
176                return false;
177        }
178
179        /**
180         * Determine whether the element's "name" attribute should get parsed as
181         * bean definition aliases, i.e. alternative bean definition names.
182         * <p>The default implementation returns {@code true}.
183         * @return whether the parser should evaluate the "name" attribute as aliases
184         * @since 4.1.5
185         */
186        protected boolean shouldParseNameAsAliases() {
187                return true;
188        }
189
190        /**
191         * Determine whether this parser is supposed to fire a
192         * {@link org.springframework.beans.factory.parsing.BeanComponentDefinition}
193         * event after parsing the bean definition.
194         * <p>This implementation returns {@code true} by default; that is,
195         * an event will be fired when a bean definition has been completely parsed.
196         * Override this to return {@code false} in order to suppress the event.
197         * @return {@code true} in order to fire a component registration event
198         * after parsing the bean definition; {@code false} to suppress the event
199         * @see #postProcessComponentDefinition
200         * @see org.springframework.beans.factory.parsing.ReaderContext#fireComponentRegistered
201         */
202        protected boolean shouldFireEvents() {
203                return true;
204        }
205
206        /**
207         * Hook method called after the primary parsing of a
208         * {@link BeanComponentDefinition} but before the
209         * {@link BeanComponentDefinition} has been registered with a
210         * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}.
211         * <p>Derived classes can override this method to supply any custom logic that
212         * is to be executed after all the parsing is finished.
213         * <p>The default implementation is a no-op.
214         * @param componentDefinition the {@link BeanComponentDefinition} that is to be processed
215         */
216        protected void postProcessComponentDefinition(BeanComponentDefinition componentDefinition) {
217        }
218
219}