package org.jboss.windup.rules.apps.javaee.rules.spring;
import static org.joox.JOOX.$;
import static org.joox.JOOX.attr;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.metadata.RuleMetadata;
import org.jboss.windup.config.phase.InitialAnalysisPhase;
import org.jboss.windup.config.query.Query;
import org.jboss.windup.config.ruleprovider.IteratingRuleProvider;
import org.jboss.windup.rules.apps.javaee.model.DataSourceModel;
import org.jboss.windup.rules.apps.javaee.model.SpringBeanModel;
import org.jboss.windup.rules.apps.javaee.model.SpringConfigurationFileModel;
import org.jboss.windup.rules.apps.javaee.model.association.JNDIReferenceModel;
import org.jboss.windup.rules.apps.javaee.rules.DiscoverSpringConfigurationFilesRuleProvider;
import org.jboss.windup.rules.apps.javaee.service.DataSourceService;
import org.jboss.windup.rules.apps.javaee.service.SpringBeanService;
import org.jboss.windup.rules.apps.javaee.util.HibernateDialectDataSourceTypeResolver;
import org.jboss.windup.rules.apps.javaee.util.SpringDataSourceTypeResolver;
import org.joox.Context;
import org.joox.FastFilter;
import org.ocpsoft.rewrite.config.ConditionBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Discovers server resource types from specific Spring bean types eg. Hibernate Dialect in LocalSessionFactoryBean -> Spring Bean JNDI resource ->
* Oracle Data Source
*
* @author <a href="mailto:bradsdavis@gmail.com">Brad Davis</a>
*/
@RuleMetadata(phase = InitialAnalysisPhase.class, after = DiscoverSpringConfigurationFilesRuleProvider.class, perform = "Resolve Spring JNDI to DataSource")
public class ResolveSpringHibernateJPADataSourceRuleProvider extends IteratingRuleProvider<SpringBeanModel>
{
private static final Logger LOG = Logger.getLogger(ResolveSpringHibernateJPADataSourceRuleProvider.class.getSimpleName());
@Override
public ConditionBuilder when()
{
return Query.fromType(SpringBeanModel.class);
}
@Override
public void perform(GraphRewrite event, EvaluationContext context, SpringBeanModel payload)
{
// handles only xml based spring beans with certain java classes, such as LocalSessionFactoryBean
if (payload.getSpringConfiguration() == null || payload.getJavaClass() == null
|| !isLocalSessionFactoryBean(payload.getJavaClass().getQualifiedName()))
{
return;
}
SpringConfigurationFileModel springConfig = payload.getSpringConfiguration();
Document doc = springConfig.asDocument();
if (doc == null)
{
LOG.warning("Document corrupt. Skipping.");
return;
}
for (Element bean : $(doc).find("bean").filter(springid(payload.getSpringBeanName())))
{
String dsBeanNameRef = extractJndiRefBeanName(bean);
if (StringUtils.isBlank(dsBeanNameRef))
{
continue;
}
String hibernateDialect = null;
Map<String, String> hibernateProperties = extractProperties(doc, bean);
hibernateProperties.putAll(extractHibernateJpaVendorJpaProperties(doc, bean));
if (hibernateProperties.containsKey("hibernate.dialect"))
{
hibernateDialect = hibernateProperties.get("hibernate.dialect");
}
String springDbName = extractHibernateJpaVendorDatabase(doc, bean);
processHibernateSessionFactoryBean(event, dsBeanNameRef, hibernateDialect, springDbName);
return;
}
LOG.warning("Did not find bean: " + payload.getSpringBeanName() + " to process within: " + springConfig.getFileName());
}
private boolean isLocalSessionFactoryBean(String qualifiedName)
{
if (qualifiedName == null)
{
return false;
}
return (qualifiedName.equals("org.springframework.orm.hibernate3.LocalSessionFactoryBean")
|| qualifiedName.equals("org.springframework.orm.hibernate3.AbstractSessionFactoryBean")
|| qualifiedName.equals("org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean")
|| qualifiedName.equals("org.springframework.orm.hibernate4.LocalSessionFactoryBean")
|| qualifiedName.equals("org.springframework.orm.hibernate5.LocalSessionFactoryBean")
|| qualifiedName.equals("org.springframework.orm.jpa.LocalEntityManagerFactoryBean")
|| qualifiedName.equals("org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean")
|| qualifiedName.equals("org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"));
}
/*
* <bean id="jpaVendorAdapter2" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="dataSource" value="HSQL"/>
* </bean>
*/
private String extractHibernateJpaVendorDatabase(Document doc, Element bean)
{
for (Element jpaVendorAdapterProperty : $(bean).children("property").filter(attr("name", "jpaVendorAdapter")).get())
{
String propertyRef = $(jpaVendorAdapterProperty).attr("ref");
if (StringUtils.isNotBlank(propertyRef))
{
// look for the properties referenced by a local bean..
for (Element jpaVendorAdapter : findLocalBeanById(doc, propertyRef))
{
// check attribute on element
String propAttrValue = $(jpaVendorAdapter).attr("database");
if (StringUtils.isNotBlank(propAttrValue))
{
return propAttrValue;
}
// now look for the property "dataSource" off of that bean.
for (Element p : $(jpaVendorAdapter).children("property").filter(attr("name", "database")).get())
{
String value = $(p).attr("value");
if (StringUtils.isNotBlank(value))
{
return value;
}
}
}
}
}
return null;
}
private Map<String, String> extractHibernateJpaVendorJpaProperties(Document doc, Element bean)
{
Map<String, String> properties = new HashMap<>();
for (Element jpaVendorAdapterProperty : $(bean).children("property").filter(attr("name", "jpaVendorAdapter")).get())
{
String propertyRef = $(jpaVendorAdapterProperty).attr("ref");
if (StringUtils.isNotBlank(propertyRef))
{
// look for the properties referenced by a local bean..
for (Element jpaVendorAdapter : findLocalBeanById(doc, propertyRef))
{
properties = extractProperties(doc, jpaVendorAdapter);
// read the hibernate dialect
for (Element p : $(jpaVendorAdapter).children("property").filter(attr("name", "databasePlatform")).get())
{
String dialect = $(p).attr("value");
if (StringUtils.isNotBlank(dialect))
{
if (!properties.containsKey("hibernate.dialect"))
{
properties.put("hibernate.dialect", dialect);
}
}
}
}
}
}
return properties;
}
private Map<String, String> extractProperties(Document doc, Element bean)
{
Map<String, String> properties = new HashMap<>();
for (Element p : $(bean).children("property").filter(attr("name", "hibernateProperties", "jpaProperties", "jpaPropertyMap")).get())
{
// first, check to see if it uses a ref attribute...
String propertyRef = $(p).attr("ref");
if (StringUtils.isNotBlank(propertyRef))
{
// look for the properties referenced by a local bean..
for (Element ref : findLocalBeanById(doc, propertyRef))
{
properties.putAll(readProperties(ref));
}
}
else
{
properties.putAll(readProperties(p));
}
}
return properties;
}
private Iterable<Element> findLocalBeanById(Document doc, String id)
{
List<Element> elements = new LinkedList<>();
elements.addAll($(doc).children().filter(attr("id", id)).get());
if (elements.isEmpty())
{
elements.addAll($(doc).children().filter(attr("name", id)).get());
}
return elements;
}
private String extractJndiRefBeanName(Element bean)
{
for (Element dataSource : $(bean).children("property").filter(attr("name", "dataSource")).get())
{
// read ref...
String jndiRef = $(dataSource).attr("ref");
if (StringUtils.isBlank(jndiRef))
{
LOG.info("Looking at ref child of property tag...");
//look to see if the ref is a child of the property tag...
jndiRef = $(dataSource).child("ref").attr("bean");
}
if(StringUtils.isNotBlank(jndiRef)) {
return jndiRef;
}
}
return null;
}
/*
* Reads Spring maps, properties, and value pair xml
*/
private Map<String, String> readProperties(Element properties)
{
Map<String, String> values = new HashMap<>();
for (Element p : $(properties).find("prop"))
{
String key = $(p).attr("key");
String val = $(p).text();
values.put(key, val);
}
for (Element p : $(properties).find("entry"))
{
String key = $(p).attr("key");
String val = $(p).attr("value");
values.put(key, val);
}
for (Element p : $(properties).find("value"))
{
String propVal = StringUtils.trim($(p).text());
String key = StringUtils.substringBefore(propVal, "=");
String val = StringUtils.substringAfter(propVal, "=");
values.put(key, val);
}
return values;
}
/*
* Takes a JNDI reference and turns the JNDI reference into a database, typing the database based on either the hibernate dialect or the spring
* database name
*/
private void processHibernateSessionFactoryBean(GraphRewrite event, String dsBeanName, String hibernateDialect, String springDatabaseName)
{
LOG.info("DS Name: " + dsBeanName + ", " + hibernateDialect + ", " + springDatabaseName);
SpringBeanService springBeanService = new SpringBeanService(event.getGraphContext());
DataSourceService dataSourceService = new DataSourceService(event.getGraphContext());
for (SpringBeanModel model : springBeanService.findAllBySpringBeanName(dsBeanName))
{
if (model instanceof JNDIReferenceModel && ((JNDIReferenceModel) model).getJndiReference() != null)
{
// then this is likely a datasource; set JNDI to Datasource
JNDIReferenceModel ref = (JNDIReferenceModel) model;
DataSourceModel dataSource = dataSourceService.addTypeToModel(ref.getJndiReference());
if (StringUtils.isNotBlank(hibernateDialect))
{
LOG.info(" - Resolved Hibernate dialect: " + hibernateDialect);
String resolvedType = HibernateDialectDataSourceTypeResolver.resolveDataSourceTypeFromDialect(hibernateDialect);
if (StringUtils.isNotBlank(resolvedType))
{
dataSource.setDatabaseTypeName(resolvedType);
}
}
else if (StringUtils.isNotBlank(springDatabaseName))
{
LOG.info(" - Resolved Spring database type: " + springDatabaseName);
String resolvedType = SpringDataSourceTypeResolver.resolveDataSourceTypeFromDialect(springDatabaseName);
if (StringUtils.isNotBlank(resolvedType))
{
dataSource.setDatabaseTypeName(resolvedType);
}
}
}
else
{
LOG.warning("Not JNDI Reference.");
}
}
}
/*
* Filters Joox by the spring bean's name (name or id) leveraging name to support legacy spring
*/
public static FastFilter springid(final String id)
{
return new FastFilter()
{
@Override
public boolean filter(Context context)
{
String name = $(context).attr("name");
String idVal = $(context).attr("id");
LOG.info("Matching: " + id + " Against -- ID: " + idVal + " Name: " + name);
return (StringUtils.equals(id, idVal) || StringUtils.equals(id, name));
}
};
}
}