package org.jboss.windup.rules.apps.javaee.rules; import static org.joox.JOOX.$; import static org.joox.JOOX.attr; import java.util.List; import java.util.Set; import java.util.logging.Level; 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.graph.model.ProjectModel; import org.jboss.windup.graph.service.GraphService; import org.jboss.windup.config.projecttraversal.ProjectTraversalCache; import org.jboss.windup.reporting.model.TechnologyTagLevel; import org.jboss.windup.reporting.service.TechnologyTagService; import org.jboss.windup.rules.apps.java.model.JavaClassModel; import org.jboss.windup.rules.apps.java.service.JavaClassService; import org.jboss.windup.rules.apps.javaee.model.JNDIResourceModel; 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.service.JNDIResourceService; import org.jboss.windup.rules.apps.javaee.service.SpringBeanService; import org.jboss.windup.rules.apps.javaee.service.SpringConfigurationFileService; import org.jboss.windup.rules.apps.xml.model.XmlFileModel; import org.jboss.windup.rules.apps.xml.service.XmlFileService; import org.ocpsoft.rewrite.config.ConditionBuilder; import org.ocpsoft.rewrite.context.EvaluationContext; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Discovers spring configuration XML files, and places the metadata into the Graph. * * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a> * @author <a href="mailto:bradsdavis@gmail.com">Brad Davis</a> */ @RuleMetadata(phase = InitialAnalysisPhase.class, perform = "Discover Spring Config Files") public class DiscoverSpringConfigurationFilesRuleProvider extends IteratingRuleProvider<XmlFileModel> { private static final Logger LOG = Logger.getLogger(DiscoverSpringConfigurationFilesRuleProvider.class .getSimpleName()); private static final String TECH_TAG = "Spring XML"; private static final TechnologyTagLevel TECH_TAG_LEVEL = TechnologyTagLevel.IMPORTANT; @Override public ConditionBuilder when() { return Query.fromType(XmlFileModel.class).withProperty(XmlFileModel.ROOT_TAG_NAME, "beans"); } @Override public void perform(GraphRewrite event, EvaluationContext context, XmlFileModel payload) { JavaClassService javaClassService = new JavaClassService(event.getGraphContext()); XmlFileService xmlFileService = new XmlFileService(event.getGraphContext()); TechnologyTagService technologyTagService = new TechnologyTagService(event.getGraphContext()); SpringConfigurationFileService springConfigurationFileService = new SpringConfigurationFileService( event.getGraphContext()); SpringBeanService springBeanService = new SpringBeanService(event.getGraphContext()); JNDIResourceService jndiResourceService = new JNDIResourceService(event.getGraphContext()); Document doc = xmlFileService.loadDocumentQuiet(event, context, payload); if (doc == null) { // skip if the xml failed to load return; } List<Element> beansElements = $(doc).namespace("s", "http://www.springframework.org/schema/beans") .xpath("/s:beans").get(); if (beansElements.isEmpty()) { LOG.log(Level.WARNING, "Found [beans] XML without namespace at: " + payload.getFilePath() + "."); return; } technologyTagService.addTagToFileModel(payload, TECH_TAG, TECH_TAG_LEVEL); Element element = beansElements.get(0); SpringConfigurationFileModel springConfigurationModel = springConfigurationFileService .addTypeToModel(payload); Set<ProjectModel> applications = ProjectTraversalCache.getApplicationsForProject(event.getGraphContext(), payload.getProjectModel()); // create bean models List<Element> beans = $(element).children("bean").get(); for (Element bean : beans) { String clz = $(bean).attr("class"); String id = $(bean).attr("id"); String name = $(bean).attr("name"); if (StringUtils.isBlank(id) && StringUtils.isNotBlank(name)) { id = name; } if (StringUtils.isBlank(clz)) { LOG.log(Level.WARNING, "Spring Bean did not include class:" + $(bean).toString()); continue; } SpringBeanModel springBeanRef = springBeanService.create(); springBeanRef.setApplications(applications); if (StringUtils.isNotBlank(id)) { springBeanRef.setSpringBeanName(id); } JavaClassModel classReference = javaClassService.getOrCreatePhantom(clz); springBeanRef.setJavaClass(classReference); springConfigurationModel.addSpringBeanReference(springBeanRef); // find out if the class is a JndiObjectFactoryBean, extract the JNDI & type the JNDI resource, when expectedType property is provided. /* * <bean id="beanDataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" * value="jdbc/ExampleSpringBeanDataSource"/> <property name="expectedType" value="javax.sql.DataSource"/> </bean> */ if (StringUtils.isNotBlank(clz) && StringUtils.equals("org.springframework.jndi.JndiObjectFactoryBean", clz)) { String expectedType = $(bean).children("property").filter(attr("name", "expectedType")).first().attr("value"); String jndiName = $(bean).children("property").filter(attr("name", "jndiName")).first().attr("value"); LOG.info("Found JNDI in Bean Spring: " + jndiName); if (StringUtils.isNotBlank(jndiName)) { JNDIResourceModel jndiResource = jndiResourceService.createUnique(applications, jndiName); if (StringUtils.isNotBlank(expectedType)) { LOG.info(" -- Type: " + expectedType); jndiResourceService.associateTypeJndiResource(jndiResource, expectedType); } JNDIReferenceModel reference = GraphService.addTypeToModel(event.getGraphContext(), springBeanRef, JNDIReferenceModel.class); reference.setJndiReference(jndiResource); } } } // extract map elements /* * <util:map id="exMap"> * <entry key="ex" value="val"/> * </util:map> */ for(Element map : $(element).children("map").get()) { String id = $(map).attr("id"); SpringBeanModel springBeanRef = springBeanService.create(); springBeanRef.setSpringBeanName(id); springBeanRef.setApplications(applications); springConfigurationModel.addSpringBeanReference(springBeanRef); JavaClassModel classReference = javaClassService.getOrCreatePhantom("java.util.Map"); springBeanRef.setJavaClass(classReference); } // extract map elements /* * <util:set id="exSet"> * <value>ex</value> * </util:set> */ for(Element map : $(element).children("set").get()) { String id = $(map).attr("id"); SpringBeanModel springBeanRef = springBeanService.create(); springBeanRef.setSpringBeanName(id); springBeanRef.setApplications(applications); springConfigurationModel.addSpringBeanReference(springBeanRef); JavaClassModel classReference = javaClassService.getOrCreatePhantom("java.util.Set"); springBeanRef.setJavaClass(classReference); } // extract JNDI references & type the JNDI resource, when expected-type is provided. /* * <jee:jndi-lookup id="jeeDataSource" jndi-name="jdbc/ExampleSpringJNDIDataSource" expected-type="javax.sql.DataSource" /> */ List<Element> jndis = $(element).children("jndi-lookup").get(); if (jndis.size() > 0) { for (Element jndi : jndis) { String id = $(jndi).attr("id"); String jndiName = $(jndi).attr("jndi-name"); String expectedType = $(jndi).attr("expected-type"); LOG.info("Found JNDI in JEE Spring: " + jndiName); SpringBeanModel springBeanRef = springBeanService.create(); springBeanRef.setSpringBeanName(id); springBeanRef.setApplications(applications); JNDIResourceModel jndiResource = null; if (StringUtils.isNotBlank(jndiName)) { jndiResource = jndiResourceService.createUnique(applications, jndiName); if (StringUtils.isNotBlank(expectedType)) { LOG.info(" -- Type: " + expectedType); jndiResourceService.associateTypeJndiResource(jndiResource, expectedType); } //this will make it so that we have an association from the SpringBean to the JNDI Resource, layered on top of the typical SpringBean. //this in turn will make this both a SpringBeanModel and a JNDIReferenceModel in the graph, so we can specifically look up Spring Bean JNDI References JNDIReferenceModel reference = GraphService.addTypeToModel(event.getGraphContext(), springBeanRef, JNDIReferenceModel.class); reference.setJndiReference(jndiResource); } //jndi-lookup is shorthand for this class. register it to allow location of the bean by name to configuration file final String clz = "org.springframework.jndi.JndiObjectFactoryBean"; JavaClassModel classReference = javaClassService.getOrCreatePhantom(clz); springBeanRef.setJavaClass(classReference); springConfigurationModel.addSpringBeanReference(springBeanRef); } } } }