/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program 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. * This program 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 this program; if not, see http://www.gnu.org/licenses/ */ package com.bc.ceres.core.runtime.internal; import com.bc.ceres.core.CoreException; import com.bc.ceres.core.runtime.*; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.extended.JavaClassConverter; import com.thoughtworks.xstream.core.util.ClassLoaderReference; import com.thoughtworks.xstream.core.util.CompositeClassLoader; import com.thoughtworks.xstream.io.xml.XppDomReader; import com.thoughtworks.xstream.io.xml.XppDriver; import com.thoughtworks.xstream.io.xml.xppdom.XppDom; import java.text.MessageFormat; /** * A configuration element, with its attributes and children, * directly reflects the content and structure of the extension * section within the declaring plug-in's manifest (plugin.xml) file. * <p/> * <p>This interface also provides a way to create executable extension objects. * This interface is not intended to be implemented by clients.</p> */ public class ConfigurationElementImpl extends ConfigurationElementBaseImpl<ConfigurationElement> implements ConfigurationElement { private ExtensionImpl declaringExtension; private ConfigurationSchemaElementImpl schemaElement; public ConfigurationElementImpl(ConfigurationElementImpl parent, XppDom dom) { super(parent, dom); } @Override public ConfigurationSchemaElement getSchemaElement() { return schemaElement; } @Override public Extension getDeclaringExtension() { return declaringExtension; } @Override public <T> T createExecutableExtension(Class<T> extensionType) throws CoreException { //////////////////////////////////////////////////////////// // Compute extensionDefaultClass String extensionClassElementName = null; String extensionClassAttributeName = null; Class<T> extensionDefaultClass = null; if (schemaElement != null) { String typeAttributeValue = schemaElement.getAttribute("type"); checkExtensionType(extensionType, typeAttributeValue); String classAttributeValue = schemaElement.getAttribute("class"); String extensionDefaultClassName = classAttributeValue; if (classAttributeValue != null) { if (classAttributeValue.startsWith("@")) { // '@' is used to bind class name to an element, whose value is the actual class name value extensionClassElementName = classAttributeValue.substring(1); ConfigurationSchemaElement extensionDefaultClassNameElement = schemaElement.getChild( extensionClassElementName); if (extensionDefaultClassNameElement != null) { extensionDefaultClassName = extensionDefaultClassNameElement.getValue(); } } else if (classAttributeValue.startsWith("#")) { // '#' is used to bind class name to an attribute, whose value is the actual class name value extensionClassAttributeName = classAttributeValue.substring(1); } } if (extensionDefaultClassName != null) { extensionDefaultClassName = extensionDefaultClassName.trim(); extensionDefaultClass = loadClass(extensionDefaultClassName, extensionType); } } Class<T> extensionClass = getExtensionClass(extensionType, extensionDefaultClass, extensionClassAttributeName, extensionClassElementName); T instance = createInstance(extensionClass); XStream xStream = getXStream(extensionType, extensionClass, extensionDefaultClass); if (xStream != null) { try { xStream.unmarshal(new XppDomReader(getDom()), instance); } catch (Throwable e) { throw new CoreException( MessageFormat.format( "Module [{0}]: Failed to unmarshal executable extension [{1}]: {2}", getDeclaringModule().getSymbolicName(), getName(), e.getMessage()), e); } } if (instance instanceof ConfigurableExtension) { ConfigurableExtension configurableExtension = (ConfigurableExtension) instance; configurableExtension.configure(this); } return instance; } private <T> T createInstance(Class<T> someClass) throws CoreException { T instance; try { instance = someClass.newInstance(); } catch (Throwable e) { throw new CoreException( MessageFormat.format( "Module [{0}]: Failed to instantiate object for extension [{1}]: {2}", getDeclaringModule().getSymbolicName(), getName(), e.getMessage()), e); } return instance; } private <T> Class<T> getExtensionClass(Class<T> extensionType, Class<T> extensionDefaultClass, String extensionClassAttributeName, String extensionClassElementName) throws CoreException { Class<T> extensionClass = null; String extensionClassName = null; if (extensionClassElementName != null) { ConfigurationElement extensionClassNameElement = getChild(extensionClassElementName); if (extensionClassNameElement != null) { extensionClassName = extensionClassNameElement.getValue(); } } else if (extensionClassAttributeName != null) { extensionClassName = getAttribute(extensionClassAttributeName); } else { extensionClassName = getAttribute("class"); } if (extensionClassName != null) { extensionClassName = extensionClassName.trim(); extensionClass = loadClass(extensionClassName, extensionType); } if (extensionClass == null) { extensionClass = extensionDefaultClass; } if (extensionClass == null) { throw new CoreException( MessageFormat.format( "Module [{0}]: Missing class definition for executable extension [{1}]", getDeclaringModule().getSymbolicName(), getName())); } return extensionClass; } private <T> void checkExtensionType(Class<T> extensionType, String typeAttributeValue) throws CoreException { if (typeAttributeValue != null) { Class<?> declaredExtensionType = loadClass(typeAttributeValue); if (!declaredExtensionType.equals(extensionType)) { throw new CoreException( MessageFormat.format( "Module [{0}]: Illegal type definition for executable extension [{1}]: must be [{2}]", getDeclaringModule().getSymbolicName(), getName(), extensionType.getName())); } } } private Class<?> loadClass(String className) throws CoreException { try { return getDeclaringModule().loadClass(className); } catch (Throwable e) { throw new CoreException( MessageFormat.format( "Module [{0}]: Executable extension [{1}]: Failed to load class [{2}]", getDeclaringModule().getSymbolicName(), getName(), className), e); } } private <T> Class<T> loadClass(String className, Class<T> requiredType) throws CoreException { Class<?> someClass = loadClass(className); if (!requiredType.isAssignableFrom(someClass)) { throw new CoreException( MessageFormat.format( "Module [{0}]: Executable extension [{1}]: Class [{2}] is not a [{3}]", getDeclaringModule().getSymbolicName(), getName(), someClass.getName(), requiredType.getName())); } return (Class<T>) someClass; } private <T> Module getDeclaringModule() { return getDeclaringExtension().getDeclaringModule(); } private <T> XStream getXStream(Class<T> extensionType, Class<T> extensionClass, Class<T> extensionDefaultClass) { if (schemaElement == null) { return null; } String attribute = schemaElement.getAttribute("autoConfig"); if (attribute == null || !attribute.equalsIgnoreCase("true")) { return null; } XStream xStream = schemaElement.getXStream(); if (xStream == null) { xStream = createXStream(extensionType, extensionClass); schemaElement.setXStream(xStream); if (extensionDefaultClass != null) { schemaElement.configureAliases(extensionDefaultClass); } } schemaElement.configureAliases(extensionClass); xStream.setClassLoader(getDeclaringModule().getClassLoader()); return xStream; } private <T> XStream createXStream(Class<T> extensionType, Class<T> extensionClass) { ClassLoaderReference classLoaderReference = new ClassLoaderReference(new CompositeClassLoader()); XStream xStream = new XStream(null, new XppDriver(), classLoaderReference); xStream.aliasType(getName(), extensionType); ConfigurationSchemaElement[] children = schemaElement.getChildren(); for (ConfigurationSchemaElement child : children) { String fieldName = child.getAttribute("field"); if (fieldName != null) { xStream.aliasField(child.getName(), extensionClass, fieldName); } } JavaClassConverter classConverter = new WhitespaceIgnoringJavaClassConverter(classLoaderReference); xStream.registerConverter(classConverter, XStream.PRIORITY_VERY_HIGH); return xStream; } @Override protected ConfigurationElement[] createChildren(XppDom[] doms) { ConfigurationElement[] children = createEmptyArray(doms.length); for (int i = 0; i < doms.length; i++) { ConfigurationElementImpl child = new ConfigurationElementImpl(this, doms[i]); child.setDeclaringExtension(declaringExtension); if (schemaElement != null) { child.setSchemaElement((ConfigurationSchemaElementImpl) schemaElement.getChild(child.getName())); } children[i] = child; } return children; } @Override protected ConfigurationElement[] createEmptyArray(int n) { return new ConfigurationElement[n]; } void setDeclaringExtension(ExtensionImpl declaringExtension) { this.declaringExtension = declaringExtension; } void setSchemaElement(ConfigurationSchemaElementImpl schemaElement) { this.schemaElement = schemaElement; } private static class WhitespaceIgnoringJavaClassConverter extends JavaClassConverter { public WhitespaceIgnoringJavaClassConverter(ClassLoader classLoader) { super(classLoader); } @Override public Object fromString(String str) { return super.fromString(str.trim()); } } }