aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorTeemu Suo-Anttila <tsuoanttila@users.noreply.github.com>2017-02-22 11:35:19 +0200
committerHenri Sara <henri.sara@gmail.com>2017-02-27 11:25:50 +0200
commitd1e2bf5b57c7ee562f2451476d4e589a306758d9 (patch)
tree8b8f6946e9f08b014eff30bb3867a01a44bef8d8 /server
parente859407eb9b614e241de76cedbfefa9b4acbb682 (diff)
downloadvaadin-framework-d1e2bf5b57c7ee562f2451476d4e589a306758d9.tar.gz
vaadin-framework-d1e2bf5b57c7ee562f2451476d4e589a306758d9.zip
Pick changes from 7.7.7 (#8577)
* Fix java packaging order (#106) Closes vaadin/archetypes#113 * Use proper UTF-8 encoding for Content-Disposition filenames (#19527) (#6607) * Enable changing the backing bean for BeanItem (#4302) (#77) When storing a bean to the database, you typically get a new and updated bean instance back. By allowing to change the bean instance, we make it possible to just update the single BeanItem instance which can be used in many places. * Make AtmospherePushConnection methods public (#7973) There is no sensible way to use a custom version of APC, so protected access does not help in any way to access the underlying resource and/or connected UI. * Use correct indexes in multiselect checkboxes after removing rows (#8072) Fixes #8011 * Fix removal of hidden Grid columns (#8071) Fixes #8018 * Call error handler for exceptions in UI.init() (#8055) Fixes #4995 * Render font icon correctly on the 'more' menu item (#8126) * Render font icon correctly on the 'more' menu item Fixes #8125 * Reopen Grid details on attach, fixes #8015 (#8074) Fixes #8015 * Fix broken Grid tests after picking changes from 7.7.7 Removed duplicate setDetailsVisible calls from onDetach * Correctly detach components in merged cells when a static row is removed (#8142) Fixes #8140
Diffstat (limited to 'server')
-rw-r--r--server/pom.xml2
-rw-r--r--server/src/main/java/com/vaadin/server/DownloadStream.java15
-rw-r--r--server/src/main/java/com/vaadin/server/communication/AtmospherePushConnection.java14
-rw-r--r--server/src/main/java/com/vaadin/server/communication/UIInitHandler.java13
-rw-r--r--server/src/main/java/com/vaadin/util/EncodeUtil.java71
-rw-r--r--server/src/test/java/com/vaadin/server/DownloadStreamTest.java8
-rw-r--r--server/src/test/java/com/vaadin/server/communication/ServletUIInitHandlerTest.java188
-rw-r--r--server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java2
-rw-r--r--server/src/test/java/com/vaadin/util/EncodeUtilTest.java32
9 files changed, 324 insertions, 21 deletions
diff --git a/server/pom.xml b/server/pom.xml
index b8566f2b39..7139096e66 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -165,7 +165,7 @@
<execution>
<id>copy-sources</id>
<!-- here the phase you need -->
- <phase>prepare-package</phase>
+ <phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
diff --git a/server/src/main/java/com/vaadin/server/DownloadStream.java b/server/src/main/java/com/vaadin/server/DownloadStream.java
index a0a1334135..b52b2e83a4 100644
--- a/server/src/main/java/com/vaadin/server/DownloadStream.java
+++ b/server/src/main/java/com/vaadin/server/DownloadStream.java
@@ -20,14 +20,14 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
+import com.vaadin.util.EncodeUtil;
+
/**
* Downloadable stream.
* <p>
@@ -329,13 +329,10 @@ public class DownloadStream implements Serializable {
* @return A value for inclusion in a Content-Disposition header
*/
public static String getContentDispositionFilename(String filename) {
- try {
- String encodedFilename = URLEncoder.encode(filename, "UTF-8");
- return String.format("filename=\"%s\"; filename*=utf-8''%s",
- encodedFilename, encodedFilename);
- } catch (UnsupportedEncodingException e) {
- return null;
- }
+ String encodedFilename = EncodeUtil.rfc5987Encode(filename);
+
+ return String.format("filename=\"%s\"; filename*=utf-8''%s",
+ encodedFilename, encodedFilename);
}
/**
diff --git a/server/src/main/java/com/vaadin/server/communication/AtmospherePushConnection.java b/server/src/main/java/com/vaadin/server/communication/AtmospherePushConnection.java
index 92d675001b..b5a6f55e4c 100644
--- a/server/src/main/java/com/vaadin/server/communication/AtmospherePushConnection.java
+++ b/server/src/main/java/com/vaadin/server/communication/AtmospherePushConnection.java
@@ -258,17 +258,21 @@ public class AtmospherePushConnection implements PushConnection {
}
/**
- * @return the UI associated with this connection.
+ * Gets the UI this push connection is associated with.
+ *
+ * @return the UI associated with this connection
*/
- protected UI getUI() {
+ public UI getUI() {
return ui;
}
/**
- * @return The AtmosphereResource associated with this connection or null if
- * connection not open.
+ * Gets the atmosphere resource associated with this connection.
+ *
+ * @return The AtmosphereResource associated with this connection or
+ * <code>null</code> if the connection is not open.
*/
- protected AtmosphereResource getResource() {
+ public AtmosphereResource getResource() {
return resource;
}
diff --git a/server/src/main/java/com/vaadin/server/communication/UIInitHandler.java b/server/src/main/java/com/vaadin/server/communication/UIInitHandler.java
index 09d2b0aa79..c0c6850d32 100644
--- a/server/src/main/java/com/vaadin/server/communication/UIInitHandler.java
+++ b/server/src/main/java/com/vaadin/server/communication/UIInitHandler.java
@@ -211,10 +211,17 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler {
// Set thread local here so it is available in init
UI.setCurrent(ui);
- ui.doInit(request, uiId.intValue(), embedId);
-
+ Exception initException = null;
+ try {
+ ui.doInit(request, uiId.intValue(), embedId);
+ } catch (Exception e) {
+ initException = e;
+ }
session.addUI(ui);
-
+ if (initException != null) {
+ ui.getSession().getCommunicationManager()
+ .handleConnectorRelatedException(ui, initException);
+ }
// Warn if the window can't be preserved
if (embedId == null
&& vaadinService.preserveUIOnRefresh(provider, event)) {
diff --git a/server/src/main/java/com/vaadin/util/EncodeUtil.java b/server/src/main/java/com/vaadin/util/EncodeUtil.java
new file mode 100644
index 0000000000..eae0890542
--- /dev/null
+++ b/server/src/main/java/com/vaadin/util/EncodeUtil.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2000-2016 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.util;
+
+import java.nio.charset.Charset;
+
+/**
+ * Utilities related to various encoding schemes.
+ *
+ * @author Vaadin Ltd
+ * @since
+ */
+public final class EncodeUtil {
+ private static final Charset UTF8 = Charset.forName("UTF-8");
+
+ private EncodeUtil() {
+ // Static utils only
+ }
+
+ /**
+ * Encodes the given string to UTF-8 <code>value-chars</code> as defined in
+ * RFC5987 for use in e.g. the <code>Content-Disposition</code> HTTP header.
+ *
+ * @param value
+ * the string to encode, not <code>null</code>
+ * @return the encoded string
+ */
+ public static String rfc5987Encode(String value) {
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0; i < value.length();) {
+ int cp = value.codePointAt(i);
+ if (cp < 127 && (Character.isLetterOrDigit(cp) || cp == '.')) {
+ builder.append((char) cp);
+ } else {
+ // Create string from a single code point
+ String cpAsString = new String(new int[] { cp }, 0, 1);
+
+ appendHexBytes(builder, cpAsString.getBytes(UTF8));
+ }
+
+ // Advance to the next code point
+ i += Character.charCount(cp);
+ }
+
+ return builder.toString();
+ }
+
+ private static void appendHexBytes(StringBuilder builder, byte[] bytes) {
+ for (byte byteValue : bytes) {
+ // mask with 0xFF to compensate for "negative" values
+ int intValue = byteValue & 0xFF;
+ String hexCode = Integer.toString(intValue, 16);
+ builder.append('%').append(hexCode);
+ }
+ }
+
+}
diff --git a/server/src/test/java/com/vaadin/server/DownloadStreamTest.java b/server/src/test/java/com/vaadin/server/DownloadStreamTest.java
index 81f45f6173..4bb97de79f 100644
--- a/server/src/test/java/com/vaadin/server/DownloadStreamTest.java
+++ b/server/src/test/java/com/vaadin/server/DownloadStreamTest.java
@@ -7,13 +7,16 @@ import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.io.InputStream;
-import java.net.URLEncoder;
import org.junit.Before;
import org.junit.Test;
public class DownloadStreamTest {
- private final String filename = "日本語.png";
+ private String filename = "A å日.png";
+ private String encodedFileName = "A" + "%20" // space
+ + "%c3%a5" // å
+ + "%e6%97%a5" // 日
+ + ".png";
private DownloadStream stream;
@Before
@@ -27,7 +30,6 @@ public class DownloadStreamTest {
stream.writeResponse(mock(VaadinRequest.class), response);
- String encodedFileName = URLEncoder.encode(filename, "utf-8");
verify(response).setHeader(eq(DownloadStream.CONTENT_DISPOSITION),
contains(String.format("filename=\"%s\";", encodedFileName)));
verify(response).setHeader(eq(DownloadStream.CONTENT_DISPOSITION),
diff --git a/server/src/test/java/com/vaadin/server/communication/ServletUIInitHandlerTest.java b/server/src/test/java/com/vaadin/server/communication/ServletUIInitHandlerTest.java
new file mode 100644
index 0000000000..6c61922307
--- /dev/null
+++ b/server/src/test/java/com/vaadin/server/communication/ServletUIInitHandlerTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2000-2016 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.communication;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.vaadin.server.DefaultDeploymentConfiguration;
+import com.vaadin.server.ErrorEvent;
+import com.vaadin.server.ErrorHandler;
+import com.vaadin.server.LegacyCommunicationManager;
+import com.vaadin.server.MockServletConfig;
+import com.vaadin.server.UIClassSelectionEvent;
+import com.vaadin.server.UICreateEvent;
+import com.vaadin.server.UIProvider;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinServlet;
+import com.vaadin.server.VaadinServletRequest;
+import com.vaadin.server.VaadinServletResponse;
+import com.vaadin.server.VaadinServletService;
+import com.vaadin.tests.util.AlwaysLockedVaadinSession;
+import com.vaadin.ui.UI;
+
+public class ServletUIInitHandlerTest {
+
+ public static class CommunicationMock {
+
+ public final UI ui;
+ public final ServletConfig servletConfig;
+ public final VaadinServlet servlet;
+ public final DefaultDeploymentConfiguration deploymentConfiguration;
+ public final VaadinServletService service;
+ public final AlwaysLockedVaadinSession session;
+
+ public CommunicationMock(final UI ui) throws Exception {
+ servletConfig = new MockServletConfig();
+ servlet = new VaadinServlet();
+ servlet.init(servletConfig);
+
+ deploymentConfiguration = new DefaultDeploymentConfiguration(
+ UI.class, new Properties());
+
+ service = new VaadinServletService(servlet,
+ deploymentConfiguration);
+ session = new AlwaysLockedVaadinSession(service);
+ LegacyCommunicationManager communicationManager = new LegacyCommunicationManager(
+ session);
+ session.setCommunicationManager(communicationManager);
+ session.setConfiguration(deploymentConfiguration);
+ session.addUIProvider(new UIProvider() {
+
+ @Override
+ public Class<? extends UI> getUIClass(
+ UIClassSelectionEvent event) {
+ return ui.getClass();
+ }
+
+ @Override
+ public UI createInstance(UICreateEvent event) {
+ return ui;
+ }
+ });
+ this.ui = ui;
+ }
+
+ public VaadinRequest createInitRequest() {
+ return new VaadinServletRequest(
+ Mockito.mock(HttpServletRequest.class), service) {
+ @Override
+ public String getMethod() {
+ return "POST";
+ }
+
+ @Override
+ public String getParameter(String name) {
+ if (UIInitHandler.BROWSER_DETAILS_PARAMETER.equals(name)) {
+ return "1";
+ }
+ return super.getParameter(name);
+ }
+
+ };
+ }
+
+ }
+
+ @Test
+ public void errorHandlerForInitException() throws Exception {
+ final AtomicInteger pre = new AtomicInteger(0);
+ final AtomicInteger errorHandlerCalls = new AtomicInteger(0);
+
+ UI ui = new UI() {
+ @Override
+ protected void init(VaadinRequest request) {
+ pre.incrementAndGet();
+ throw new RuntimeException("Exception produced in init()");
+ }
+ };
+
+ ui.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void error(ErrorEvent event) {
+ errorHandlerCalls.incrementAndGet();
+ }
+ });
+
+ CommunicationMock mock = new CommunicationMock(ui);
+ VaadinRequest initRequest = mock.createInitRequest();
+
+ ServletUIInitHandler servletUIInitHandler = new ServletUIInitHandler();
+ servletUIInitHandler.handleRequest(mock.session, initRequest,
+ new VaadinServletResponse(
+ Mockito.mock(HttpServletResponse.class), mock.service) {
+ @Override
+ public ServletOutputStream getOutputStream()
+ throws IOException {
+ return new ServletOutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ }
+ };
+ }
+ });
+
+ Assert.assertEquals(1, pre.getAndIncrement());
+ Assert.assertEquals(1, errorHandlerCalls.getAndIncrement());
+ Assert.assertEquals(mock.session, ui.getSession());
+ }
+
+ @Test
+ public void initExceptionNoErrorHandler() throws Exception {
+ final AtomicInteger pre = new AtomicInteger(0);
+
+ UI ui = new UI() {
+ @Override
+ protected void init(VaadinRequest request) {
+ pre.incrementAndGet();
+ throw new RuntimeException("Exception produced in init()");
+ }
+ };
+
+ CommunicationMock mock = new CommunicationMock(ui);
+ VaadinRequest initRequest = mock.createInitRequest();
+
+ ServletUIInitHandler servletUIInitHandler = new ServletUIInitHandler();
+ servletUIInitHandler.handleRequest(mock.session, initRequest,
+ new VaadinServletResponse(
+ Mockito.mock(HttpServletResponse.class), mock.service) {
+ @Override
+ public ServletOutputStream getOutputStream()
+ throws IOException {
+ return new ServletOutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ }
+ };
+ }
+ });
+
+ Assert.assertEquals(1, pre.getAndIncrement());
+ // Default error handler only logs the exception
+ Assert.assertEquals(mock.session, ui.getSession());
+ }
+
+}
diff --git a/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java b/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java
index fca87ccbdf..a3375585c7 100644
--- a/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java
+++ b/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java
@@ -74,6 +74,7 @@ public class ClassesSerializableTest {
// interfaces
"com\\.vaadin\\.server\\.LegacyCommunicationManager.*", //
"com\\.vaadin\\.buildhelpers.*", //
+ "com\\.vaadin\\.util\\.EncodeUtil.*", //
"com\\.vaadin\\.util\\.ReflectTools.*", //
"com\\.vaadin\\.data\\.util\\.ReflectTools.*", //
"com\\.vaadin\\.data\\.util\\.JsonUtil.*", //
@@ -235,6 +236,7 @@ public class ClassesSerializableTest {
Assert.fail(
"Serializable not implemented by the following classes and interfaces: "
+ nonSerializableString);
+
}
private static boolean isFunctionalType(Type type) {
diff --git a/server/src/test/java/com/vaadin/util/EncodeUtilTest.java b/server/src/test/java/com/vaadin/util/EncodeUtilTest.java
new file mode 100644
index 0000000000..7bac539968
--- /dev/null
+++ b/server/src/test/java/com/vaadin/util/EncodeUtilTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2016 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.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class EncodeUtilTest {
+ @Test
+ public void rfc5987Encode() {
+ Assert.assertEquals("A", EncodeUtil.rfc5987Encode("A"));
+ Assert.assertEquals("%20", EncodeUtil.rfc5987Encode(" "));
+ Assert.assertEquals("%c3%a5", EncodeUtil.rfc5987Encode("å"));
+ Assert.assertEquals("%e6%97%a5", EncodeUtil.rfc5987Encode("日"));
+
+ Assert.assertEquals("A" + "%20" + "%c3%a5" + "%e6%97%a5",
+ EncodeUtil.rfc5987Encode("A å日"));
+ }
+}