All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.batch.core.jsr.configuration.xml.JsrBeanDefinitionDocumentReader Maven / Gradle / Ivy

/*
 * Copyright 2013-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.batch.core.jsr.configuration.xml;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.jsr.configuration.support.JsrExpressionParser;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader;
import org.springframework.util.ClassUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;

/**
 * 

* {@link DefaultBeanDefinitionDocumentReader} extension to hook into the pre processing of the provided * XML document, ensuring any references to property operators such as jobParameters and jobProperties are * resolved prior to loading the context. Since we know these initial values upfront, doing this transformation * allows us to ensure values are retrieved in their resolved form prior to loading the context and property * operators can be used on any element. This document reader will also look for references to artifacts by * the same name and create new bean definitions to provide the ability to create new instances. *

* * @author Chris Schaefer * @author Mahmoud Ben Hassine * @since 3.0 */ public class JsrBeanDefinitionDocumentReader extends DefaultBeanDefinitionDocumentReader { private static final String NULL = "null"; private static final String ROOT_JOB_ELEMENT_NAME = "job"; private static final String JOB_PROPERTY_ELEMENT_NAME = "property"; private static final String JOB_PROPERTIES_ELEMENT_NAME = "properties"; private static final String JOB_PROPERTY_ELEMENT_NAME_ATTRIBUTE = "name"; private static final String JOB_PROPERTY_ELEMENT_VALUE_ATTRIBUTE = "value"; private static final String JOB_PROPERTIES_KEY_NAME = "jobProperties"; private static final String JOB_PARAMETERS_KEY_NAME = "jobParameters"; private static final String JOB_PARAMETERS_BEAN_DEFINITION_NAME = "jsr_jobParameters"; private static final Log LOG = LogFactory.getLog(JsrBeanDefinitionDocumentReader.class); private static final Pattern PROPERTY_KEY_SEPARATOR = Pattern.compile("'([^']*?)'"); private static final Pattern OPERATOR_PATTERN = Pattern.compile("(#\\{(job(Properties|Parameters))[^}]+\\})"); private BeanDefinitionRegistry beanDefinitionRegistry; private JsrExpressionParser expressionParser = new JsrExpressionParser(); private Map propertyMap = new HashMap<>(); /** *

* Creates a new {@link JsrBeanDefinitionDocumentReader} instance. *

*/ public JsrBeanDefinitionDocumentReader() { } /** *

* Create a new {@link JsrBeanDefinitionDocumentReader} instance with the provided * {@link BeanDefinitionRegistry}. *

* * @param beanDefinitionRegistry the {@link BeanDefinitionRegistry} to use */ public JsrBeanDefinitionDocumentReader(BeanDefinitionRegistry beanDefinitionRegistry) { this.beanDefinitionRegistry = beanDefinitionRegistry; } @Override protected void preProcessXml(Element root) { if (ROOT_JOB_ELEMENT_NAME.equals(root.getLocalName())) { initProperties(root); transformDocument(root); if (LOG.isDebugEnabled()) { LOG.debug("Transformed XML from preProcessXml: " + elementToString(root)); } } } protected void initProperties(Element root) { propertyMap.put(JOB_PARAMETERS_KEY_NAME, initJobParameters()); propertyMap.put(JOB_PROPERTIES_KEY_NAME, initJobProperties(root)); resolvePropertyValues(propertyMap.get(JOB_PARAMETERS_KEY_NAME)); resolvePropertyValues(propertyMap.get(JOB_PROPERTIES_KEY_NAME)); } private Properties initJobParameters() { Properties jobParameters = new Properties(); if (getBeanDefinitionRegistry().containsBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME)) { BeanDefinition beanDefinition = getBeanDefinitionRegistry().getBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME); Properties properties = (Properties) beanDefinition.getConstructorArgumentValues() .getGenericArgumentValue(Properties.class) .getValue(); if (properties == null) { return new Properties(); } Enumeration propertyNames = properties.propertyNames(); while(propertyNames.hasMoreElements()) { String curName = (String) propertyNames.nextElement(); jobParameters.put(curName, properties.getProperty(curName)); } } return jobParameters; } private Properties initJobProperties(Element root) { Properties properties = new Properties(); Node propertiesNode = root.getElementsByTagName(JOB_PROPERTIES_ELEMENT_NAME).item(0); if(propertiesNode != null) { NodeList children = propertiesNode.getChildNodes(); for(int i=0; i < children.getLength(); i++) { Node child = children.item(i); if(JOB_PROPERTY_ELEMENT_NAME.equals(child.getLocalName())) { NamedNodeMap attributes = child.getAttributes(); Node name = attributes.getNamedItem(JOB_PROPERTY_ELEMENT_NAME_ATTRIBUTE); Node value = attributes.getNamedItem(JOB_PROPERTY_ELEMENT_VALUE_ATTRIBUTE); properties.setProperty(name.getNodeValue(), value.getNodeValue()); } } } return properties; } private void resolvePropertyValues(Properties properties) { for (String propertyKey : properties.stringPropertyNames()) { String resolvedPropertyValue = resolvePropertyValue(properties.getProperty(propertyKey)); if(!properties.getProperty(propertyKey).equals(resolvedPropertyValue)) { properties.setProperty(propertyKey, resolvedPropertyValue); } } } private String resolvePropertyValue(String propertyValue) { String resolvedValue = resolveValue(propertyValue); Matcher jobParameterMatcher = OPERATOR_PATTERN.matcher(resolvedValue); while (jobParameterMatcher.find()) { resolvedValue = resolvePropertyValue(resolvedValue); } return resolvedValue; } private String resolveValue(String value) { StringBuffer valueBuffer = new StringBuffer(); Matcher jobParameterMatcher = OPERATOR_PATTERN.matcher(value); while (jobParameterMatcher.find()) { Matcher jobParameterKeyMatcher = PROPERTY_KEY_SEPARATOR.matcher(jobParameterMatcher.group(1)); if (jobParameterKeyMatcher.find()) { String propertyType = jobParameterMatcher.group(2); String extractedProperty = jobParameterKeyMatcher.group(1); Properties properties = propertyMap.get(propertyType); if(properties == null) { throw new IllegalArgumentException("Unknown property type: " + propertyType); } String resolvedProperty = properties.getProperty(extractedProperty, NULL); if (NULL.equals(resolvedProperty) && LOG.isInfoEnabled()) { LOG.info(propertyType + " with key of: " + extractedProperty + " could not be resolved. Possible configuration error?"); } jobParameterMatcher.appendReplacement(valueBuffer, resolvedProperty); } } jobParameterMatcher.appendTail(valueBuffer); String resolvedValue = valueBuffer.toString(); if (NULL.equals(resolvedValue)) { return ""; } return expressionParser.parseExpression(resolvedValue); } private BeanDefinitionRegistry getBeanDefinitionRegistry() { return beanDefinitionRegistry != null ? beanDefinitionRegistry : getReaderContext().getRegistry(); } private void transformDocument(Element root) { DocumentTraversal traversal = (DocumentTraversal) root.getOwnerDocument(); NodeIterator iterator = traversal.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, null, true); BeanDefinitionRegistry registry = getBeanDefinitionRegistry(); Map referenceCountMap = new HashMap<>(); for (Node n = iterator.nextNode(); n != null; n = iterator.nextNode()) { NamedNodeMap map = n.getAttributes(); if (map.getLength() > 0) { for (int i = 0; i < map.getLength(); i++) { Node node = map.item(i); String nodeName = node.getNodeName(); String nodeValue = node.getNodeValue(); String resolvedValue = resolveValue(nodeValue); String newNodeValue = resolvedValue; if("ref".equals(nodeName)) { if(!referenceCountMap.containsKey(resolvedValue)) { referenceCountMap.put(resolvedValue, 0); } boolean isClass = isClass(resolvedValue); Integer referenceCount = referenceCountMap.get(resolvedValue); // possibly fully qualified class name in ref tag in the JSL or pointer to bean/artifact ref. if(isClass && !registry.containsBeanDefinition(resolvedValue)) { AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(resolvedValue) .getBeanDefinition(); beanDefinition.setScope("step"); registry.registerBeanDefinition(resolvedValue, beanDefinition); newNodeValue = resolvedValue; } else { if(registry.containsBeanDefinition(resolvedValue)) { referenceCount++; referenceCountMap.put(resolvedValue, referenceCount); newNodeValue = resolvedValue + referenceCount; BeanDefinition beanDefinition = registry.getBeanDefinition(resolvedValue); registry.registerBeanDefinition(newNodeValue, beanDefinition); } } } if(!nodeValue.equals(newNodeValue)) { node.setNodeValue(newNodeValue); } } } else { String nodeValue = n.getTextContent(); String resolvedValue = resolveValue(nodeValue); if(!nodeValue.equals(resolvedValue)) { n.setTextContent(resolvedValue); } } } } private boolean isClass(String className) { try { Class.forName(className, false, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException e) { return false; } return true; } protected Properties getJobParameters() { return propertyMap.get(JOB_PARAMETERS_KEY_NAME); } protected Properties getJobProperties() { return propertyMap.get(JOB_PROPERTIES_KEY_NAME); } private String elementToString(Element root) { DOMImplementationLS domImplLS = (DOMImplementationLS) root.getOwnerDocument().getImplementation(); return domImplLS.createLSSerializer().writeToString(root); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy