Skip to content

Commit e852ba5

Browse files
authored
feat: WebComponent more Jackson (#21101)
* feat: WebComponent more Jackson Change more parts of webComponent to use Jackson instead of elemental. * Fix review identified issues
1 parent 442b051 commit e852ba5

File tree

7 files changed

+230
-18
lines changed

7 files changed

+230
-18
lines changed

flow-server/src/main/java/com/vaadin/flow/component/WebComponentExporter.java

+37-5
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@
2525
import java.util.Objects;
2626
import java.util.Set;
2727
import java.util.stream.Collectors;
28-
import java.util.stream.Stream;
28+
29+
import com.fasterxml.jackson.databind.JsonNode;
30+
import com.fasterxml.jackson.databind.node.BaseJsonNode;
2931

3032
import com.vaadin.flow.component.webcomponent.PropertyConfiguration;
3133
import com.vaadin.flow.component.webcomponent.WebComponent;
3234
import com.vaadin.flow.component.webcomponent.WebComponentConfiguration;
3335
import com.vaadin.flow.di.Instantiator;
3436
import com.vaadin.flow.dom.Element;
37+
import com.vaadin.flow.internal.JacksonUtils;
3538
import com.vaadin.flow.internal.ReflectTools;
3639
import com.vaadin.flow.server.webcomponent.PropertyConfigurationImpl;
3740
import com.vaadin.flow.server.webcomponent.PropertyData;
@@ -90,8 +93,9 @@ public abstract class WebComponentExporter<C extends Component>
9093
implements Serializable {
9194

9295
private static final List<Class> SUPPORTED_TYPES = Collections
93-
.unmodifiableList(Arrays.asList(Boolean.class, String.class,
94-
Integer.class, Double.class, JsonValue.class));
96+
.unmodifiableList(
97+
Arrays.asList(Boolean.class, String.class, Integer.class,
98+
Double.class, JsonValue.class, JsonNode.class));
9599

96100
private final String tag;
97101
private HashMap<String, PropertyConfigurationImpl<C, ? extends Serializable>> propertyConfigurationMap = new HashMap<>();
@@ -245,11 +249,31 @@ public final PropertyConfiguration<C, Boolean> addProperty(String name,
245249
* default value of property.
246250
* @return fluent {@code PropertyConfiguration} for configuring the property
247251
*/
252+
@Deprecated
248253
public final PropertyConfiguration<C, JsonValue> addProperty(String name,
249254
JsonValue defaultValue) {
250255
return addProperty(name, JsonValue.class, defaultValue);
251256
}
252257

258+
/**
259+
* Add an {@code JsonValue} property to the exported web component
260+
* identified by {@code name}.
261+
*
262+
* @param name
263+
* name of the property. While all formats are allowed, names in
264+
* camelCase will be converted to dash-separated form, when
265+
* property update events are generated, using form
266+
* "property-name-changed", if the property is called
267+
* "propertyName"
268+
* @param defaultValue
269+
* default value of property.
270+
* @return fluent {@code PropertyConfiguration} for configuring the property
271+
*/
272+
public final PropertyConfiguration<C, BaseJsonNode> addProperty(String name,
273+
BaseJsonNode defaultValue) {
274+
return addProperty(name, BaseJsonNode.class, defaultValue);
275+
}
276+
253277
/**
254278
* If custom initialization for the created {@link Component} instance is
255279
* needed, it can be done here. It is also possible to configure custom
@@ -343,6 +367,14 @@ public Set<PropertyData<? extends Serializable>> getPropertyDataSet() {
343367
public WebComponentBinding<C> createWebComponentBinding(
344368
Instantiator instantiator, Element element,
345369
JsonObject newAttributeDefaults) {
370+
return createWebComponentBinding(instantiator, element,
371+
JacksonUtils.mapElemental(newAttributeDefaults));
372+
}
373+
374+
@Override
375+
public WebComponentBinding<C> createWebComponentBinding(
376+
Instantiator instantiator, Element element,
377+
JsonNode newAttributeDefaults) {
346378
assert (instantiator != null);
347379

348380
final C componentReference = instantiator
@@ -374,8 +406,8 @@ public WebComponentBinding<C> createWebComponentBinding(
374406
componentReference);
375407

376408
// collect possible new defaults from attributes as JsonValues
377-
final Map<String, JsonValue> newDefaultValues = Stream
378-
.of(newAttributeDefaults.keys()).collect(Collectors
409+
final Map<String, JsonNode> newDefaultValues = JacksonUtils
410+
.getKeys(newAttributeDefaults).stream().collect(Collectors
379411
.toMap(key -> key, newAttributeDefaults::get));
380412

381413
// bind properties onto the WebComponentBinding. Since

flow-server/src/main/java/com/vaadin/flow/component/webcomponent/WebComponentConfiguration.java

+22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.io.Serializable;
2020
import java.util.Set;
2121

22+
import com.fasterxml.jackson.databind.JsonNode;
23+
2224
import com.vaadin.flow.component.Component;
2325
import com.vaadin.flow.component.WebComponentExporter;
2426
import com.vaadin.flow.di.Instantiator;
@@ -97,6 +99,26 @@ public interface WebComponentConfiguration<C extends Component>
9799
* @return web component binding which can be used by the web component host
98100
* to communicate with the component it is hosting
99101
*/
102+
WebComponentBinding<C> createWebComponentBinding(Instantiator instantiator,
103+
Element element, JsonNode newAttributeDefaults);
104+
105+
/**
106+
* Creates a new {@link WebComponentBinding} instance.
107+
*
108+
* @param instantiator
109+
* {@link com.vaadin.flow.di.Instantiator} used to construct
110+
* instances
111+
* @param element
112+
* element which acts as the root element for the exported
113+
* {@code component} instance
114+
* @param newAttributeDefaults
115+
* {@link JsonObject} containing default overrides set by the
116+
* user defining the component on a web page. These defaults are
117+
* set using the web component's attributes.
118+
* @return web component binding which can be used by the web component host
119+
* to communicate with the component it is hosting
120+
*/
121+
@Deprecated
100122
WebComponentBinding<C> createWebComponentBinding(Instantiator instantiator,
101123
Element element, JsonObject newAttributeDefaults);
102124

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

+11
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@
4949

5050
import elemental.json.Json;
5151
import elemental.json.JsonArray;
52+
import elemental.json.JsonBoolean;
5253
import elemental.json.JsonNull;
54+
import elemental.json.JsonNumber;
5355
import elemental.json.JsonObject;
5456
import elemental.json.JsonValue;
5557

@@ -153,6 +155,15 @@ public static BaseJsonNode mapElemental(JsonValue jsonValue) {
153155
if (jsonValue == null || jsonValue instanceof JsonNull) {
154156
return nullNode();
155157
}
158+
if (jsonValue instanceof JsonObject) {
159+
return mapElemental((JsonObject) jsonValue);
160+
}
161+
if (jsonValue instanceof JsonNumber) {
162+
return objectMapper.valueToTree(jsonValue.asNumber());
163+
}
164+
if (jsonValue instanceof JsonBoolean) {
165+
return objectMapper.valueToTree(jsonValue.asBoolean());
166+
}
156167
return objectMapper.valueToTree(jsonValue.asString());
157168
}
158169

flow-server/src/main/java/com/vaadin/flow/server/webcomponent/WebComponentBinding.java

+125-1
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@
2020
import java.util.HashMap;
2121
import java.util.Objects;
2222

23+
import com.fasterxml.jackson.databind.JsonNode;
24+
import com.fasterxml.jackson.databind.node.BaseJsonNode;
25+
import com.fasterxml.jackson.databind.node.NullNode;
2326
import org.slf4j.LoggerFactory;
2427

2528
import com.vaadin.flow.component.Component;
2629
import com.vaadin.flow.component.webcomponent.WebComponentConfiguration;
2730
import com.vaadin.flow.function.SerializableBiConsumer;
2831
import com.vaadin.flow.function.SerializableConsumer;
32+
import com.vaadin.flow.internal.JacksonCodec;
2933
import com.vaadin.flow.internal.JsonCodec;
3034

35+
import elemental.json.Json;
3136
import elemental.json.JsonValue;
3237

3338
/**
@@ -54,7 +59,7 @@ public final class WebComponentBinding<C extends Component>
5459
* Constructs a new {@code WebComponentBinding}. The bound {@link Component}
5560
* is given via {@code component} parameter. The web component properties
5661
* are bound by calling
57-
* {@link #bindProperty(PropertyConfigurationImpl, boolean, elemental.json.JsonValue)};
62+
* {@link #bindProperty(PropertyConfigurationImpl, boolean, JsonNode)};
5863
*
5964
* @param component
6065
* component which exposes {@code properties} as web component.
@@ -116,6 +121,7 @@ public void updateProperty(String propertyName, Serializable value) {
116121
* if the {@code jsonValue} cannot be converted to the type of
117122
* the property identified by {@code propertyName}.
118123
*/
124+
@Deprecated
119125
public void updateProperty(String propertyName, JsonValue jsonValue) {
120126
Objects.requireNonNull(propertyName,
121127
"Parameter 'propertyName' must not be null!");
@@ -127,6 +133,34 @@ public void updateProperty(String propertyName, JsonValue jsonValue) {
127133
updateProperty(propertyName, value);
128134
}
129135

136+
/**
137+
* Updates a property bound to the {@code component}. Converts the {@code
138+
* jsonValue} into the correct type if able and then calls
139+
* {@link #updateProperty(String, java.io.Serializable)}.
140+
*
141+
* @param propertyName
142+
* name of the property, not {@code null}
143+
* @param jsonValue
144+
* new value to set for the property
145+
* @throws NullPointerException
146+
* if {@code propertyName} is {@code null}
147+
* @throws IllegalArgumentException
148+
* if no bound property can be found for {@code propertyName}
149+
* @throws IllegalArgumentException
150+
* if the {@code jsonValue} cannot be converted to the type of
151+
* the property identified by {@code propertyName}.
152+
*/
153+
public void updateProperty(String propertyName, BaseJsonNode jsonValue) {
154+
Objects.requireNonNull(propertyName,
155+
"Parameter 'propertyName' must not be null!");
156+
157+
Class<? extends Serializable> propertyType = getPropertyType(
158+
propertyName);
159+
160+
Serializable value = jsonValueToConcreteType(jsonValue, propertyType);
161+
updateProperty(propertyName, value);
162+
}
163+
130164
/**
131165
* Retrieves the bound {@link Component} instance.
132166
*
@@ -170,6 +204,69 @@ public void updatePropertiesToComponent() {
170204
properties.forEach((key, value) -> value.notifyValueChange());
171205
}
172206

207+
/**
208+
* Adds a property to {@code this} web component binding based on the {@code
209+
* propertyConfiguration}. If a property with an existing name is bound, the
210+
* previous binding is removed. Starting value for the property is set to
211+
* {@code null}.
212+
*
213+
* @param propertyConfiguration
214+
* property configuration, not {@code null}
215+
* @param overrideDefault
216+
* set to {@code true} if the property should be initialized with
217+
* {@literal null} instead of default value found in
218+
* {@link PropertyData}
219+
* @throws NullPointerException
220+
* if {@code propertyConfiguration} is {@code null}
221+
*/
222+
public void bindProperty(
223+
PropertyConfigurationImpl<C, ? extends Serializable> propertyConfiguration,
224+
boolean overrideDefault) {
225+
bindProperty(propertyConfiguration, overrideDefault, (JsonNode) null);
226+
}
227+
228+
/**
229+
* Adds a property to {@code this} web component binding based on the {@code
230+
* propertyConfiguration}. If a property with an existing name is bound, the
231+
* previous binding is removed.
232+
*
233+
* @param propertyConfiguration
234+
* property configuration, not {@code null}
235+
* @param overrideDefault
236+
* set to {@code true} if the property should be initialized with
237+
* {@code startingValue} instead of default value found in
238+
* {@link PropertyData}
239+
* @param startingValue
240+
* starting value for the property. Can be {@code null}.
241+
* {@code overrideDefault} must be {@code true} for this value to
242+
* have any effect
243+
* @throws NullPointerException
244+
* if {@code propertyConfiguration} is {@code null}
245+
*/
246+
public void bindProperty(
247+
PropertyConfigurationImpl<C, ? extends Serializable> propertyConfiguration,
248+
boolean overrideDefault, JsonNode startingValue) {
249+
Objects.requireNonNull(propertyConfiguration,
250+
"Parameter 'propertyConfiguration' cannot be null!");
251+
252+
final SerializableBiConsumer<C, Serializable> consumer = propertyConfiguration
253+
.getOnChangeHandler();
254+
255+
final Serializable selectedStartingValue = !overrideDefault
256+
? propertyConfiguration.getPropertyData().getDefaultValue()
257+
: jsonValueToConcreteType(startingValue,
258+
propertyConfiguration.getPropertyData().getType());
259+
260+
final PropertyBinding<? extends Serializable> binding = new PropertyBinding<>(
261+
propertyConfiguration.getPropertyData(),
262+
consumer == null ? null
263+
: value -> consumer.accept(component, value),
264+
selectedStartingValue);
265+
266+
properties.put(propertyConfiguration.getPropertyData().getName(),
267+
binding);
268+
}
269+
173270
/**
174271
* Adds a property to {@code this} web component binding based on the {@code
175272
* propertyConfiguration}. If a property with an existing name is bound, the
@@ -188,6 +285,7 @@ public void updatePropertiesToComponent() {
188285
* @throws NullPointerException
189286
* if {@code propertyConfiguration} is {@code null}
190287
*/
288+
@Deprecated
191289
public void bindProperty(
192290
PropertyConfigurationImpl<C, ? extends Serializable> propertyConfiguration,
193291
boolean overrideDefault, JsonValue startingValue) {
@@ -212,6 +310,7 @@ public void bindProperty(
212310
binding);
213311
}
214312

313+
@Deprecated
215314
private Serializable jsonValueToConcreteType(JsonValue jsonValue,
216315
Class<? extends Serializable> type) {
217316
Objects.requireNonNull(type, "Parameter 'type' must not be null!");
@@ -229,6 +328,31 @@ private Serializable jsonValueToConcreteType(JsonValue jsonValue,
229328
}
230329
}
231330

331+
private Serializable jsonValueToConcreteType(JsonNode jsonValue,
332+
Class<? extends Serializable> type) {
333+
Objects.requireNonNull(type, "Parameter 'type' must not be null!");
334+
335+
if (JacksonCodec.canEncodeWithoutTypeInfo(type)) {
336+
Serializable value = null;
337+
if (jsonValue != null) {
338+
value = JacksonCodec.decodeAs(jsonValue, type);
339+
}
340+
return value;
341+
} else if (JsonCodec.canEncodeWithoutTypeInfo(type)) {
342+
// TODO: Remove when ClientCallable works with jackson types only.
343+
Serializable value = null;
344+
if (jsonValue != null && !(jsonValue instanceof NullNode)) {
345+
value = JsonCodec.decodeAs(Json.parse(jsonValue.toString()),
346+
type);
347+
}
348+
return value;
349+
} else {
350+
throw new IllegalArgumentException(
351+
String.format("Received '%s' was not convertible to '%s'",
352+
jsonValue.getClass().getName(), type.getName()));
353+
}
354+
}
355+
232356
private static class PropertyBinding<P extends Serializable>
233357
implements Serializable {
234358
private PropertyData<P> data;

flow-server/src/main/java/com/vaadin/flow/server/webcomponent/WebComponentGenerator.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import java.util.Objects;
2525
import java.util.Set;
2626

27+
import com.fasterxml.jackson.databind.JsonNode;
28+
import com.fasterxml.jackson.databind.node.ArrayNode;
29+
import com.fasterxml.jackson.databind.node.ObjectNode;
2730
import org.apache.commons.io.IOUtils;
2831

2932
import com.vaadin.flow.component.Component;
@@ -253,11 +256,14 @@ private static String getDefaultJsValue(PropertyData<?> property) {
253256
"\\'") + "'";
254257
} else if (JsonValue.class.isAssignableFrom(property.getType())) {
255258
value = ((JsonValue) property.getDefaultValue()).toJson();
259+
} else if (JsonNode.class.isAssignableFrom(property.getType())) {
260+
value = property.getDefaultValue().toString();
256261
} else {
257262
throw new UnsupportedPropertyTypeException(String.format(
258263
"%s is not a currently supported type for a Property."
259-
+ " Please use %s instead.",
264+
+ " Please use %s or %s instead.",
260265
property.getType().getSimpleName(),
266+
JsonNode.class.getSimpleName(),
261267
JsonValue.class.getSimpleName()));
262268
}
263269
if (value == null) {
@@ -300,9 +306,11 @@ private static String getJSTypeName(PropertyData<?> propertyData) {
300306
return "Number";
301307
} else if (propertyData.getType() == String.class) {
302308
return "String";
303-
} else if (JsonArray.class.isAssignableFrom(propertyData.getType())) {
309+
} else if (JsonArray.class.isAssignableFrom(propertyData.getType())
310+
|| ArrayNode.class.isAssignableFrom(propertyData.getType())) {
304311
return "Array";
305-
} else if (JsonValue.class.isAssignableFrom(propertyData.getType())) {
312+
} else if (JsonValue.class.isAssignableFrom(propertyData.getType())
313+
|| ObjectNode.class.isAssignableFrom(propertyData.getType())) {
306314
return "Object";
307315
} else {
308316
throw new IllegalStateException(

0 commit comments

Comments
 (0)