Browse Source

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
tags/7.7.0.alpha2
Markus Koivisto 8 years ago
parent
commit
7787eee10d

+ 1
- 0
all/src/main/templates/release-notes.html View File

@@ -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>

+ 38
- 20
server/src/main/java/com/vaadin/server/BootstrapHandler.java View File

@@ -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
*/

+ 90
- 20
server/src/main/java/com/vaadin/server/UIProvider.java View File

@@ -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());
}

}

+ 57
- 0
server/src/main/java/com/vaadin/server/WidgetsetInfo.java View File

@@ -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();

}

+ 65
- 0
server/src/main/java/com/vaadin/server/WidgetsetInfoImpl.java View File

@@ -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;
}

}

+ 13
- 3
server/src/main/resources/VAADIN/vaadinBootstrap.js View File

@@ -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) {

Loading…
Cancel
Save