summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeif Åstrand <leif@vaadin.com>2015-02-25 16:24:29 +0200
committerVaadin Code Review <review@vaadin.com>2015-02-26 11:26:24 +0000
commit5830a1f96b24186a68023258630ef1d89590d31e (patch)
treec62cf65129c6d1c05ae751a3c046f48b5d071023
parent405aef419f671e5cd3f4d54309cbb55a46f9dbd1 (diff)
downloadvaadin-framework-5830a1f96b24186a68023258630ef1d89590d31e.tar.gz
vaadin-framework-5830a1f96b24186a68023258630ef1d89590d31e.zip
Add pluggable mechanism for loading classes for a design (#16583)
Change-Id: I2ac17e3c5a7c36492567238af8f4cf6723b0ec69
-rw-r--r--server/src/com/vaadin/ui/declarative/Design.java113
-rw-r--r--server/src/com/vaadin/ui/declarative/DesignContext.java51
-rw-r--r--server/tests/src/com/vaadin/tests/design/ComponentFactoryTest.java117
3 files changed, 241 insertions, 40 deletions
diff --git a/server/src/com/vaadin/ui/declarative/Design.java b/server/src/com/vaadin/ui/declarative/Design.java
index dc96e789bf..1b8585e6f6 100644
--- a/server/src/com/vaadin/ui/declarative/Design.java
+++ b/server/src/com/vaadin/ui/declarative/Design.java
@@ -57,6 +57,119 @@ import com.vaadin.ui.declarative.DesignContext.ComponentCreationListener;
* @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
*
diff --git a/server/src/com/vaadin/ui/declarative/DesignContext.java b/server/src/com/vaadin/ui/declarative/DesignContext.java
index 5f160d6f26..09fefd0a6b 100644
--- a/server/src/com/vaadin/ui/declarative/DesignContext.java
+++ b/server/src/com/vaadin/ui/declarative/DesignContext.java
@@ -31,6 +31,7 @@ import com.vaadin.annotations.DesignRoot;
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
@@ -482,14 +483,17 @@ public class DesignContext implements Serializable {
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;
}
/**
@@ -530,39 +534,6 @@ public class DesignContext implements Serializable {
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.
*
diff --git a/server/tests/src/com/vaadin/tests/design/ComponentFactoryTest.java b/server/tests/src/com/vaadin/tests/design/ComponentFactoryTest.java
new file mode 100644
index 0000000000..a5f1d288a2
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/design/ComponentFactoryTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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();
+ }
+
+}