* @author Vaadin Ltd
*/
public class Design implements Serializable {
+
+ /**
+ * Callback for creating instances of a given component class when reading
+ * designs. The default implementation, {@link DefaultComponentFactory} will
+ * use <code>Class.forName(className).newInstance()</code>, which might not
+ * be suitable e.g. in an OSGi environment or if the Component instances
+ * should be created as managed CDI beans.
+ * <p>
+ * Use {@link Design#setComponentFactory(ComponentFactory)} to configure
+ * Vaadin to use a custom component factory.
+ *
+ *
+ * @since 7.4.1
+ */
+ public interface ComponentFactory extends Serializable {
+ /**
+ * Creates a component based on the fully qualified name derived from
+ * the tag name in the design.
+ *
+ * @param fullyQualifiedClassName
+ * the fully qualified name of the component to create
+ * @param context
+ * the design context for which the component is created
+ *
+ * @return a newly created component
+ */
+ public Component createComponent(String fullyQualifiedClassName,
+ DesignContext context);
+ }
+
+ /**
+ * Default implementation of {@link ComponentFactory}, using
+ * <code>Class.forName(className).newInstance()</code> for finding the
+ * component class and creating a component instance.
+ *
+ * @since 7.4.1
+ */
+ public static class DefaultComponentFactory implements ComponentFactory {
+ @Override
+ public Component createComponent(String fullyQualifiedClassName,
+ DesignContext context) {
+ Class<? extends Component> componentClass = resolveComponentClass(
+ fullyQualifiedClassName, context);
+
+ assert Component.class.isAssignableFrom(componentClass) : "resolveComponentClass returned "
+ + componentClass + " which is not a Vaadin Component class";
+
+ try {
+ return componentClass.newInstance();
+ } catch (Exception e) {
+ throw new DesignException("Could not create component "
+ + fullyQualifiedClassName, e);
+ }
+ }
+
+ /**
+ * Resolves a component class based on the fully qualified name of the
+ * class.
+ *
+ * @param qualifiedClassName
+ * the fully qualified name of the resolved class
+ * @param context
+ * the design context for which the class is resolved
+ * @return a component class object representing the provided class name
+ */
+ protected Class<? extends Component> resolveComponentClass(
+ String qualifiedClassName, DesignContext context) {
+ try {
+ Class<?> componentClass = Class.forName(qualifiedClassName);
+ return componentClass.asSubclass(Component.class);
+ } catch (ClassNotFoundException e) {
+ throw new DesignException(
+ "Unable to load component for design", e);
+ }
+ }
+
+ }
+
+ private static volatile ComponentFactory componentFactory = new DefaultComponentFactory();
+
+ /**
+ * Sets the component factory that is used for creating component instances
+ * based on fully qualified class names derived from a design file.
+ * <p>
+ * Please note that this setting is global, so care should be taken to avoid
+ * conflicting changes.
+ *
+ * @param componentFactory
+ * the component factory to set; not <code>null</code>
+ *
+ * @since 7.4.1
+ */
+ public static void setComponentFactory(ComponentFactory componentFactory) {
+ if (componentFactory == null) {
+ throw new IllegalArgumentException(
+ "Cannot set null component factory");
+ }
+ Design.componentFactory = componentFactory;
+ }
+
+ /**
+ * Gets the currently used component factory.
+ *
+ * @see #setComponentFactory(ComponentFactory)
+ *
+ * @return the component factory
+ *
+ * @since 7.4.1
+ */
+ public static ComponentFactory getComponentFactory() {
+ return componentFactory;
+ }
+
/**
* Parses the given input stream into a jsoup document
*
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.Component;
import com.vaadin.ui.HasComponents;
+import com.vaadin.ui.declarative.Design.ComponentFactory;
/**
* This class contains contextual information that is collected when a component
private Component instantiateComponent(Node node) {
// Extract the package and class names.
String qualifiedClassName = tagNameToClassName(node);
- try {
- Class<? extends Component> componentClass = resolveComponentClass(qualifiedClassName);
- Component newComponent = componentClass.newInstance();
- return newComponent;
- } catch (Exception e) {
- throw new DesignException("No component class could be found for "
- + node.nodeName() + ".", e);
+
+ ComponentFactory factory = Design.getComponentFactory();
+ Component component = factory.createComponent(qualifiedClassName, this);
+
+ if (component == null) {
+ throw new DesignException("Got unexpected null component from "
+ + factory.getClass().getName() + " for class "
+ + qualifiedClassName);
}
+
+ return component;
}
/**
return packageName + "." + className;
}
- @SuppressWarnings("unchecked")
- private Class<? extends Component> resolveComponentClass(
- String qualifiedClassName) throws ClassNotFoundException {
- Class<?> componentClass = null;
- componentClass = Class.forName(qualifiedClassName);
-
- // Check that we're dealing with a Component.
- if (isComponent(componentClass)) {
- return (Class<? extends Component>) componentClass;
- } else {
- throw new IllegalArgumentException(String.format(
- "Resolved class %s is not a %s.", componentClass.getName(),
- Component.class.getName()));
- }
- }
-
- /**
- * Returns {@code true} if the given {@link Class} implements the
- * {@link Component} interface of Vaadin Framework otherwise {@code false}.
- *
- * @param componentClass
- * {@link Class} to check against {@link Component} interface.
- * @return {@code true} if the given {@link Class} is a {@link Component},
- * {@code false} otherwise.
- */
- private static boolean isComponent(Class<?> componentClass) {
- if (componentClass != null) {
- return Component.class.isAssignableFrom(componentClass);
- } else {
- return false;
- }
- }
-
/**
* Returns the root component of a created component hierarchy.
*
--- /dev/null
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed 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 com.vaadin.tests.design;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.declarative.Design;
+import com.vaadin.ui.declarative.Design.ComponentFactory;
+import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignException;
+
+public class ComponentFactoryTest {
+
+ private static final ComponentFactory defaultFactory = Design
+ .getComponentFactory();
+
+ private static final ThreadLocal<ComponentFactory> currentComponentFactory = new ThreadLocal<ComponentFactory>();
+
+ // Set static component factory that delegate to a thread local factory
+ static {
+ Design.setComponentFactory(new ComponentFactory() {
+ @Override
+ public Component createComponent(String fullyQualifiedClassName,
+ DesignContext context) {
+ ComponentFactory componentFactory = currentComponentFactory
+ .get();
+ if (componentFactory == null) {
+ componentFactory = defaultFactory;
+ }
+ return componentFactory.createComponent(
+ fullyQualifiedClassName, context);
+ }
+ });
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetNullComponentFactory() {
+ Design.setComponentFactory(null);
+ }
+
+ @Test
+ public void testComponentFactoryLogging() {
+ final List<String> messages = new ArrayList<String>();
+ currentComponentFactory.set(new ComponentFactory() {
+ @Override
+ public Component createComponent(String fullyQualifiedClassName,
+ DesignContext context) {
+ messages.add("Requested class " + fullyQualifiedClassName);
+ return defaultFactory.createComponent(fullyQualifiedClassName,
+ context);
+ }
+ });
+
+ Design.read(new ByteArrayInputStream("<v-label />".getBytes()));
+
+ Assert.assertEquals("There should be one message logged", 1,
+ messages.size());
+ Assert.assertEquals(
+ "Requested class " + Label.class.getCanonicalName(),
+ messages.get(0));
+ }
+
+ @Test(expected = DesignException.class)
+ public void testComponentFactoryReturningNull() {
+ currentComponentFactory.set(new ComponentFactory() {
+ @Override
+ public Component createComponent(String fullyQualifiedClassName,
+ DesignContext context) {
+ return null;
+ }
+ });
+
+ Design.read(new ByteArrayInputStream("<v-label />".getBytes()));
+ }
+
+ @Test(expected = DesignException.class)
+ public void testComponentFactoryThrowingStuff() {
+ currentComponentFactory.set(new ComponentFactory() {
+ @Override
+ public Component createComponent(String fullyQualifiedClassName,
+ DesignContext context) {
+ // Will throw because class is not found
+ return defaultFactory.createComponent("foobar."
+ + fullyQualifiedClassName, context);
+ }
+ });
+
+ Design.read(new ByteArrayInputStream("<v-label />".getBytes()));
+ }
+
+ @After
+ public void cleanup() {
+ currentComponentFactory.remove();
+ }
+
+}