/*
* Copyright 2013-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.cloud.aws.jdbc.config.xml;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.cloud.aws.context.config.xml.GlobalBeanDefinitionUtils;
import org.springframework.cloud.aws.core.config.AmazonWebserviceClientConfigurationUtils;
import org.springframework.cloud.aws.jdbc.datasource.TomcatJdbcDataSourceFactory;
import org.springframework.cloud.aws.jdbc.rds.AmazonRdsDataSourceFactoryBean;
import org.springframework.cloud.aws.jdbc.rds.AmazonRdsReadReplicaAwareDataSourceFactoryBean;
import org.springframework.core.Conventions;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import static org.springframework.cloud.aws.core.config.xml.XmlWebserviceConfigurationUtils.getCustomClientOrDefaultClientBeanName;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} parser implementation for the datasource
* element. Parses the element and constructs a fully configured {@link AmazonRdsDataSourceFactoryBean} bean
* definition. Also creates a bean definition for the {@link com.amazonaws.services.rds.AmazonRDSClient} if there is
* not already an existing one this application context.
*
* @author Agim Emruli
* @since 1.0
*/
class AmazonRdsDataSourceBeanDefinitionParser extends AbstractBeanDefinitionParser {
static final String DB_INSTANCE_IDENTIFIER = "db-instance-identifier";
private static final String AMAZON_RDS_CLIENT_CLASS_NAME = "com.amazonaws.services.rds.AmazonRDSClient";
private static final String IDENTITY_MANAGEMENT_CLIENT_CLASS_NAME = "com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient";
private static final String USER_TAG_FACTORY_BEAN_CLASS_NAME = "org.springframework.cloud.aws.jdbc.rds.AmazonRdsDataSourceUserTagsFactoryBean";
private static final String USERNAME = "username";
private static final String PASSWORD = "password";
private static final String DATABASE_NAME = "database-name";
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder datasourceBuilder = getBeanDefinitionBuilderForDataSource(element);
//Constructor (mandatory) args
String amazonRdsClientBeanName = getCustomClientOrDefaultClientBeanName(element, parserContext, "amazon-rds", AMAZON_RDS_CLIENT_CLASS_NAME);
datasourceBuilder.addConstructorArgReference(amazonRdsClientBeanName);
datasourceBuilder.addConstructorArgValue(element.getAttribute(DB_INSTANCE_IDENTIFIER));
datasourceBuilder.addConstructorArgValue(element.getAttribute(PASSWORD));
//optional args
if (StringUtils.hasText(element.getAttribute(USERNAME))) {
datasourceBuilder.addPropertyValue(USERNAME, element.getAttribute(USERNAME));
}
if (StringUtils.hasText(element.getAttribute(DATABASE_NAME))) {
datasourceBuilder.addPropertyValue(Conventions.attributeNameToPropertyName(DATABASE_NAME), element.getAttribute(DATABASE_NAME));
}
datasourceBuilder.addPropertyValue("dataSourceFactory", createDataSourceFactoryBeanDefinition(element));
//Register registry to enable cloud formation support
String resourceResolverBeanName = GlobalBeanDefinitionUtils.retrieveResourceIdResolverBeanName(parserContext.getRegistry());
datasourceBuilder.addPropertyReference("resourceIdResolver", resourceResolverBeanName);
registerUserTagsMapIfNecessary(element, parserContext, amazonRdsClientBeanName);
return datasourceBuilder.getBeanDefinition();
}
private BeanDefinitionBuilder getBeanDefinitionBuilderForDataSource(Element element) {
BeanDefinitionBuilder datasourceBuilder;
if (Boolean.TRUE.toString().equalsIgnoreCase(element.getAttribute("read-replica-support"))) {
datasourceBuilder = BeanDefinitionBuilder.rootBeanDefinition(AmazonRdsReadReplicaAwareDataSourceFactoryBean.class);
} else {
datasourceBuilder = BeanDefinitionBuilder.rootBeanDefinition(AmazonRdsDataSourceFactoryBean.class);
}
return datasourceBuilder;
}
/**
* Creates a {@link org.springframework.cloud.aws.jdbc.datasource.DataSourceFactory} implementation. Uses the
* TomcatJdbcDataSourceFactory implementation and passes all pool attributes from the xml directly to the class
* (through setting the bean properties).
*
* @param element
* - The datasource element which may contain a pool-attributes element
* @return - fully configured bean definition for the DataSourceFactory
*/
private static AbstractBeanDefinition createDataSourceFactoryBeanDefinition(Element element) {
BeanDefinitionBuilder datasourceFactoryBuilder = BeanDefinitionBuilder.rootBeanDefinition(TomcatJdbcDataSourceFactory.class);
Element poolAttributes = DomUtils.getChildElementByTagName(element, "pool-attributes");
if (poolAttributes != null) {
NamedNodeMap attributes = poolAttributes.getAttributes();
for (int i = 0, x = attributes.getLength(); i < x; i++) {
Node item = attributes.item(i);
datasourceFactoryBuilder.addPropertyValue(item.getNodeName(), item.getNodeValue());
}
}
return datasourceFactoryBuilder.getBeanDefinition();
}
private static void registerUserTagsMapIfNecessary(Element element, ParserContext parserContext, String rdsClientBeanName) {
if (!StringUtils.hasText(element.getAttribute("user-tags-map"))) {
return;
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(USER_TAG_FACTORY_BEAN_CLASS_NAME);
builder.addConstructorArgReference(rdsClientBeanName);
builder.addConstructorArgValue(element.getAttribute(DB_INSTANCE_IDENTIFIER));
builder.addConstructorArgReference(getCustomClientOrDefaultClientBeanName(element, parserContext, "amazon-identity-management",
IDENTITY_MANAGEMENT_CLIENT_CLASS_NAME));
// Use custom region-provider of data source
if (StringUtils.hasText(element.getAttribute("region"))) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition("com.amazonaws.regions.Region");
beanDefinitionBuilder.setFactoryMethod("getRegion");
beanDefinitionBuilder.addConstructorArgValue(element.getAttribute("region"));
builder.addPropertyValue("region", beanDefinitionBuilder.getBeanDefinition());
} else {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingFactoryBean.class);
if (StringUtils.hasText(element.getAttribute("region-provider"))) {
beanDefinitionBuilder.addPropertyValue("targetObject", new RuntimeBeanReference(element.getAttribute("region-provider")));
} else {
beanDefinitionBuilder.addPropertyValue("targetObject", new RuntimeBeanReference(AmazonWebserviceClientConfigurationUtils.
getRegionProviderBeanName(parserContext.getRegistry())));
}
beanDefinitionBuilder.addPropertyValue("targetMethod", "getRegion");
builder.addPropertyValue("region", beanDefinitionBuilder.getBeanDefinition());
}
String resourceResolverBeanName = GlobalBeanDefinitionUtils.retrieveResourceIdResolverBeanName(parserContext.getRegistry());
builder.addPropertyReference("resourceIdResolver", resourceResolverBeanName);
parserContext.getRegistry().registerBeanDefinition(element.getAttribute("user-tags-map"), builder.getBeanDefinition());
}
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException {
return element.getAttribute(DB_INSTANCE_IDENTIFIER);
}
}