/*
* Copyright 2008-2014 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
*
* http://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.data.jdbc.config.oracle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StringUtils;
import org.springframework.util.SystemPropertyUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
/**
* BeanDefinitionParser for the "pooling-data-source" element of the "orcl" name space
*
* @author Thomas Risberg
* @since 1.0
*/
public class PoolingDataSourceBeanDefinitionParser extends AbstractBeanDefinitionParser {
private static final String DEFAULT_PROPERTY_FILE_LOCATION = "classpath:orcl.properties";
private static final String DEFAULT_PROPERTY_PREFIX = null;
private static final String DEFAULT_CONNECTION_CACHING_ENABLED = "true";
// Property file attributes
private static final String CONNECTION_PROPERTIES_CHILD_ELEMENT = "connection-properties";
private static final String CONNECTION_CACHE_PROPERTIES_CHILD_ELEMENT = "connection-cache-properties";
private static final String USERNAME_CONNECTION_PROXY = "username-connection-proxy";
private static final String CONNECTION_CONTEXT_PROVIDER = "connection-context-provider";
private static final String PROPERTIES_LOCATION_ATTRIBUTE = "properties-location";
private static final String CONNECTION_PROPERTIES_PREFIX_ATTRIBUTE = "connection-properties-prefix";
private static final String CONNECTON_CACHE_PROPERTIS_PREFIX_ATTRIBUTE = "connection-cache-properties-prefix";
// Required attributes
private static final String URL_ATTRIBUTE = "url";
// Optional attributes
private static final String USERNAME_ATTRIBUTE = "username";
private static final String PASSWORD_ATTRIBUTE = "password";
private static final String ONS_CONFIGURATION_ATTRIBUTE = "ONS-configuration";
private static final String FAST_CONNECTION_FAILOVER_ENABLED_ATTRIBUTE = "fast-connection-failover-enabled";
private static final String CONNECTION_CACHING_ENABLED_ATTRIBUTE = "connection-caching-enabled";
protected final Log logger = LogFactory.getLog(getClass());
private Map<String, String> attributeToPropertyMap = new HashMap<String, String>();
public PoolingDataSourceBeanDefinitionParser() {
attributeToPropertyMap.put(URL_ATTRIBUTE, "url");
attributeToPropertyMap.put(USERNAME_ATTRIBUTE, "user");
attributeToPropertyMap.put(PASSWORD_ATTRIBUTE, "password");
attributeToPropertyMap.put(CONNECTION_CACHING_ENABLED_ATTRIBUTE, "connectionCachingEnabled");
attributeToPropertyMap.put(FAST_CONNECTION_FAILOVER_ENABLED_ATTRIBUTE, "fastConnectionFailoverEnabled");
attributeToPropertyMap.put(ONS_CONFIGURATION_ATTRIBUTE, "ONSConfiguration");
}
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
//ToDo look for username-connection-proxy
boolean useWrapper = false;
String connectionContextProviderRef = null;
Element usernameConnectionProxyElement = DomUtils.getChildElementByTagName(element, USERNAME_CONNECTION_PROXY);
if (usernameConnectionProxyElement != null) {
if (logger.isDebugEnabled()) {
logger.debug("Using username-connection-proxy");
}
if (usernameConnectionProxyElement.hasAttribute(CONNECTION_CONTEXT_PROVIDER)) {
if (logger.isDebugEnabled()) {
logger.debug(CONNECTION_CONTEXT_PROVIDER + ": " + usernameConnectionProxyElement.getAttribute(CONNECTION_CONTEXT_PROVIDER));
}
connectionContextProviderRef = usernameConnectionProxyElement.getAttribute(CONNECTION_CONTEXT_PROVIDER);
}
useWrapper = true;
//builder.addPropertyValue("connectionProperties", connProperties);
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
builder.getRawBeanDefinition().setBeanClassName(getBeanClassName(element));
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
builder.getRawBeanDefinition().setDestroyMethodName("close");
if (parserContext.isNested()) {
// Inner bean definition must receive same scope as containing bean.
builder.setScope(parserContext.getContainingBeanDefinition().getScope());
}
if (parserContext.isDefaultLazyInit()) {
// Default-lazy-init applies to custom bean definitions as well.
builder.setLazyInit(true);
}
doParse(element, parserContext, builder);
if (useWrapper) {
BeanDefinitionBuilder wrapper = BeanDefinitionBuilder.genericBeanDefinition();
wrapper.getRawBeanDefinition().setBeanClassName("org.springframework.data.jdbc.support.oracle.ProxyDataSource");
wrapper.addConstructorArgValue(builder.getBeanDefinition());
if (connectionContextProviderRef == null) {
wrapper.addConstructorArgValue(null);
}
else {
wrapper.addConstructorArgReference(connectionContextProviderRef);
}
return wrapper.getBeanDefinition();
}
else {
return builder.getBeanDefinition();
}
}
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
ResourceLoader rl = parserContext.getReaderContext().getResourceLoader();
//attributes
String propertyFileLocation = element.getAttribute(PROPERTIES_LOCATION_ATTRIBUTE);
String connectionPropertyFileLocation = element.getAttribute(PROPERTIES_LOCATION_ATTRIBUTE);
String connectionPropertyPrefix = element.getAttribute(CONNECTION_PROPERTIES_PREFIX_ATTRIBUTE);
String cachingPropertyPrefix = element.getAttribute(CONNECTON_CACHE_PROPERTIS_PREFIX_ATTRIBUTE);
String url = element.getAttribute(URL_ATTRIBUTE);
String username = element.getAttribute(USERNAME_ATTRIBUTE);
String password = element.getAttribute(PASSWORD_ATTRIBUTE);
String onsConfiguration = element.getAttribute(ONS_CONFIGURATION_ATTRIBUTE);
String fastConnectionFailoverEnabled = element.getAttribute(FAST_CONNECTION_FAILOVER_ENABLED_ATTRIBUTE);
String connectionCachingEnabled = element.getAttribute(CONNECTION_CACHING_ENABLED_ATTRIBUTE);
boolean propertyFileProvided = false;
Map<String, Object> providedProperties = new HashMap<String, Object>();
// defaults
if (!StringUtils.hasText(propertyFileLocation) && !StringUtils.hasText(connectionPropertyFileLocation)) {
propertyFileLocation = DEFAULT_PROPERTY_FILE_LOCATION;
}
if (!StringUtils.hasText(connectionPropertyPrefix)) {
connectionPropertyPrefix = DEFAULT_PROPERTY_PREFIX;
}
// look for property files
if (StringUtils.hasText(propertyFileLocation)) {
logger.debug("Using properties location: " + propertyFileLocation);
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(propertyFileLocation);
Resource r = rl.getResource(resolvedLocation);
logger.debug("Loading properties from resource: " + r);
PropertiesFactoryBean factoryBean = new PropertiesFactoryBean();
factoryBean.setLocation(r);
try {
factoryBean.afterPropertiesSet();
Properties resource = factoryBean.getObject();
for (Map.Entry<Object, Object> entry : resource.entrySet()) {
providedProperties.put((String)entry.getKey(), entry.getValue());
}
propertyFileProvided = true;
} catch (FileNotFoundException e) {
propertyFileProvided = false;
if (propertyFileLocation.equals(DEFAULT_PROPERTY_FILE_LOCATION)) {
logger.debug("Unable to find " + propertyFileLocation);
}
else {
parserContext.getReaderContext().error("pooling-datasource defined with attribute '" +
PROPERTIES_LOCATION_ATTRIBUTE + "' but the property file was not found at location \"" +
propertyFileLocation + "\"", element);
}
} catch (IOException e) {
logger.warn("Error loading " + propertyFileLocation + ": " + e.getMessage());
}
}
else {
propertyFileProvided = false;
}
if (logger.isDebugEnabled()) {
logger.debug("Using provided properties: " + providedProperties);
}
if (connectionPropertyPrefix == null) {
connectionPropertyPrefix = "";
}
if (connectionPropertyPrefix.length() > 0 && !connectionPropertyPrefix.endsWith(".")) {
connectionPropertyPrefix = connectionPropertyPrefix + ".";
}
logger.debug("Using connection properties prefix: " + connectionPropertyPrefix);
if (cachingPropertyPrefix == null) {
cachingPropertyPrefix = "";
}
if (cachingPropertyPrefix.length() > 0 && !cachingPropertyPrefix.endsWith(".")) {
cachingPropertyPrefix = cachingPropertyPrefix + ".";
}
logger.debug("Using caching properties prefix: " + cachingPropertyPrefix);
if (!(StringUtils.hasText(connectionCachingEnabled) ||
providedProperties.containsKey(attributeToPropertyMap.get(CONNECTION_CACHING_ENABLED_ATTRIBUTE)))) {
connectionCachingEnabled = DEFAULT_CONNECTION_CACHING_ENABLED;
}
setRequiredAttribute(builder, parserContext, element, providedProperties, connectionPropertyPrefix, propertyFileProvided, url, URL_ATTRIBUTE, "URL");
setOptionalAttribute(builder, providedProperties, connectionPropertyPrefix, username, USERNAME_ATTRIBUTE);
setOptionalAttribute(builder, providedProperties, connectionPropertyPrefix, password, PASSWORD_ATTRIBUTE);
setOptionalAttribute(builder, providedProperties, connectionPropertyPrefix, connectionCachingEnabled, CONNECTION_CACHING_ENABLED_ATTRIBUTE);
setOptionalAttribute(builder, providedProperties, connectionPropertyPrefix, fastConnectionFailoverEnabled, FAST_CONNECTION_FAILOVER_ENABLED_ATTRIBUTE);
setOptionalAttribute(builder, providedProperties, connectionPropertyPrefix, onsConfiguration, ONS_CONFIGURATION_ATTRIBUTE);
Properties providedConnectionProperties = new Properties();
Properties providedCachingProperties = new Properties();
for (String key : providedProperties.keySet()) {
if (StringUtils.hasText(connectionPropertyPrefix) && key.startsWith(connectionPropertyPrefix)) {
String newKey = key.substring(connectionPropertyPrefix.length());
providedConnectionProperties.put(newKey, providedProperties.get(key));
}
else {
if (StringUtils.hasText(cachingPropertyPrefix) && key.startsWith(cachingPropertyPrefix)) {
String newKey = key.substring(cachingPropertyPrefix.length());
providedCachingProperties.put(newKey, providedProperties.get(key));
}
else {
providedConnectionProperties.put(key, providedProperties.get(key));
}
}
}
// look for connectionProperties
Object connProperties = DomUtils.getChildElementValueByTagName(element, CONNECTION_PROPERTIES_CHILD_ELEMENT);
if (connProperties != null) {
if (logger.isDebugEnabled()) {
logger.debug("Using connection-properties");
}
builder.addPropertyValue("connectionProperties", connProperties);
}
else {
if (providedConnectionProperties.size() > 0) {
if (logger.isDebugEnabled()) {
logger.debug("Using provided connection properties: " + providedConnectionProperties);
}
builder.addPropertyValue("connectionProperties", providedConnectionProperties);
}
}
// look for connectionCacheProperties
Object cacheProperties = DomUtils.getChildElementValueByTagName(element, CONNECTION_CACHE_PROPERTIES_CHILD_ELEMENT);
if (cacheProperties != null) {
if (logger.isDebugEnabled()) {
logger.debug("Using connection-cache-properties: [" + cacheProperties + "]");
}
builder.addPropertyValue("connectionCacheProperties", cacheProperties);
}
else {
if (providedCachingProperties.size() > 0) {
if (logger.isDebugEnabled()) {
logger.debug("Using provided caching properties: " + providedCachingProperties);
}
builder.addPropertyValue("connectionCacheProperties", providedCachingProperties);
}
}
builder.setRole(BeanDefinition.ROLE_SUPPORT);
}
protected String getBeanClassName(Element element) {
return "oracle.jdbc.pool.OracleDataSource";
}
protected boolean shouldGenerateId() {
return false;
}
private void setRequiredAttribute(BeanDefinitionBuilder builder,
ParserContext parserContext,
Element element,
Map<String, Object> providedProperties,
String propertyPrefix,
boolean propertyFileProvided,
String attributeValue,
String attributeName,
String orclPropertyName) {
String propertyToRemove = null;
String propertyKey = propertyPrefix != null ? propertyPrefix + attributeName : attributeName;
String orclKey = propertyPrefix != null ? propertyPrefix + orclPropertyName : orclPropertyName;
if (StringUtils.hasText(attributeValue)) {
if (logger.isDebugEnabled()) {
logger.debug("Registering required attribute " + orclPropertyName + " with attribute value " + attributeValue);
}
builder.addPropertyValue(orclPropertyName, attributeValue);
}
else if (providedProperties.containsKey(propertyKey) || providedProperties.containsKey(orclKey)) {
Object value;
if (providedProperties.containsKey(propertyKey)) {
value = providedProperties.get(propertyKey);
propertyToRemove = propertyKey;
}
else {
value = providedProperties.get(orclKey);
propertyToRemove = orclKey;
}
if (logger.isDebugEnabled()) {
logger.debug("Registering required attribute " + orclPropertyName + " with property value " + value);
}
builder.addPropertyValue(orclPropertyName, value);
}
else {
if (propertyFileProvided) {
parserContext.getReaderContext().error(
"pooling-datasource defined without the required '" + attributeName +
"' attribute and the property file does not contain a \"" +
attributeToPropertyMap.get(attributeName) + "\" entry", element);
}
else {
parserContext.getReaderContext().error("pooling-datasource defined without the required '" + attributeName +
"' attribute and a property file was not found at location \"" + DEFAULT_PROPERTY_FILE_LOCATION + "\"",
element);
}
}
if (propertyToRemove != null) {
removeProvidedProperty(providedProperties, propertyToRemove);
}
}
private void setOptionalAttribute(BeanDefinitionBuilder builder,
Map<String, Object> providedProperties,
String propertyPrefix,
String attributeValue,
String attributeName) {
String propertyKey;
if ("username".equals(attributeName)) {
String userKey = (propertyPrefix != null ?
propertyPrefix + "user" :
"user");
if (providedProperties.containsKey(userKey)) {
propertyKey = userKey;
}
else {
propertyKey = (propertyPrefix != null ?
propertyPrefix + attributeName :
attributeName);
}
}
else {
propertyKey = (propertyPrefix != null ?
propertyPrefix + attributeToPropertyMap.get(attributeName) :
attributeToPropertyMap.get(attributeName));
}
if (StringUtils.hasText(attributeValue)) {
if (logger.isDebugEnabled()) {
if ("password".equals(attributeName)) {
logger.debug("Registering optional attribute " + attributeToPropertyMap.get(attributeName) +
" with attribute value ******");
}
else {
logger.debug("Registering optional attribute " + attributeToPropertyMap.get(attributeName) +
" with attribute value " + attributeValue);
}
}
builder.addPropertyValue(attributeToPropertyMap.get(attributeName), attributeValue);
}
else if (providedProperties.containsKey(propertyKey)) {
if (logger.isDebugEnabled()) {
logger.debug("Registering optional attribute " + attributeToPropertyMap.get(attributeName) +
" with property value " +
("password".equals(attributeName) ? "******" : providedProperties.get(propertyKey)));
}
builder.addPropertyValue(attributeToPropertyMap.get(attributeName), providedProperties.get(propertyKey));
}
removeProvidedProperty(providedProperties, propertyKey);
}
private void removeProvidedProperty(Map<String, Object> providedProperties, String attributeName) {
if (providedProperties.containsKey(attributeName)) {
providedProperties.remove(attributeName);
}
}
protected void registerBeanDefinition(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry beanDefinitionRegistry) {
//register other beans here if necessary
super.registerBeanDefinition(beanDefinitionHolder, beanDefinitionRegistry);
}
}