Skip to content

Commit 2d330c5

Browse files
authored
fix: Not write UIDL sync message when RPC handling throws (#215)
Liferay container is so strict regards ServletResponse::setContentLength comparing to normal servlet containers and Apache Pluto portal, thus the PortalUidlRequestHandler works incorrectly in case of exception in the server-side listener: it writes the error meta info JSON into response, then appends sync UIDL JSON message, then calls setContentLength with the length of the sync UIDL message. Which in turn leads to an unpredictable JSON sent to client. When this change is applied, UidlRequestHandler doesn't write sync UIDL message to the response once the RPC handler execution throws, but instead sends only an error message to the client. No IT tests added, because this is covered by LiferayErrorHandlingIT being added afterwards within Liferay tests PR. Fixes: #213
1 parent f1a96b0 commit 2d330c5

File tree

4 files changed

+152
-2
lines changed

4 files changed

+152
-2
lines changed

vaadin-portlet/src/main/java/com/vaadin/flow/portal/DefaultPortletErrorHandler.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* client-side, displaying the errors on the portlet which caused the exception.
1414
*/
1515
public class DefaultPortletErrorHandler implements ErrorHandler {
16+
static final String ERROR_ATTRIBUTE_NAME =
17+
DefaultPortletErrorHandler.class.getName() + ".error.thrown";
1618
private static final Logger logger = LoggerFactory
1719
.getLogger(DefaultPortletErrorHandler.class);
1820

@@ -36,6 +38,11 @@ public void error(ErrorEvent event) {
3638
event.getThrowable().getMessage(),
3739
getCauseString(event.getThrowable()), null,
3840
getQuerySelector(response)));
41+
// Liferay related: tells UIDL handler not to write the sync
42+
// UIDL, because it corrupts RPC response in case of exception
43+
// see https://github.com/vaadin/portlet/issues/213
44+
VaadinPortletRequest.getCurrentPortletRequest().setAttribute(
45+
ERROR_ATTRIBUTE_NAME, Boolean.TRUE);
3946
} catch (Exception e) {
4047
logger.error("Failed to send critical notification!", e);
4148
}
@@ -47,7 +54,7 @@ public void error(ErrorEvent event) {
4754
* error box should be added. If the element found by the
4855
* {@code querySelector} has a shadow root, the error will be added into the
4956
* shadow instead.
50-
*
57+
*
5158
* @param response
5259
* the portlet response used to write the error to the
5360
* client-side

vaadin-portlet/src/main/java/com/vaadin/flow/portal/PortletUidlRequestHandler.java

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,16 @@
1515
*/
1616
package com.vaadin.flow.portal;
1717

18+
import javax.servlet.http.Cookie;
19+
import java.io.IOException;
20+
import java.io.OutputStream;
21+
import java.io.PrintWriter;
22+
import java.io.Serializable;
23+
1824
import com.vaadin.flow.server.VaadinRequest;
25+
import com.vaadin.flow.server.VaadinResponse;
26+
import com.vaadin.flow.server.VaadinService;
27+
import com.vaadin.flow.server.VaadinSession;
1928
import com.vaadin.flow.server.communication.UidlRequestHandler;
2029

2130
/**
@@ -30,4 +39,132 @@ class PortletUidlRequestHandler extends UidlRequestHandler {
3039
protected boolean canHandleRequest(VaadinRequest request) {
3140
return "/uidl".equals(request.getPathInfo());
3241
}
42+
43+
@Override
44+
public boolean synchronizedHandleRequest(VaadinSession session, VaadinRequest request,
45+
VaadinResponse response) throws IOException {
46+
VaadinResponseWrapper vaadinResponseWrapper =
47+
new VaadinResponseWrapper(request, response);
48+
return super.synchronizedHandleRequest(session, request, vaadinResponseWrapper);
49+
}
50+
51+
/**
52+
* Wraps the portlet response to stub the writing actions so as to not
53+
* write the UIDL sync message, when the error occurs during RPC handling.
54+
* Specific to Liferay.
55+
* See https://github.com/vaadin/portlet/issues/213
56+
*/
57+
private static class VaadinResponseWrapper implements VaadinResponse {
58+
59+
private final VaadinPortletRequest request;
60+
private final VaadinPortletResponse delegate;
61+
62+
public VaadinResponseWrapper(VaadinRequest vaadinRequest,
63+
VaadinResponse vaadinResponse) {
64+
if (!(vaadinResponse instanceof VaadinPortletResponse &&
65+
vaadinRequest instanceof VaadinPortletRequest)) {
66+
throw new IllegalArgumentException(
67+
"Portlet request/response expected, make sure you run the application in the portal container");
68+
}
69+
request = (VaadinPortletRequest) vaadinRequest;
70+
delegate = (VaadinPortletResponse) vaadinResponse;
71+
}
72+
73+
@Override
74+
public void setStatus(int statusCode) {
75+
delegate.setStatus(statusCode);
76+
}
77+
78+
@Override
79+
public void setContentType(String contentType) {
80+
delegate.setContentType(contentType);
81+
}
82+
83+
@Override
84+
public void setHeader(String name, String value) {
85+
delegate.setHeader(name, value);
86+
}
87+
88+
@Override
89+
public void setDateHeader(String name, long timestamp) {
90+
delegate.setDateHeader(name, timestamp);
91+
}
92+
93+
@Override
94+
public OutputStream getOutputStream() throws IOException {
95+
if (noError()) {
96+
return delegate.getOutputStream();
97+
} else {
98+
return new OutputStreamWrapper();
99+
}
100+
}
101+
102+
@Override
103+
public PrintWriter getWriter() throws IOException {
104+
return null;
105+
}
106+
107+
@Override
108+
public void setCacheTime(long milliseconds) {
109+
delegate.setCacheTime(milliseconds);
110+
}
111+
112+
@Override
113+
public void sendError(int errorCode, String message) throws IOException {
114+
delegate.sendError(errorCode, message);
115+
}
116+
117+
@Override
118+
public VaadinService getService() {
119+
return delegate.getService();
120+
}
121+
122+
@Override
123+
public void addCookie(Cookie cookie) {
124+
delegate.addCookie(cookie);
125+
}
126+
127+
@Override
128+
public void setContentLength(int len) {
129+
if (noError()) {
130+
delegate.setContentLength(len);
131+
}
132+
}
133+
134+
@Override
135+
public void setNoCacheHeaders() {
136+
delegate.setNoCacheHeaders();
137+
}
138+
139+
private boolean noError() {
140+
return request.getPortletRequest().getAttribute(
141+
DefaultPortletErrorHandler.ERROR_ATTRIBUTE_NAME) == null;
142+
}
143+
}
144+
145+
/**
146+
* Null output stream implementation.
147+
*/
148+
private static class OutputStreamWrapper extends OutputStream
149+
implements Serializable {
150+
private volatile boolean closed;
151+
152+
private void ensureOpen() throws IOException {
153+
if (this.closed) {
154+
throw new IOException("Stream closed");
155+
}
156+
}
157+
158+
public void write(int b) throws IOException {
159+
this.ensureOpen();
160+
}
161+
162+
public void write(byte[] b, int off, int len) throws IOException {
163+
this.ensureOpen();
164+
}
165+
166+
public void close() {
167+
this.closed = true;
168+
}
169+
}
33170
}

