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: If3a7b35e3b25371e08e6d9b504fcda6f66de5119tags/7.7.0.alpha2
@@ -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> |
@@ -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 | |||
*/ |
@@ -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()); | |||
} | |||
} |
@@ -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(); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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) { |