/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.tuscany.implementation.spring.xml; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import javax.xml.namespace.QName; import javax.xml.stream.XMLInputFactory; import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.apache.tuscany.implementation.spring.SpringImplementation; import org.apache.tuscany.sca.assembly.ComponentType; import org.apache.tuscany.sca.assembly.Reference; import org.apache.tuscany.sca.assembly.Service; import org.apache.tuscany.sca.contribution.service.ContributionReadException; import org.apache.tuscany.sca.interfacedef.InvalidInterfaceException; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.apache.tuscany.sca.assembly.AssemblyFactory; import org.apache.tuscany.sca.assembly.Multiplicity; import org.apache.tuscany.sca.interfacedef.java.JavaInterfaceFactory; import org.apache.tuscany.sca.interfacedef.java.introspect.JavaInterfaceIntrospector; import org.apache.tuscany.sca.interfacedef.java.JavaInterface; import org.apache.tuscany.sca.interfacedef.java.JavaInterfaceContract; import org.apache.tuscany.sca.policy.PolicyFactory; import org.apache.tuscany.sca.contribution.service.ContributionResolveException; /** * Introspects a Spring XML application-context configuration file to create * component type information. * * * @version $Rev: 512919 $ $Date: 2007-02-28 19:32:56 +0000 (Wed, 28 Feb 2007) $ */ public class SpringXMLComponentTypeLoader { private static final String SCA_NS = "http://www.springframework.org/schema/sca"; private static final String SPRING_NS = "http://www.springframework.org/schema/beans"; private static final QName SERVICE_ELEMENT = new QName(SCA_NS, "service"); private static final QName REFERENCE_ELEMENT = new QName(SCA_NS, "reference"); private static final QName BEANS_ELEMENT = new QName(SPRING_NS, "beans"); private static final QName BEAN_ELEMENT = new QName(SPRING_NS, "bean"); private static final QName PROPERTY_ELEMENT = new QName(SPRING_NS, "property"); private static final String APPLICATION_CONTEXT = "application-context.xml"; private AssemblyFactory assemblyFactory; private JavaInterfaceIntrospector interfaceIntrospector; private JavaInterfaceFactory javaFactory; private PolicyFactory policyFactory; private ClassLoader cl; private SpringBeanIntrospector beanIntrospector; public SpringXMLComponentTypeLoader( AssemblyFactory assemblyFactory, JavaInterfaceIntrospector interfaceIntrospector, JavaInterfaceFactory javaFactory, PolicyFactory policyFactory) { super(); this.assemblyFactory = assemblyFactory; this.interfaceIntrospector = interfaceIntrospector; this.javaFactory = javaFactory; this.policyFactory = policyFactory; beanIntrospector = new SpringBeanIntrospector( assemblyFactory, interfaceIntrospector, javaFactory, policyFactory ); } protected Class getImplementationClass() { return SpringImplementation.class; } /** * Base method which loads the component type from the application-context attached to the * Spring implementation * */ public void load( SpringImplementation implementation ) throws ContributionReadException { System.out.println("Spring TypeLoader - load method start"); ComponentType componentType = implementation.getComponentType(); /* Check that there is a component type object already set */ if ( componentType == null) { throw new ContributionReadException("SpringXMLLoader load: implementation has no ComponentType object"); } if ( componentType.isUnresolved() ) { /* Fetch the location of the application-context file from the implementation */ loadFromXML( implementation ); if( !componentType.isUnresolved() ) implementation.setUnresolved( false ); } // end if System.out.println("Spring TypeLoader - load method complete"); } // end method load /** * Method which fills out the component type for a Spring implementation by reading the * Spring application-context.xml file * @param componentType - the component type to complete * @param location - a string containing the relative location of the application-context.xml * @return a Spring Resource for the application-context * file */ private void loadFromXML( SpringImplementation implementation ) throws ContributionReadException { XMLStreamReader reader; List beans = new ArrayList(); List services = new ArrayList(); List references = new ArrayList(); SpringBeanElement bean = null; Resource resource; String location = implementation.getSpringLocation(); try { // FIXME - is the ContextClassLoader the right place to start the search? cl = Thread.currentThread().getContextClassLoader(); resource = getApplicationContextResource( location, cl ); implementation.setResource( resource ); // FIXME - need a better way to handle the XMLInputFactory than allocating a new one every time XMLInputFactory xmlFactory = XMLInputFactory.newInstance(); reader = xmlFactory.createXMLStreamReader(resource.getInputStream()); // System.out.println("Spring TypeLoader - starting to read context file"); boolean completed = false; while ( !completed ) { switch (reader.next()) { case START_ELEMENT: QName qname = reader.getName(); //System.out.println("Spring TypeLoader - found element with name: " + qname.toString()); if (SERVICE_ELEMENT.equals(qname)) { SpringSCAServiceElement service = new SpringSCAServiceElement( reader.getAttributeValue(SCA_NS, "name"), reader.getAttributeValue(SCA_NS, "type"), reader.getAttributeValue(SCA_NS, "target") ); services.add( service ); } else if (REFERENCE_ELEMENT.equals(qname)) { SpringSCAReferenceElement reference = new SpringSCAReferenceElement( reader.getAttributeValue(SCA_NS, "name"), reader.getAttributeValue(SCA_NS, "type") ); references.add( reference ); } else if (BEAN_ELEMENT.equals(qname)) { // TODO FIX THIS !! int count=reader.getAttributeCount(); bean = new SpringBeanElement( reader.getAttributeValue( null, "id"), reader.getAttributeValue( null, "class") ); beans.add( bean ); } else if (PROPERTY_ELEMENT.equals(qname)) { SpringPropertyElement property = new SpringPropertyElement( reader.getAttributeValue(SPRING_NS, "name"), reader.getAttributeValue(SPRING_NS, "ref")); bean.addProperty( property ); } // end if break; case END_ELEMENT: if (BEANS_ELEMENT.equals(reader.getName())) { //System.out.println("Spring TypeLoader - finished read of context file"); completed = true; break; } // end if } // end switch } // end while } catch (IOException e) { throw new ContributionReadException(e); } catch (XMLStreamException e) { throw new ContributionReadException(e); } /* At this point, the complete application-context.xml file has been read and its contents */ /* stored in the lists of beans, services, references. These are now used to generate */ /* the implied componentType for the application context */ generateComponentType( implementation, beans, services, references ); return; } // end method loadFromXML /** * Generates the Spring implementation component type from the configuration contained in the * lists of beans, services and references derived from the application context */ private void generateComponentType( SpringImplementation implementation, List beans, List services, List references ) throws ContributionReadException { /* * Each service becomes a service in the component type * Each reference becomes a reference in the component type * Each bean becomes a service, unless it is the target of a service element or unless it * is the target of a bean reference property * Each bean property which is a reference not pointing at another bean in the * application context becomes a reference unless it is pointing at one of the references */ ComponentType componentType = implementation.getComponentType(); try { // Deal with the services first.... Iterator its = services.iterator(); while( its.hasNext() ) { SpringSCAServiceElement serviceElement = its.next(); Class interfaze = cl.loadClass( serviceElement.getType() ); Service theService = createService( interfaze, serviceElement.getName() ); componentType.getServices().add( theService ); // Add this service to the Service / Bean map String beanName = serviceElement.getTarget(); for( SpringBeanElement beanElement : beans ) { if( beanName == beanElement.getId() ) { implementation.setBeanForService( theService, beanElement ); } } // end for } // end while // Next handle the references Iterator itr = references.iterator(); while( itr.hasNext() ) { SpringSCAReferenceElement referenceElement = itr.next(); Class interfaze = cl.loadClass( referenceElement.getType() ); Reference theReference = createReference( interfaze, referenceElement.getName() ); componentType.getReferences().add( theReference ); } // end while // Finally deal with the beans Iterator itb; // If there are no explicit service elements, then expose all the beans if( services.isEmpty() ) { itb = beans.iterator(); // Loop through all the beans found while( itb.hasNext() ) { SpringBeanElement beanElement = itb.next(); // Load the Spring bean class Class beanClass = cl.loadClass( beanElement.getClassName() ); // Introspect the bean ComponentType beanComponentType = assemblyFactory.createComponentType(); beanIntrospector.introspectBean( beanClass, beanComponentType ); // Get the service interface defined by this Spring Bean and add to // the component type of the Spring Assembly List beanServices = beanComponentType.getServices(); componentType.getServices().addAll( beanServices ); // Add these services to the Service / Bean map for( Service beanService : beanServices ) { implementation.setBeanForService( beanService, beanElement ); } } // end while } // end if // Now check to see if there are any more references itb = beans.iterator(); while( itb.hasNext() ) { SpringBeanElement beanElement = itb.next(); if( !beanElement.getProperties().isEmpty() ) { Iterator itp = beanElement.getProperties().iterator(); while( itp.hasNext() ) { SpringPropertyElement propertyElement = itp.next(); if( propertyRefUnresolved( propertyElement.getRef(), beans, references ) ) { // This means an unresolved reference from the spring bean... // TODO } // end if } // end while } // end if //Reference theReference = createReference( interfaze, referenceElement.getName() ); //componentType.getReferences().add( theReference ); } // end while } catch ( ClassNotFoundException e ) { // Means that either an interface class or a bean was not found throw new ContributionReadException( e ); } catch ( InvalidInterfaceException e ) { throw new ContributionReadException( e ); } catch ( ContributionResolveException e ) { } // end try // If we get here, the Spring assembly component type is resolved componentType.setUnresolved( false ); implementation.setComponentType( componentType ); return; } // end method generateComponentType /* * Determines whether a reference attribute of a Spring property element is resolved either * by a bean in the application context or by an SCA reference element * @param ref - a String containing the name of the reference - may be null * @param beans - a List of SpringBean elements * @param references - a List of SCA reference elements * @return true if the property is not resolved, false if it is resolved */ private boolean propertyRefUnresolved( String ref, List beans, List references ) { boolean unresolved = true; if( ref != null ) { // Scan over the beans looking for a match Iterator itb = beans.iterator(); while( itb.hasNext() ) { SpringBeanElement beanElement = itb.next(); // Does the bean name match the ref? if( ref.equals(beanElement.getId()) ) { unresolved = false; break; } // end if } // end while // Scan over the references looking for a match if( unresolved ) { Iterator itr = references.iterator(); while( itr.hasNext() ) { SpringSCAReferenceElement referenceElement = itr.next(); if( ref.equals(referenceElement.getName()) ) { unresolved = false; break; } // end if } // end while } // end if } // end if return unresolved; } // end method propertyRefUnresolved /** * Gets hold of the application-context.xml file as a Spring resource * @param locationAttr - the location attribute from the element * @param cl - the classloader for the Spring implementation */ protected Resource getApplicationContextResource( String locationAttr, ClassLoader cl ) throws ContributionReadException { File manifestFile = null; File appXmlFile; File locationFile = new File(locationAttr); if (!locationFile.exists()) { // FIXME hack URL url = cl.getResource(locationAttr); if (url != null) { return new UrlResource(url); } throw new ContributionReadException("SpringXMLLoader getApplicationContextResource: " + "unable to find resource file " + locationFile.toString()); } if (locationFile.isDirectory()) { try { manifestFile = new File(locationFile, "META-INF/MANIFEST.MF"); if (manifestFile.exists()) { Manifest mf = new Manifest(new FileInputStream(manifestFile)); Attributes mainAttrs = mf.getMainAttributes(); String appCtxPath = mainAttrs.getValue("Spring-Context"); if (appCtxPath != null) { appXmlFile = new File(locationFile, appCtxPath); if (appXmlFile.exists()) { return new UrlResource(appXmlFile.toURL()); } } } // no manifest-specified Spring context, use default appXmlFile = new File(locationFile, APPLICATION_CONTEXT); if (appXmlFile.exists()) { return new UrlResource(appXmlFile.toURL()); } } catch (IOException e) { throw new ContributionReadException("Error reading manifest " + manifestFile); } } else { try { JarFile jf = new JarFile(locationFile); JarEntry je; Manifest mf = jf.getManifest(); if (mf != null) { Attributes mainAttrs = mf.getMainAttributes(); String appCtxPath = mainAttrs.getValue("Spring-Context"); if (appCtxPath != null) { je = jf.getJarEntry(appCtxPath); if (je != null) { // TODO return a Spring specific Resouce type for jars return new UrlResource(new URL("jar:" + locationFile.toURL() + "!/" + appCtxPath)); } } } je = jf.getJarEntry(APPLICATION_CONTEXT); if (je != null) { return new UrlResource(new URL("jar:" + locationFile.toURI().toURL() + "!" + APPLICATION_CONTEXT)); } } catch (IOException e) { // bad archive // TODO: create a more appropriate exception type throw new ContributionReadException("SpringXMLLoader getApplicationContextResource: " + " IO exception reading context file.", e); } } throw new ContributionReadException("SpringXMLLoader getApplicationContextResource: " + APPLICATION_CONTEXT + "not found"); } // end method getApplicationContextResource /** * Creates a Service for the component type based on its name and Java interface */ public Service createService( Class interfaze, String name ) throws InvalidInterfaceException { Service service = assemblyFactory.createService(); JavaInterfaceContract interfaceContract = javaFactory.createJavaInterfaceContract(); service.setInterfaceContract(interfaceContract); // Set the name for the service service.setName( name ); // Set the call interface and, if present, the callback interface JavaInterface callInterface = interfaceIntrospector.introspect(interfaze); service.getInterfaceContract().setInterface(callInterface); if (callInterface.getCallbackClass() != null) { JavaInterface callbackInterface = interfaceIntrospector.introspect(callInterface.getCallbackClass()); service.getInterfaceContract().setCallbackInterface(callbackInterface); } return service; } // end method createService /** * Creates a Reference for the component type based on its name and Java interface */ private org.apache.tuscany.sca.assembly.Reference createReference( Class interfaze, String name ) throws InvalidInterfaceException { org.apache.tuscany.sca.assembly.Reference reference = assemblyFactory.createReference(); JavaInterfaceContract interfaceContract = javaFactory.createJavaInterfaceContract(); reference.setInterfaceContract(interfaceContract); // Set the name of the reference to the supplied name and the multiplicity of the reference // to 1..1 - for Spring implementations, this is the only multiplicity supported reference.setName(name); reference.setMultiplicity(Multiplicity.ONE_ONE); // Set the call interface and, if present, the callback interface JavaInterface callInterface = interfaceIntrospector.introspect( interfaze ); reference.getInterfaceContract().setInterface( callInterface ); if (callInterface.getCallbackClass() != null) { JavaInterface callbackInterface = interfaceIntrospector.introspect(callInterface.getCallbackClass()); reference.getInterfaceContract().setCallbackInterface(callbackInterface); } return reference; } } // end class SpringXMLComponentTypeLoader