
org.fabric3.implementation.spring.introspection.SpringImplementationProcessorImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fabric3-spring Show documentation
Show all versions of fabric3-spring Show documentation
Fabric3 Spring Component Implementation.
The newest version!
/*
* Fabric3
* Copyright (c) 2009-2013 Metaform Systems
*
* Fabric3 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version, with the
* following exception:
*
* Linking this software statically or dynamically with other
* modules is making a combined work based on this software.
* Thus, the terms and conditions of the GNU General Public
* License cover the whole combination.
*
* As a special exception, the copyright holders of this software
* give you permission to link this software with independent
* modules to produce an executable, regardless of the license
* terms of these independent modules, and to copy and distribute
* the resulting executable under terms of your choice, provided
* that you also meet, for each linked independent module, the
* terms and conditions of the license of that module. An
* independent module is a module which is not derived from or
* based on this software. If you modify this software, you may
* extend this exception to your version of the software, but
* you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version.
*
* Fabric3 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the
* GNU General Public License along with Fabric3.
* If not, see .
*/
package org.fabric3.implementation.spring.introspection;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import org.fabric3.host.stream.Source;
import org.fabric3.implementation.spring.model.BeanDefinition;
import org.fabric3.implementation.spring.model.SpringComponentType;
import org.fabric3.implementation.spring.model.SpringConsumer;
import org.fabric3.implementation.spring.model.SpringReferenceDefinition;
import org.fabric3.implementation.spring.model.SpringService;
import org.fabric3.model.type.component.ConsumerDefinition;
import org.fabric3.model.type.component.ProducerDefinition;
import org.fabric3.model.type.component.Property;
import org.fabric3.model.type.component.ReferenceDefinition;
import org.fabric3.model.type.contract.ServiceContract;
import org.fabric3.spi.introspection.IntrospectionContext;
import org.fabric3.spi.introspection.java.contract.JavaContractProcessor;
import org.fabric3.spi.introspection.xml.InvalidValue;
import org.fabric3.spi.introspection.xml.MissingAttribute;
import org.fabric3.spi.introspection.xml.UnrecognizedElement;
import org.fabric3.spi.model.type.java.JavaClass;
import org.fabric3.spi.xml.XMLFactory;
import org.oasisopen.sca.Constants;
import org.oasisopen.sca.annotation.Reference;
import org.oasisopen.sca.annotation.Remotable;
import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
/**
* Default SpringImplementationProcessor implementation.
*/
public class SpringImplementationProcessorImpl implements SpringImplementationProcessor {
private static final String BEAN = "bean";
private static final QName SERVICE = new QName(Constants.SCA_NS, "service");
private static final QName REFERENCE = new QName(Constants.SCA_NS, "reference");
private static final QName PROPERTY = new QName(Constants.SCA_NS, "property");
private static final QName PRODUCER = new QName(Constants.SCA_NS, "producer");
private static final QName CONSUMER = new QName(Constants.SCA_NS, "consumer");
private static final String XSD_NS = XMLConstants.W3C_XML_SCHEMA_NS_URI;
private static final QName XSD_STRING = new QName(XSD_NS, "string");
private static final QName XSD_BOOLEAN = new QName(XSD_NS, "boolean");
private static final QName XSD_INT = new QName(XSD_NS, "integer");
private JavaContractProcessor contractProcessor;
private final XMLInputFactory xmlInputFactory;
private boolean strictValidation;
@org.oasisopen.sca.annotation.Property(required = false)
public void setValidate(boolean validation) {
strictValidation = validation;
}
public SpringImplementationProcessorImpl(@Reference JavaContractProcessor contractProcessor, @Reference XMLFactory factory) {
this.contractProcessor = contractProcessor;
xmlInputFactory = factory.newInputFactoryInstance();
}
public SpringComponentType introspect(Source source, IntrospectionContext context) throws XMLStreamException {
InputStream stream = null;
XMLStreamReader reader = null;
try {
SpringComponentType type = new SpringComponentType();
stream = source.openStream();
reader = xmlInputFactory.createXMLStreamReader(stream);
Location start = reader.getLocation();
processStream(context, reader, type);
if (source instanceof MultiSource) {
// multiple app contexts
MultiSource multiSource = (MultiSource) source;
for (Source contextSource : multiSource.getSources()) {
stream = contextSource.openStream();
reader = xmlInputFactory.createXMLStreamReader(stream);
processStream(context, reader, type);
}
}
validate(type, context, start);
return type;
} catch (IOException e) {
throw new XMLStreamException(e);
} finally {
if (reader != null) {
reader.close();
}
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
// ignore
e.printStackTrace();
}
}
}
private void processStream(IntrospectionContext context, XMLStreamReader reader, SpringComponentType type) throws XMLStreamException {
while (true) {
switch (reader.next()) {
case START_ELEMENT:
if (BEAN.equals(reader.getName().getLocalPart())) {
if (!processBean(type, reader, context)) {
return;
}
} else if (SERVICE.equals(reader.getName())) {
if (!processService(type, reader, context)) {
return;
}
} else if (REFERENCE.equals(reader.getName())) {
if (!processReference(type, reader, context)) {
return;
}
} else if (PROPERTY.equals(reader.getName())) {
if (!processProperty(type, reader, context)) {
return;
}
} else if (PRODUCER.equals(reader.getName())) {
if (!processProducer(type, reader, context)) {
return;
}
} else if (CONSUMER.equals(reader.getName())) {
if (!processConsumer(type, reader, context)) {
return;
}
} else {
if (reader.getName().getNamespaceURI().equals(Constants.SCA_NS)) {
UnrecognizedElement error = new UnrecognizedElement(reader, reader.getLocation(), type);
context.addError(error);
}
}
break;
case XMLStreamConstants.END_DOCUMENT:
postProcess(type, context);
return;
}
}
}
/**
* Performs validation.
*
* @param type the component type
* @param context the context
*/
private void validate(SpringComponentType type, IntrospectionContext context, Location location) {
Collection references = type.getReferences().values();
for (ReferenceDefinition reference : references) {
String defaultStr = ((SpringReferenceDefinition) reference).getDefaultValue();
if (defaultStr != null) {
if (!type.getBeansById().containsKey(defaultStr) && !type.getBeansByName().containsKey(defaultStr)) {
InvalidValue error = new InvalidValue("Default value '" + defaultStr + "' does not reference a valid bean", location, type);
context.addError(error);
}
}
}
if (strictValidation) {
// SCA spec validation
if (type.getSpringServices().isEmpty()) {
// if no services defined, check remotables
for (BeanDefinition beanDefinition : type.getBeansByName().values()) {
validateBean(type, beanDefinition, context, location);
}
for (BeanDefinition beanDefinition : type.getBeansById().values()) {
validateBean(type, beanDefinition, context, location);
}
}
}
}
private void validateBean(SpringComponentType type, BeanDefinition beanDefinition, IntrospectionContext context, Location location) {
Class> clazz = beanDefinition.getBeanClass();
int number = 0;
for (Class> interfaze : clazz.getInterfaces()) {
if (interfaze.isAnnotationPresent(Remotable.class)) {
number++;
}
}
if (number > 1) {
InvalidValue error = new InvalidValue("Bean cannot implement multiple remotable services if no SCA services are defined in the parent" +
" application context: " + clazz, location, type);
context.addError(error);
}
}
/**
* Processes a Spring bean
definition.
*
* @param type the component type
* @param reader the reader
* @param context the context for reporting errors
* @return true if processing completed without validation errors
*/
private boolean processBean(SpringComponentType type, XMLStreamReader reader, IntrospectionContext context) {
Location location = reader.getLocation();
String id = reader.getAttributeValue(null, "id");
String name = reader.getAttributeValue(null, "name");
if (id == null && name == null) {
MissingAttribute failure = new MissingAttribute("A bean id or name must be specified", location);
context.addError(failure);
return false;
}
String classAttr = reader.getAttributeValue(null, "class");
Class> clazz = null;
if (classAttr != null) {
try {
clazz = context.getClassLoader().loadClass(classAttr);
} catch (ClassNotFoundException e) {
InvalidValue failure = new InvalidValue("Bean class not found: " + classAttr, location, e);
context.addError(failure);
}
}
BeanDefinition bean = new BeanDefinition();
bean.setId(id);
bean.setName(name);
bean.setBeanClass(clazz);
type.add(bean);
return true;
}
/**
* Processes an SCA service
element.
*
* @param type the component type
* @param reader the reader
* @param context the context for reporting errors
* @return true if processing completed without validation errors
*/
private boolean processService(SpringComponentType type, XMLStreamReader reader, IntrospectionContext context) {
Location startLocation = reader.getLocation();
// TODO This does not currently support policy declarations
String name = reader.getAttributeValue(null, "name");
if (name == null) {
MissingAttribute failure = new MissingAttribute("A service name must be specified", startLocation);
context.addError(failure);
return false;
}
if (type.getServices().containsKey(name)) {
DuplicateService failure = new DuplicateService(name, startLocation, type);
context.addError(failure);
return false;
}
String target = reader.getAttributeValue(null, "target");
if (target == null) {
MissingAttribute failure = new MissingAttribute("A service target must be specified", startLocation);
context.addError(failure);
return false;
}
String typeAttr = reader.getAttributeValue(null, "type");
ServiceContract contract = null;
if (typeAttr != null) {
Class> interfaze;
try {
ClassLoader loader = context.getClassLoader();
interfaze = loader.loadClass(typeAttr);
} catch (ClassNotFoundException e) {
InvalidValue failure = new InvalidValue("Service interface not found: " + typeAttr, startLocation);
context.addError(failure);
return false;
}
contract = contractProcessor.introspect(interfaze, context, type);
}
SpringService definition = new SpringService(name, contract, target);
type.add(definition);
return true;
}
/**
* Processes an SCA reference
element.
*
* @param type the component type
* @param reader the reader
* @param context the context for reporting errors
* @return true if processing completed without validation errors
*/
private boolean processReference(SpringComponentType type, XMLStreamReader reader, IntrospectionContext context) {
// TODO This does not currently support policy declarations
// TODO This does not currently support the @default attribute
Location startLocation = reader.getLocation();
String name = reader.getAttributeValue(null, "name");
if (name == null) {
MissingAttribute failure = new MissingAttribute("A reference name must be specified", startLocation);
context.addError(failure);
return false;
}
if (type.getReferences().containsKey(name)) {
DuplicateReference failure = new DuplicateReference(name, startLocation, type);
context.addError(failure);
return false;
}
String typeAttr = reader.getAttributeValue(null, "type");
if (typeAttr == null) {
MissingAttribute failure = new MissingAttribute("A service type must be specified", startLocation);
context.addError(failure);
return false;
}
Class> interfaze;
try {
ClassLoader loader = context.getClassLoader();
interfaze = loader.loadClass(typeAttr);
} catch (ClassNotFoundException e) {
InvalidValue failure = new InvalidValue("Service interface not found: " + typeAttr, startLocation);
context.addError(failure);
return false;
}
String defaultStr = reader.getAttributeValue(null, "default");
ServiceContract contract = contractProcessor.introspect(interfaze, context, type);
ReferenceDefinition definition = new SpringReferenceDefinition(name, contract, defaultStr);
type.add(definition);
return true;
}
/**
* Processes an SCA property
element.
*
* @param type the component type
* @param reader the reader
* @param context the context for reporting errors
* @return true if processing completed without validation errors
*/
private boolean processProperty(SpringComponentType type, XMLStreamReader reader, IntrospectionContext context) {
Location startLocation = reader.getLocation();
String name = reader.getAttributeValue(null, "name");
if (name == null) {
MissingAttribute failure = new MissingAttribute("A property name must be specified", startLocation);
context.addError(failure);
return false;
}
if (type.getProperties().containsKey(name)) {
DuplicateProperty failure = new DuplicateProperty(name, startLocation, type);
context.addError(failure);
return false;
}
Property property = new Property(name);
property.setRequired(true);
String propertyType = reader.getAttributeValue(null, "type");
if (Integer.class.getName().equals(propertyType)) {
property.setType(XSD_INT);
} else if (Boolean.class.getName().equals(propertyType)) {
property.setType(XSD_BOOLEAN);
} else {
property.setType(XSD_STRING);
}
type.add(property);
return true;
}
/**
* Processes an SCA consumer
element.
*
* @param type the component type
* @param reader the reader
* @param context the context for reporting errors
* @return true if processing completed without validation errors
*/
private boolean processConsumer(SpringComponentType type, XMLStreamReader reader, IntrospectionContext context) {
Location startLocation = reader.getLocation();
String name = reader.getAttributeValue(null, "name");
if (name == null) {
MissingAttribute failure = new MissingAttribute("A consumer name must be specified", startLocation);
context.addError(failure);
return false;
}
if (type.getConsumers().containsKey(name)) {
DuplicateConsumer failure = new DuplicateConsumer(name, startLocation, type);
context.addError(failure);
return false;
}
String typeAttr = reader.getAttributeValue(null, "type");
if (typeAttr == null) {
MissingAttribute failure = new MissingAttribute("A consumer data type must be specified", startLocation);
context.addError(failure);
return false;
}
Class consumerType;
try {
ClassLoader loader = context.getClassLoader();
consumerType = cast(loader.loadClass(typeAttr));
} catch (ClassNotFoundException e) {
InvalidValue failure = new InvalidValue("Consumer interface not found: " + typeAttr, startLocation);
context.addError(failure);
return false;
}
JavaClass dataType = new JavaClass(consumerType);
String target = reader.getAttributeValue(null, "target");
if (target == null) {
MissingAttribute failure = new MissingAttribute("A consumer target must be specified", startLocation);
context.addError(failure);
return false;
}
String[] targetTokens = target.split("/");
if (targetTokens.length != 2) {
InvalidValue failure = new InvalidValue("Target value must be in the form beanName/methodName", startLocation);
context.addError(failure);
return false;
}
ConsumerDefinition definition = new SpringConsumer(name, dataType, targetTokens[0], targetTokens[1]);
type.add(definition);
return true;
}
/**
* Processes an SCA producer
element.
*
* @param type the component type
* @param reader the reader
* @param context the context for reporting errors
* @return true if processing completed without validation errors
*/
private boolean processProducer(SpringComponentType type, XMLStreamReader reader, IntrospectionContext context) {
Location startLocation = reader.getLocation();
String name = reader.getAttributeValue(null, "name");
if (name == null) {
MissingAttribute failure = new MissingAttribute("A producer name must be specified", startLocation);
context.addError(failure);
return false;
}
if (type.getProducers().containsKey(name)) {
DuplicateProducer failure = new DuplicateProducer(name, startLocation, type);
context.addError(failure);
return false;
}
String typeAttr = reader.getAttributeValue(null, "type");
if (typeAttr == null) {
MissingAttribute failure = new MissingAttribute("A producer data type must be specified", startLocation);
context.addError(failure);
return false;
}
Class> interfaze;
try {
ClassLoader loader = context.getClassLoader();
interfaze = loader.loadClass(typeAttr);
} catch (ClassNotFoundException e) {
InvalidValue failure = new InvalidValue("Service interface not found: " + typeAttr, startLocation);
context.addError(failure);
return false;
}
ServiceContract contract = contractProcessor.introspect(interfaze, context, type);
if (contract.getOperations().size() != 1) {
String interfaceName = contract.getInterfaceName();
InvalidValue error = new InvalidValue("Producer interfaces must have one method: " + interfaceName, startLocation, type);
context.addError(error);
}
ProducerDefinition definition = new ProducerDefinition(name, contract);
type.add(definition);
return true;
}
/**
* Performs heuristic introspection and validation.
*
* @param type the component type
* @param context the context for reporting errors
*/
private void postProcess(SpringComponentType type, IntrospectionContext context) {
if (type.getServices().isEmpty() && type.getReferences().isEmpty() && type.getProperties().isEmpty()) {
processHueristics(type, context);
return;
}
// introspect service contracts for service elements that do not explicitly have a type element
postProcessServices(type, context);
}
/**
* Performs heuristic introspection.
*
* @param type the component type
* @param context the context for reporting errors
*/
private void processHueristics(SpringComponentType type, IntrospectionContext context) {
// TODO synthesize optional references
// TODO synthesize services
// TODO synthesize properties
}
/**
* Performs heuristic introspection and validation of services.
*
* @param type the component type
* @param context the context for reporting errors
*/
private void postProcessServices(SpringComponentType type, IntrospectionContext context) {
for (SpringService service : type.getSpringServices().values()) {
String target = service.getTarget();
BeanDefinition definition = type.getBeansById().get(target);
if (definition == null) {
definition = type.getBeansByName().get(target);
}
if (definition == null) {
ServiceTargetNotFound failure = new ServiceTargetNotFound(service.getName(), target, type);
context.addError(failure);
continue;
}
if (service.getServiceContract() == null) {
introspectContract(service, definition, type, context);
}
}
}
/**
* Introspects a service contract from a bean definition.
*
* @param service the service
* @param definition the bean definition
* @param type the component type
* @param context the context for reporting errors
*/
private void introspectContract(SpringService service, BeanDefinition definition, SpringComponentType type, IntrospectionContext context) {
Class> beanClass = definition.getBeanClass();
String serviceName = service.getName();
if (beanClass == null) {
UnknownServiceType failure = new UnknownServiceType(serviceName, beanClass, type);
context.addError(failure);
return;
}
Class>[] interfaces = beanClass.getInterfaces();
if (interfaces.length == 0) {
// use the implementation class
ServiceContract contract = contractProcessor.introspect(beanClass, context, type);
service.setServiceContract(contract);
} else if (interfaces.length == 1) {
// default service
ServiceContract contract = contractProcessor.introspect(interfaces[0], context, type);
service.setServiceContract(contract);
} else {
// match on service name
ServiceContract contract = null;
for (Class> interfaze : interfaces) {
if (serviceName.equals(interfaze.getSimpleName())) {
contract = contractProcessor.introspect(interfaze, context, type);
service.setServiceContract(contract);
break;
}
}
if (contract == null) {
UnknownServiceType failure = new UnknownServiceType(serviceName, beanClass, type);
context.addError(failure);
}
}
}
@SuppressWarnings({"unchecked"})
private T cast(Object o) {
return (T) o;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy