diff options
author | Markus Koivisto <markus@vaadin.com> | 2016-04-26 11:35:02 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2016-04-29 10:28:13 +0000 |
commit | 7787eee10da332201e14ca69c52b5404c3b7f7f8 (patch) | |
tree | 30cdee54f4b65cd4f415fc69c62d4b01e6f833f0 | |
parent | e5addd7eb2cddaaea7d5c74e6e435ca4f854e915 (diff) | |
download | vaadin-framework-7787eee10da332201e14ca69c52b5404c3b7f7f8.tar.gz vaadin-framework-7787eee10da332201e14ca69c52b5404c3b7f7f8.zip |
Add interface for AppWidgetset (#19770)
This allows selecting the widgetset name and URL simply by placing
a class in the default package if nothing is explicitly give by
the user. This can be used e.g. by the Maven plug-in and other build
automation tools to automatically generate widgetsets or use one
from CDN.
Change-Id: If3a7b35e3b25371e08e6d9b504fcda6f66de5119
6 files changed, 264 insertions, 43 deletions
diff --git a/all/src/main/templates/release-notes.html b/all/src/main/templates/release-notes.html index b9dcc9774b..6c522ef4d6 100644 --- a/all/src/main/templates/release-notes.html +++ b/all/src/main/templates/release-notes.html @@ -112,6 +112,7 @@ <ul> <li>Vaadin artifacts no longer bring a transitive dependency to javax.servlet:servlet-api.</li> <li>System properties now override application parameters for settings such as production mode (see above).</li> + <li>The return type of UIProvider.getWidgetset() and BootstrapHandler.getWidgetsetForUI() has changed.</li> </ul> <h3 id="knownissues">Known Issues and Limitations</h3> <ul> diff --git a/server/src/main/java/com/vaadin/server/BootstrapHandler.java b/server/src/main/java/com/vaadin/server/BootstrapHandler.java index a9343a7e03..b766dfc92c 100644 --- a/server/src/main/java/com/vaadin/server/BootstrapHandler.java +++ b/server/src/main/java/com/vaadin/server/BootstrapHandler.java @@ -55,10 +55,10 @@ import elemental.json.JsonObject; import elemental.json.impl.JsonUtil; /** - * + * * @author Vaadin Ltd * @since 7.0.0 - * + * * @deprecated As of 7.0. Will likely change or be removed in a future version */ @Deprecated @@ -76,12 +76,12 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { private final VaadinResponse response; private final BootstrapFragmentResponse bootstrapResponse; - private String widgetsetName; private String themeName; private String appId; private PushMode pushMode; private JsonObject applicationParameters; private VaadinUriResolver uriResolver; + private WidgetsetInfo widgetsetInfo; public BootstrapContext(VaadinResponse response, BootstrapFragmentResponse bootstrapResponse) { @@ -105,11 +105,20 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { return bootstrapResponse.getUiClass(); } - public String getWidgetsetName() { - if (widgetsetName == null) { - widgetsetName = getWidgetsetForUI(this); + public WidgetsetInfo getWidgetsetInfo() { + if (widgetsetInfo == null) { + widgetsetInfo = getWidgetsetForUI(this); } - return widgetsetName; + return widgetsetInfo; + } + + /** + * @return returns the name of the widgetset to use + * @deprecated use {@link #getWidgetsetInfo()} instead + */ + @Deprecated + public String getWidgetsetName() { + return getWidgetsetInfo().getWidgetsetName(); } public String getThemeName() { @@ -455,18 +464,19 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { return null; } - public String getWidgetsetForUI(BootstrapContext context) { + public WidgetsetInfo getWidgetsetForUI(BootstrapContext context) { VaadinRequest request = context.getRequest(); UICreateEvent event = new UICreateEvent(context.getRequest(), context.getUIClass()); - String widgetset = context.getBootstrapResponse().getUIProvider() - .getWidgetset(event); + WidgetsetInfo widgetset = context.getBootstrapResponse() + .getUIProvider().getWidgetset(event); if (widgetset == null) { - widgetset = request.getService().getConfiguredWidgetset(request); + // TODO do we want to move WidgetsetInfoImpl elsewhere? + widgetset = new WidgetsetInfoImpl(request.getService() + .getConfiguredWidgetset(request)); } - widgetset = VaadinServlet.stripSpecialChars(widgetset); return widgetset; } @@ -477,9 +487,9 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { * Override this method if you want to add some custom html around around * the div element into which the actual Vaadin application will be * rendered. - * + * * @param context - * + * * @throws IOException */ private void setupMainDiv(BootstrapContext context) throws IOException { @@ -618,7 +628,15 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { } appConfig.put("versionInfo", versionInfo); - appConfig.put("widgetset", context.getWidgetsetName()); + + WidgetsetInfo widgetsetInfo = context.getWidgetsetInfo(); + appConfig.put("widgetset", VaadinServlet + .stripSpecialChars(widgetsetInfo.getWidgetsetName())); + // add widgetset url if not null + if (widgetsetInfo.getWidgetsetUrl() != null) { + appConfig.put("widgetsetUrl", widgetsetInfo.getWidgetsetUrl()); + } + appConfig.put("widgetsetReady", !widgetsetInfo.isCdn()); // Use locale from session if set, else from the request Locale locale = ServletPortletHelper.findLocale(null, @@ -694,13 +712,13 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { /** * Get the URI for the application theme. - * + * * A portal-wide default theme is fetched from the portal shared resource * directory (if any), other themes from the portlet. - * + * * @param context * @param themeName - * + * * @return */ public String getThemeUri(BootstrapContext context, String themeName) { @@ -713,7 +731,7 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { /** * Override if required - * + * * @param context * @return */ @@ -725,7 +743,7 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { /** * Do not override. - * + * * @param context * @return */ diff --git a/server/src/main/java/com/vaadin/server/UIProvider.java b/server/src/main/java/com/vaadin/server/UIProvider.java index 7fd880919e..9b0087185b 100644 --- a/server/src/main/java/com/vaadin/server/UIProvider.java +++ b/server/src/main/java/com/vaadin/server/UIProvider.java @@ -1,12 +1,12 @@ /* * 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 @@ -20,6 +20,8 @@ import java.io.InputStream; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.annotation.Inherited; +import java.util.logging.Level; +import java.util.logging.Logger; import com.vaadin.annotations.PreserveOnRefresh; import com.vaadin.annotations.Push; @@ -58,7 +60,7 @@ public abstract class UIProvider implements Serializable { * do not follow the standard semantics and are supported for backwards * compatibility. Future versions of the framework might only support the * standard semantics of {@code @Inherited}. - * + * * @param clazz * the class from which the annotation should be found * @param annotationType @@ -104,13 +106,13 @@ public abstract class UIProvider implements Serializable { * <p> * The default implementation checks for a @{@link Theme} annotation on the * UI class. - * + * * @param event * the UI create event with information about the UI and the * current request. * @return the name of the theme, or <code>null</code> if the default theme * should be used - * + * */ public String getTheme(UICreateEvent event) { Theme uiTheme = getAnnotationFor(event.getUIClass(), Theme.class); @@ -125,26 +127,90 @@ public abstract class UIProvider implements Serializable { * Finds the widgetset to use for a specific UI. If no specific widgetset is * required, <code>null</code> is returned. * <p> - * The default implementation uses the @{@link Widgetset} annotation if it's - * defined for the UI class. - * + * The default implementation uses the following order of priority for + * finding the widgetset information: + * <ul> + * <li>@{@link Widgetset} annotation if it is defined for the UI class</li> + * <li>The class AppWidgetset if one exists in the default package</li> + * <li>A widgetset called AppWidgetset if there is an AppWidgetset.gwt.xml + * file in the default package</li> + * <li>null to use the default widgetset otherwise</li> + * </ul> + * + * Note that the return type of this method changed in Vaadin 7.7. + * * @param event * the UI create event with information about the UI and the * current request. * @return the name of the widgetset, or <code>null</code> if the default * widgetset should be used - * + * + * @since 7.7 */ - public String getWidgetset(UICreateEvent event) { + public WidgetsetInfo getWidgetset(UICreateEvent event) { Widgetset uiWidgetset = getAnnotationFor(event.getUIClass(), Widgetset.class); + + // First case: We have an @Widgetset annotation, use that if (uiWidgetset != null) { - return uiWidgetset.value(); - } else { + return new WidgetsetInfoImpl(uiWidgetset.value()); + } + + // Find the class AppWidgetset in the default package if one exists + WidgetsetInfo info = getWidgetsetClassInfo(); + + // Second case: we have a generated class called APP_WIDGETSET_NAME + if (info != null) { + return info; + } + + // third case: we have an AppWidgetset.gwt.xml file + else { InputStream resource = event.getUIClass().getResourceAsStream( "/" + APP_WIDGETSET_NAME + ".gwt.xml"); if (resource != null) { - return APP_WIDGETSET_NAME; + return new WidgetsetInfoImpl(false, null, APP_WIDGETSET_NAME); + } + } + + // fourth case: we are using the default widgetset + return null; + } + + private Class<WidgetsetInfo> findWidgetsetClass() { + try { + // We cannot naively use Class.forname without getting the correct + // classloader + // FIXME This might still fail with osgi + ClassLoader tccl = VaadinService.getCurrent().getClassLoader(); + Class<?> c = Class.forName(APP_WIDGETSET_NAME, true, tccl); + + // if not implementing the interface, possibly a @WebListener class + // from an earlier version - ignore it + if (WidgetsetInfo.class.isAssignableFrom(c)) { + return (Class<WidgetsetInfo>) c; + } + } catch (ClassNotFoundException e) { + // ClassNotFound is a normal case + } + return null; + } + + private WidgetsetInfo getWidgetsetClassInfo() { + Class<WidgetsetInfo> cls = findWidgetsetClass(); + if (cls != null) { + try { + return cls.newInstance(); + } catch (InstantiationException e) { + getLogger().log( + Level.INFO, + "Unexpected trying to instantiate class " + + cls.getName(), e); + } catch (IllegalAccessException e) { + getLogger() + .log(Level.INFO, + "Unexpected trying to access class " + + cls.getName(), e); } } return null; @@ -159,12 +225,12 @@ public abstract class UIProvider implements Serializable { * Whenever a preserved UI is reused, its * {@link UI#refresh(com.vaadin.server.VaadinRequest) refresh} method is * invoked by the framework first. - * - * + * + * * @param event * the UI create event with information about the UI and the * current request. - * + * * @return <code>true</code>if the same UI instance should be reused e.g. * when the browser window is refreshed. */ @@ -190,13 +256,13 @@ public abstract class UIProvider implements Serializable { * <p> * The default implementation uses the @{@link Push} annotation if it's * defined for the UI class. - * + * * @param event * the UI create event with information about the UI and the * current request. * @return the push mode to use, or <code>null</code> if the default push * mode should be used - * + * */ public PushMode getPushMode(UICreateEvent event) { Push push = getAnnotationFor(event.getUIClass(), Push.class); @@ -213,7 +279,7 @@ public abstract class UIProvider implements Serializable { * <p> * The default implementation uses the @{@link Push} annotation if it's * defined for the UI class. - * + * * @param event * the UI create event with information about the UI and the * current request. @@ -229,4 +295,8 @@ public abstract class UIProvider implements Serializable { } } + private static final Logger getLogger() { + return Logger.getLogger(UIProvider.class.getName()); + } + } diff --git a/server/src/main/java/com/vaadin/server/WidgetsetInfo.java b/server/src/main/java/com/vaadin/server/WidgetsetInfo.java new file mode 100644 index 0000000000..fd62b0b309 --- /dev/null +++ b/server/src/main/java/com/vaadin/server/WidgetsetInfo.java @@ -0,0 +1,57 @@ +/* + * 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.server; + +import java.io.Serializable; + +/** + * An interface describing the widgetset that the client should try to load. + * <p> + * In addition to explicit use within the framework, adding a class called + * AppWidgetset implementing this interface in the default package will + * configure the widgetset to use unless the user has explicitly selected a + * different widgetset. See {@link BootstrapHandler} and {@link UIProvider} for + * more information. + * + * @since 7.7 + */ +public interface WidgetsetInfo extends Serializable { + + /** + * Returns the name of the widgetset to use. + * + * @return widgetset name + */ + public String getWidgetsetName(); + + /** + * Returns the widgetset URL. Can be null for local widgetsets at default + * location. + * + * @return widgetset URL or null for client generated URL + */ + public String getWidgetsetUrl(); + + /** + * If cdn is true, the client side should wait if it didn't manage to load + * the widgetset, as it might still be compiling. + * + * @return true to wait and retry if the widgetset could not be loaded + */ + public boolean isCdn(); + +} diff --git a/server/src/main/java/com/vaadin/server/WidgetsetInfoImpl.java b/server/src/main/java/com/vaadin/server/WidgetsetInfoImpl.java new file mode 100644 index 0000000000..a887b0b7ac --- /dev/null +++ b/server/src/main/java/com/vaadin/server/WidgetsetInfoImpl.java @@ -0,0 +1,65 @@ +/* + * 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.server; + +/** + * Default implementation of {@link WidgetsetInfo} that is used for internal + * communication between the parts of the framework. + * <p> + * Class needs to be static so that it can be easily used in e.g. + * BootstrapHandler. + * <p> + * This class is intended primarily for internal use. It is recommended to + * implement WidgetsetInfo directly rather than extending or using this + * class outside the framework, and this class is subject to changes. + * + * @since 7.7 + */ +class WidgetsetInfoImpl implements WidgetsetInfo { + + private final boolean cdn; + private final String widgetsetUrl; + private final String widgetsetName; + + public WidgetsetInfoImpl(boolean cdn, String widgetsetUrl, + String widgetsetName) { + + this.cdn = cdn; + this.widgetsetUrl = widgetsetUrl; + this.widgetsetName = widgetsetName; + } + + public WidgetsetInfoImpl(String widgetsetName) { + this(false, null, widgetsetName); + } + + @Override + public boolean isCdn() { + return cdn; + } + + @Override + public String getWidgetsetUrl() { + return widgetsetUrl; + } + + @Override + public String getWidgetsetName() { + return widgetsetName; + } + +}
\ No newline at end of file diff --git a/server/src/main/resources/VAADIN/vaadinBootstrap.js b/server/src/main/resources/VAADIN/vaadinBootstrap.js index 89514dbcc2..a33884eee3 100644 --- a/server/src/main/resources/VAADIN/vaadinBootstrap.js +++ b/server/src/main/resources/VAADIN/vaadinBootstrap.js @@ -41,14 +41,23 @@ return (typeof window[className]) != "undefined"; }; - var loadWidgetset = function(url, widgetset) { + var loadWidgetset = function(url, widgetset, ready) { if (widgetsets[widgetset]) { return; } log("load widgetset", url, widgetset); setTimeout(function() { if (!isWidgetsetLoaded(widgetset)) { - alert("Failed to load the widgetset: " + url); + if (ready) { + alert("Failed to load the widgetset: " + url); + } else { + if (window.confirm("Failed to load the widgetset. If using CDN for the widgetset, it is possible that compiling it takes up to a few minutes. Would you like to try again?")) { + window[widgetset] = undefined; + window.location.reload(false); + } else { + alert("Failed to load the widgetset: " + url); + } + } } }, 15000); @@ -216,7 +225,8 @@ if (!widgetsetUrl) { widgetsetUrl = vaadinDir + 'widgetsets/' + widgetset + "/" + widgetset + ".nocache.js?" + new Date().getTime(); } - loadWidgetset(widgetsetUrl, widgetset); + var widgetsetReady = getConfig('widgetsetReady'); + loadWidgetset(widgetsetUrl, widgetset, widgetsetReady); if (getConfig('uidl') === undefined) { if (mayDefer) { |