vaadin-portlet/src/main/java/com/vaadin/flow/portal/VaadinPortletService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import com.vaadin.flow.server.WrappedSession;
5151
import com.vaadin.flow.server.communication.HeartbeatHandler;
5252
import com.vaadin.flow.server.communication.StreamRequestHandler;
53+
import com.vaadin.flow.server.communication.UidlRequestHandler;
5354
import com.vaadin.flow.server.startup.PortletApplicationRouteRegistryUtil;
5455
import com.vaadin.flow.shared.Registration;
5556
import com.vaadin.flow.theme.AbstractTheme;
@@ -117,6 +118,9 @@ protected List<RequestHandler> createRequestHandlers()
117118
handlers.add(new PortletBootstrapHandler());
118119
handlers.add(new PortletWebComponentProvider());
119120
handlers.add(new PortletWebComponentBootstrapHandler());
121+
122+
handlers.removeIf(
123+
requestHandler -> requestHandler instanceof UidlRequestHandler);
120124
handlers.add(new PortletUidlRequestHandler());
121125

122126
handlers.removeIf(

vaadin-portlet/src/test/java/com/vaadin/flow/portal/PortletClassesSerializableTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ protected Stream<String> getExcludedPatterns() {
3333
"PortletStreamReceiverHandler\\$StreamRequestContext",
3434
"com\\.vaadin\\.flow\\.portal\\.VaadinHttpAndPortletRequest",
3535
"com\\.vaadin\\.flow\\.portal\\.VaadinHttpPortletRequest",
36-
"com\\.vaadin\\.flow\\.portal\\.VaadinLiferayRequest"
36+
"com\\.vaadin\\.flow\\.portal\\.VaadinLiferayRequest",
37+
"com\\.vaadin\\.flow\\.portal\\." +
38+
"PortletUidlRequestHandler\\$VaadinResponseWrapper"
3739
);
3840
}
3941

0 commit comments

Comments
 (0)