Skip to content

Commit 7af11a8

Browse files
authored
feat: Add bean serialization support (#22378)
Implements bean-only serialization using Jackson. Beans are serialized on server and handled as standard JS objects on client.
1 parent 1fc4200 commit 7af11a8

File tree

22 files changed

+407
-182
lines changed

22 files changed

+407
-182
lines changed

flow-polymer-template/src/test/java/com/vaadin/flow/component/polymertemplate/PolymerTemplateTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public class PolymerTemplateTest extends HasCurrentService {
6969
private DeploymentConfiguration configuration;
7070

7171
private List<Object> executionOrder = new ArrayList<>();
72-
private List<Serializable[]> executionParams = new ArrayList<>();
72+
private List<Object[]> executionParams = new ArrayList<>();
7373

7474
// Field to prevent current instance from being garbage collected
7575
private UI ui;
@@ -477,7 +477,7 @@ public void setUp() throws SecurityException, IllegalArgumentException {
477477

478478
@Override
479479
public PendingJavaScriptResult executeJs(String expression,
480-
Serializable... parameters) {
480+
Object... parameters) {
481481
executionOrder.add(expression);
482482
executionParams.add(parameters);
483483
return null;
@@ -963,7 +963,7 @@ public void initModel_requestPopulateModel_onlyUnsetPropertiesAreSent() {
963963
Assert.assertEquals("this.populateModelProperties($0, $1)",
964964
executionOrder.get(1));
965965

966-
Serializable[] params = executionParams.get(1);
966+
Object[] params = executionParams.get(1);
967967
ArrayNode properties = (ArrayNode) params[1];
968968
Assert.assertEquals(1, properties.size());
969969
Assert.assertEquals("title", properties.get(0).asText());
@@ -982,7 +982,7 @@ public void initModel_sendUpdatableProperties() {
982982
Assert.assertEquals("this.registerUpdatableModelProperties($0, $1)",
983983
executionOrder.get(0));
984984

985-
Serializable[] params = executionParams.get(0);
985+
Object[] params = executionParams.get(0);
986986
ArrayNode properties = (ArrayNode) params[1];
987987
Assert.assertEquals(2, properties.size());
988988

flow-server/src/main/java/com/vaadin/flow/component/internal/UIInternals.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,12 @@ public class UIInternals implements Serializable {
109109
private static Set<String> bundledImports = BundleUtils.loadBundleImports();
110110

111111
/**
112-
* A {@link Page#executeJs(String, Serializable...)} invocation that has not
113-
* yet been sent to the client.
112+
* A {@link Page#executeJs(String, Object...)} invocation that has not yet
113+
* been sent to the client.
114114
*/
115115
public static class JavaScriptInvocation implements Serializable {
116116
private final String expression;
117-
private final List<Serializable> parameters = new ArrayList<>();
117+
private final List<Object> parameters = new ArrayList<>();
118118

119119
/**
120120
* Creates a new invocation.
@@ -124,8 +124,7 @@ public static class JavaScriptInvocation implements Serializable {
124124
* @param parameters
125125
* a list of parameters to use when invoking the script
126126
*/
127-
public JavaScriptInvocation(String expression,
128-
Serializable... parameters) {
127+
public JavaScriptInvocation(String expression, Object... parameters) {
129128
/*
130129
* To ensure attached elements are actually attached, the parameters
131130
* won't be serialized until the phase the UIDL message is created.

flow-server/src/main/java/com/vaadin/flow/component/page/ExtendedClientDetails.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727
* <p>
2828
* Please note that all information is fetched only once, and <em>not updated
2929
* automatically</em>. To retrieve updated values, you can execute JS with
30-
* {@link Page#executeJs(String, Serializable...)} and get the current value
31-
* back.
30+
* {@link Page#executeJs(String, Object...)} and get the current value back.
3231
*
3332
* @author Vaadin Ltd
3433
* @since 2.0

flow-server/src/main/java/com/vaadin/flow/component/page/Page.java

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -270,17 +270,16 @@ public void addDynamicImport(String expression) {
270270
* return value will be ignored.
271271
* <p>
272272
* The given parameters will be available to the expression as variables
273-
* named <code>$0</code>, <code>$1</code>, and so on. Supported parameter
274-
* types are:
273+
* named <code>$0</code>, <code>$1</code>, and so on. All types supported by
274+
* Jackson for JSON serialization are supported as parameters. Special
275+
* cases:
275276
* <ul>
276-
* <li>{@link String}
277-
* <li>{@link Integer}
278-
* <li>{@link Double}
279-
* <li>{@link Boolean}
280-
* <li>{@link tools.jackson.databind.node.BaseJsonNode}
281-
* <li>{@link Element} (will be sent as <code>null</code> if the server-side
282-
* element instance is not attached when the invocation is sent to the
283-
* client)
277+
* <li>{@link Element} (will be sent as a DOM element reference to the
278+
* browser if the server-side element instance is attached when the
279+
* invocation is sent to the client, or as <code>null</code> if not
280+
* attached)
281+
* <li>{@link tools.jackson.databind.node.BaseJsonNode} (sent as-is without
282+
* additional wrapping)
284283
* </ul>
285284
* Note that the parameter variables can only be used in contexts where a
286285
* JavaScript variable can be used. You should for instance do
@@ -296,7 +295,7 @@ public void addDynamicImport(String expression) {
296295
* the expression
297296
*/
298297
public PendingJavaScriptResult executeJs(String expression,
299-
Serializable... parameters) {
298+
Object... parameters) {
300299
JavaScriptInvocation invocation = new JavaScriptInvocation(expression,
301300
parameters);
302301

@@ -308,6 +307,24 @@ public PendingJavaScriptResult executeJs(String expression,
308307
return execution;
309308
}
310309

310+
/**
311+
* Executes the given JavaScript expression in the browser.
312+
*
313+
* @deprecated Use {@link #executeJs(String, Object...)} instead. This
314+
* method exists only for binary compatibility.
315+
* @param expression
316+
* the JavaScript expression to execute
317+
* @param parameters
318+
* parameters to pass to the expression
319+
* @return a pending result that can be used to get a value returned from
320+
* the expression
321+
*/
322+
@Deprecated
323+
public PendingJavaScriptResult executeJs(String expression,
324+
Serializable[] parameters) {
325+
return executeJs(expression, (Object[]) parameters);
326+
}
327+
311328
/**
312329
* Gets a representation of <code>window.history</code> for this page.
313330
*
@@ -545,7 +562,7 @@ private void handleExtendedClientDetailsResponse(JsonNode json) {
545562
* proxy between the client and the server.
546563
* <p>
547564
* In case you need more control over the execution you can use
548-
* {@link #executeJs(String, Serializable...)} by passing
565+
* {@link #executeJs(String, Object...)} by passing
549566
* {@code return window.location.href}.
550567
* <p>
551568
* <em>NOTE: </em> the URL is not escaped, use {@link URL#toURI()} to escape
@@ -578,7 +595,7 @@ public void fetchCurrentURL(SerializableConsumer<URL> callback) {
578595
* request and passed to the callback.
579596
* <p>
580597
* In case you need more control over the execution you can use
581-
* {@link #executeJs(String, Serializable...)} by passing
598+
* {@link #executeJs(String, Object...)} by passing
582599
* {@code return document.dir}.
583600
*
584601
* @param callback

flow-server/src/main/java/com/vaadin/flow/dom/Element.java

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,26 +1380,26 @@ public <T extends Component> T as(Class<T> componentType) {
13801380
* registered, the return value will be ignored.
13811381
* <p>
13821382
* The function will be called after all pending DOM updates have completed,
1383-
* at the same time that {@link Page#executeJs(String, Serializable...)}
1384-
* calls are invoked.
1383+
* at the same time that {@link Page#executeJs(String, Object...)} calls are
1384+
* invoked.
13851385
* <p>
13861386
* If the element is not attached or not visible, the function call will be
13871387
* deferred until the element is attached and visible.
13881388
*
1389-
* @see JsonCodec JsonCodec for supported argument types
1390-
*
13911389
* @param functionName
13921390
* the name of the function to call, may contain dots to indicate
13931391
* a function on a property.
13941392
* @param arguments
1395-
* the arguments to pass to the function. Must be of a type
1396-
* supported by the communication mechanism, as defined by
1397-
* {@link JsonCodec}
1393+
* the arguments to pass to the function. All types supported by
1394+
* Jackson for JSON serialization are supported. Special cases:
1395+
* {@link Element} instances (will be sent as DOM element
1396+
* references to the browser if attached when invoked, or as
1397+
* <code>null</code> if not attached).
13981398
* @return a pending result that can be used to get a return value from the
13991399
* execution
14001400
*/
14011401
public PendingJavaScriptResult callJsFunction(String functionName,
1402-
Serializable... arguments) {
1402+
Object... arguments) {
14031403
assert functionName != null;
14041404
assert !functionName.startsWith(".")
14051405
: "Function name should not start with a dot";
@@ -1421,6 +1421,25 @@ public PendingJavaScriptResult callJsFunction(String functionName,
14211421
+ paramPlaceholderString + ")", jsParameters);
14221422
}
14231423

1424+
/**
1425+
* Calls the given JavaScript function with this element as
1426+
* <code>this</code> and the given arguments.
1427+
*
1428+
* @deprecated Use {@link #callJsFunction(String, Object...)} instead. This
1429+
* method exists only for binary compatibility.
1430+
* @param functionName
1431+
* the name of the function to call
1432+
* @param arguments
1433+
* the arguments to pass to the function
1434+
* @return a pending result that can be used to get a return value from the
1435+
* execution
1436+
*/
1437+
@Deprecated
1438+
public PendingJavaScriptResult callJsFunction(String functionName,
1439+
Serializable[] arguments) {
1440+
return callJsFunction(functionName, (Object[]) arguments);
1441+
}
1442+
14241443
// When updating JavaDocs here, keep in sync with Page.executeJavaScript
14251444
/**
14261445
* Asynchronously runs the given JavaScript expression in the browser in the
@@ -1436,17 +1455,15 @@ public PendingJavaScriptResult callJsFunction(String functionName,
14361455
* <p>
14371456
* This element will be available to the expression as <code>this</code>.
14381457
* The given parameters will be available as variables named
1439-
* <code>$0</code>, <code>$1</code>, and so on. Supported parameter types
1440-
* are:
1458+
* <code>$0</code>, <code>$1</code>, and so on. All types supported by
1459+
* Jackson for JSON serialization are supported as parameters. Special
1460+
* cases:
14411461
* <ul>
1442-
* <li>{@link String}
1443-
* <li>{@link Integer}
1444-
* <li>{@link Double}
1445-
* <li>{@link Boolean}
1446-
* <li>{@link BaseJsonNode}
1447-
* <li>{@link Element} (will be sent as <code>null</code> if the server-side
1448-
* element instance is not attached when the invocation is sent to the
1449-
* client)
1462+
* <li>{@link Element} (will be sent as a DOM element reference to the
1463+
* browser if the server-side element instance is attached when the
1464+
* invocation is sent to the client, or as <code>null</code> if not
1465+
* attached)
1466+
* <li>{@link BaseJsonNode} (sent as-is without additional wrapping)
14501467
* </ul>
14511468
* Note that the parameter variables can only be used in contexts where a
14521469
* JavaScript variable can be used. You should for instance do
@@ -1465,15 +1482,15 @@ public PendingJavaScriptResult callJsFunction(String functionName,
14651482
* the expression
14661483
*/
14671484
public PendingJavaScriptResult executeJs(String expression,
1468-
Serializable... parameters) {
1485+
Object... parameters) {
14691486

14701487
// Add "this" as the last parameter
1471-
Serializable[] wrappedParameters;
1488+
Object[] wrappedParameters;
14721489
if (parameters.length == 0) {
1473-
wrappedParameters = new Serializable[] { this };
1490+
wrappedParameters = new Object[] { this };
14741491
} else {
1475-
wrappedParameters = Arrays.copyOf(parameters, parameters.length + 1,
1476-
Serializable[].class);
1492+
wrappedParameters = Arrays.copyOf(parameters,
1493+
parameters.length + 1);
14771494
wrappedParameters[parameters.length] = this;
14781495
}
14791496

@@ -1485,8 +1502,27 @@ public PendingJavaScriptResult executeJs(String expression,
14851502
wrappedParameters);
14861503
}
14871504

1505+
/**
1506+
* Asynchronously runs the given JavaScript expression in the browser in the
1507+
* context of this element.
1508+
*
1509+
* @deprecated Use {@link #executeJs(String, Object...)} instead. This
1510+
* method exists only for binary compatibility.
1511+
* @param expression
1512+
* the JavaScript expression to invoke
1513+
* @param parameters
1514+
* parameters to pass to the expression
1515+
* @return a pending result that can be used to get a value returned from
1516+
* the expression
1517+
*/
1518+
@Deprecated
1519+
public PendingJavaScriptResult executeJs(String expression,
1520+
Serializable[] parameters) {
1521+
return executeJs(expression, (Object[]) parameters);
1522+
}
1523+
14881524
private PendingJavaScriptResult scheduleJavaScriptInvocation(
1489-
String expression, Serializable[] parameters) {
1525+
String expression, Object[] parameters) {
14901526
StateNode node = getNode();
14911527

14921528
JavaScriptInvocation invocation = new JavaScriptInvocation(expression,

flow-server/src/main/java/com/vaadin/flow/internal/JacksonCodec.java

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -83,21 +83,26 @@ private JacksonCodec() {
8383
* @return the value encoded as JSON
8484
*/
8585
public static JsonNode encodeWithTypeInfo(Object value) {
86-
assert value == null || canEncodeWithTypeInfo(value.getClass());
8786

88-
if (value instanceof Component) {
87+
if (value == null) {
88+
return encodeWithoutTypeInfo(value);
89+
} else if (value instanceof Component) {
8990
return encodeNode(((Component) value).getElement());
9091
} else if (value instanceof Node<?>) {
9192
return encodeNode((Node<?>) value);
9293
} else if (value instanceof ReturnChannelRegistration) {
9394
return encodeReturnChannel((ReturnChannelRegistration) value);
94-
} else {
95+
} else if (canEncodeWithoutTypeInfo(value.getClass())) {
9596
JsonNode encoded = encodeWithoutTypeInfo(value);
9697
if (encoded.getNodeType() == JsonNodeType.ARRAY) {
9798
// Must "escape" arrays
9899
encoded = wrapComplexValue(ARRAY_TYPE, encoded);
99100
}
100101
return encoded;
102+
} else {
103+
// Encode as bean using Jackson serialization - send directly as
104+
// JSON
105+
return JacksonUtils.getMapper().valueToTree(value);
101106
}
102107
}
103108

@@ -142,23 +147,6 @@ public static boolean canEncodeWithoutTypeInfo(Class<?> type) {
142147
|| JsonNode.class.isAssignableFrom(type);
143148
}
144149

145-
/**
146-
* Helper for checking whether the type is supported by
147-
* {@link #encodeWithTypeInfo(Object)}. Supported values types are
148-
* {@link Node}, {@link Component}, {@link ReturnChannelRegistration} and
149-
* anything accepted by {@link #canEncodeWithoutTypeInfo(Class)}.
150-
*
151-
* @param type
152-
* the type to check
153-
* @return whether the type can be encoded
154-
*/
155-
public static boolean canEncodeWithTypeInfo(Class<?> type) {
156-
return canEncodeWithoutTypeInfo(type)
157-
|| Node.class.isAssignableFrom(type)
158-
|| Component.class.isAssignableFrom(type)
159-
|| ReturnChannelRegistration.class.isAssignableFrom(type);
160-
}
161-
162150
/**
163151
* Encodes a "primitive" value or a constant pool reference to JSON. This
164152
* methods supports {@link ConstantPoolKey} in addition to the types
@@ -213,7 +201,6 @@ assert canEncodeWithoutTypeInfo(value.getClass())
213201
} else if (JsonNode.class.isAssignableFrom(type)) {
214202
return (JsonNode) value;
215203
}
216-
assert !canEncodeWithoutTypeInfo(type);
217204
throw new IllegalArgumentException(
218205
"Can't encode " + value.getClass() + " to json");
219206
}
@@ -279,7 +266,6 @@ public static <T> T decodeAs(JsonNode json, Class<T> type) {
279266
} else if (JsonNode.class.isAssignableFrom(type)) {
280267
return type.cast(json);
281268
} else {
282-
assert !canEncodeWithoutTypeInfo(type);
283269
throw new IllegalArgumentException(
284270
"Unknown type " + type.getName());
285271
}

0 commit comments

Comments
 (0)