Skip to content

Commit

Permalink
🐛 [Device Management] Fixed DeviceComponentConfiguration propery hand…
Browse files Browse the repository at this point in the history
…ling with empty or null values

Signed-off-by: Alberto Codutti <alberto.codutti@eurotech.com>
  • Loading branch information
Coduz committed Jun 21, 2024
1 parent 192c491 commit c9d7b55
Show file tree
Hide file tree
Showing 15 changed files with 278 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*******************************************************************************/
package org.eclipse.kapua.commons.configuration.metatype;

import com.google.common.base.Strings;
import org.eclipse.kapua.commons.crypto.CryptoUtil;
import org.eclipse.kapua.model.xml.XmlPropertyAdapted;
import org.eclipse.kapua.model.xml.adapters.ClassBasedXmlPropertyAdapterBase;
Expand All @@ -28,7 +29,7 @@ public PasswordPropertyAdapter(CryptoUtil cryptoUtil) {
}

@Override
public boolean canMarshall(Class objectClass) {
public boolean canMarshall(Class<?> objectClass) {
return Password.class.equals(objectClass);
}

Expand All @@ -42,19 +43,59 @@ public String marshallValue(Object value) {
return cryptoUtil.encodeBase64(value.toString());
}

@Override
public boolean canUnmarshallEmptyString() {
return true;
}

@Override
public Password unmarshallValue(String value) {
return new Password(cryptoUtil.decodeBase64(value));
}

/**
* Unmarshalls the given value according to {@link XmlPropertyAdapted#isEncrypted()}.
*
* @param value The value to unmarshall.
* @param isEncrypted The {@link XmlPropertyAdapted#isEncrypted()}.
* @return The unmarshalled {@link Password}
* @since 2.1.0
*/
public Password unmarshallValue(String value, boolean isEncrypted) {
return isEncrypted ? unmarshallValue(value) : new Password(value);
}

@Override
public Object unmarshallValues(XmlPropertyAdapted<?> property) {
if (!property.getArray()) {
return property.isEncrypted() ? unmarshallValue(property.getValues()[0]) : new Password(property.getValues()[0]);
String[] values = property.getValues();

// Values might not have been defined
// ie:
//
// <property name="propertyName" array="false" encrypted="false" type="Integer">
// </property>
if (values == null || values.length == 0) {
return null;
}

String value = property.getValues()[0];
return unmarshallValue(value, property.isEncrypted() && !Strings.isNullOrEmpty(value));
} else {
String[] values = property.getValues();

// Values might not have been defined
// ie:
//
// <property name="propertyName" array="true" encrypted="false" type="Integer">
// </property>
if (values == null) {
return null;
}

return Arrays
.stream(property.getValues())
.map(value -> property.isEncrypted() ? unmarshallValue(value) : new Password(value))
.map(value -> unmarshallValue(value, property.isEncrypted() && !Strings.isNullOrEmpty(value)))
.collect(Collectors.toList()).toArray();
}
}
Expand Down
5 changes: 4 additions & 1 deletion service/api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@
<artifactId>jaxb-impl</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public BooleanPropertyAdapter() {
super(Boolean.class);
}

@Override
public boolean canUnmarshallEmptyString() {
return false;
}

@Override
public Boolean unmarshallValue(String value) {
return Boolean.parseBoolean(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public BytePropertyAdapter() {
super(Byte.class);
}

@Override
public boolean canUnmarshallEmptyString() {
return false;
}

@Override
public Byte unmarshallValue(String value) {
return Byte.parseByte(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public CharPropertyAdapter() {
super(Character.class);
}

@Override
public boolean canUnmarshallEmptyString() {
return false;
}

@Override
public Character unmarshallValue(String value) {
return value.charAt(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*******************************************************************************/
package org.eclipse.kapua.model.xml.adapters;

import com.google.common.base.Strings;
import org.eclipse.kapua.model.xml.XmlPropertyAdapted;

import java.lang.reflect.Array;
Expand Down Expand Up @@ -52,6 +53,9 @@ public void marshallValues(XmlPropertyAdapted<?> property, Object value) {
if (nativeValues[i] != null) {
stringValues[i] = this.marshallValue(nativeValues[i]);
}
else {
stringValues[i] = null;
}
}
property.setValues(stringValues);
}
Expand All @@ -61,14 +65,69 @@ public String marshallValue(Object object) {
return object.toString();
}

/**
* Whether the {@link ClassBasedXmlPropertyAdapterBase} implementation can unmarshall a empty {@link String}.
* @return {@code true} if it can, {@code false} otherwise.
* @since 2.1.0
*/
public abstract boolean canUnmarshallEmptyString();

public abstract T unmarshallValue(String value);

@Override
public Object unmarshallValues(XmlPropertyAdapted<?> property) {
if (!property.getArray()) {
String[] values = property.getValues();

// Values might not have been defined
// ie:
//
// <property name="propertyName" array="false" encrypted="false" type="Integer">
// </property>
if (values == null || values.length == 0) {
return null;
}

// Value might be empty and some XmlPropertyAdapter can't handle empty values (ie: DoublePropertyAdapter).
//
// ie:
//
// <property name="propertyName" array="false" encrypted="false" type="Integer">
// <value/>
// </property>
//
// <property name="propertyName" array="false" encrypted="false" type="Integer">
// <value><value/>
// </property>
String value = values[0];
if (Strings.isNullOrEmpty(value) && !canUnmarshallEmptyString()) {
// FIXME: we should return an empty String, but ESF is not currently handling the DeviceConfiguration recevied.
// This might be the default behaviour to treat single values and arrays values the same...
return null;
}

return unmarshallValue(property.getValues()[0]);
} else {
final List<T> items = Arrays.stream(property.getValues()).map(this::unmarshallValue).collect(Collectors.toList());
String[] values = property.getValues();

// Values might not have been defined
// ie:
//
// <property name="propertyName" array="true" encrypted="false" type="Integer">
// </property>
if (values == null) {
return null;
}

final List<T> items = Arrays
.stream(property.getValues())
.map(value ->
!Strings.isNullOrEmpty(value) || canUnmarshallEmptyString() ?
unmarshallValue(value) :
null
)
.collect(Collectors.toList());
return items.toArray((T[]) Array.newInstance(clazz, items.size()));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public DoublePropertyAdapter() {
super(Double.class);
}

@Override
public boolean canUnmarshallEmptyString() {
return false;
}

@Override
public Double unmarshallValue(String value) {
return Double.parseDouble(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public FloatPropertyAdapter() {
super(Float.class);
}

@Override
public boolean canUnmarshallEmptyString() {
return false;
}

@Override
public Float unmarshallValue(String value) {
return Float.parseFloat(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public IntegerPropertyAdapter() {
super(Integer.class);
}

@Override
public boolean canUnmarshallEmptyString() {
return false;
}

@Override
public Integer unmarshallValue(String property) {
return Integer.parseInt(property);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public LongPropertyAdapter() {
super(Long.class);
}

@Override
public boolean canUnmarshallEmptyString() {
return false;
}

@Override
public Long unmarshallValue(String property) {
return Long.parseLong(property);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public ShortPropertyAdapter() {
super(Short.class);
}

@Override
public boolean canUnmarshallEmptyString() {
return false;
}

@Override
public Short unmarshallValue(String property) {
return Short.parseShort(property);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ public StringPropertyAdapter() {
super(String.class);
}

/**
* Yes, definitely String can be empty.
*
* @return {@code true}
* @since 2.1.0
*/
@Override
public boolean canUnmarshallEmptyString() {
return true;
}

@Override
public String unmarshallValue(String property) {
return property;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -37,18 +38,45 @@ public XmlPropertiesAdapter(Class<V> propertyClass, Supplier<V> adaptedPropertyF

@Override
public Map<String, Object> unmarshal(V[] properties) {
return Optional.ofNullable(properties)
Map<String, Object> unmarshalledProperties = new HashMap<>();

Optional.ofNullable(properties)
.map(Arrays::asList)
.orElse(Collections.emptyList())
.stream()
.filter(adaptedProp -> adaptedProp.getType() != null)
.filter(adaptedProp -> xmlPropertyAdapters.containsKey((adaptedProp.getType())))
.collect(Collectors.toMap(
adaptedProp -> adaptedProp.getName(),
adaptedProp -> {
final XmlPropertyAdapter xmlPropertyAdapter = xmlPropertyAdapters.get(adaptedProp.getType());
return xmlPropertyAdapter.unmarshallValues(adaptedProp);
}));
.filter(adaptedProp -> adaptedProp.getType() == null || xmlPropertyAdapters.containsKey((adaptedProp.getType())))
.forEach(adaptedProp -> unmarshalledProperties.put(adaptedProp.getName(), unmarshalProperty(adaptedProp)));

// This was a better way to do it, but Collectors.toMap does not accept `null` values.
// See Collectors#153 for reference.
// .collect(Collectors.toMap(
// adaptedProp -> adaptedProp.getName(),
// adaptedProp -> {
// final XmlPropertyAdapter xmlPropertyAdapter = xmlPropertyAdapters.get(adaptedProp.getType());
// return xmlPropertyAdapter.unmarshallValues(adaptedProp);
// }));

return unmarshalledProperties;
}

/**
* It unmarshal the given {@link XmlPropertyAdapted}.
* <p>
* If {@link XmlPropertyAdapted#getType()} is {@code null} it means that {@link XmlPropertyAdapted#getValues()} was {@code null}, so we can't determine its {@link XmlPropertyAdapted#getType()}.
* By the way, if original value was {@code null} we can safely return {@code null}.
*
* @param adaptedProp The {@link XmlPropertyAdapted} to unmarshal
* @return The unmarshalled {@link XmlPropertyAdapted}
* @since 2.1.0
*/
public Object unmarshalProperty(V adaptedProp) {
if (adaptedProp.getType() != null) {
XmlPropertyAdapter xmlPropertyAdapter = xmlPropertyAdapters.get(adaptedProp.getType());
return xmlPropertyAdapter.unmarshallValues(adaptedProp);
}
else {
return null;
}
}

@Override
Expand All @@ -57,23 +85,28 @@ public V[] marshal(Map<String, Object> props) {
.orElse(Collections.emptyMap())
.entrySet()
.stream()
.filter(nameAndValue -> nameAndValue.getValue() != null)
.map(nameAndValue -> {
final Object value = nameAndValue.getValue();
final V resEntry = adaptedPropertyFactory.get();
resEntry.setName(nameAndValue.getKey());
xmlPropertyAdapters
.entrySet()
.stream()
.filter(pa -> pa.getValue().canMarshall(value.getClass()))
.findFirst()
.ifPresent(typeToAdapter -> {
resEntry.setType(typeToAdapter.getKey());
typeToAdapter.getValue().marshallValues(resEntry, value);
});

// Some properties can be sent as `null` so we cannot determine the type by the value.
if (nameAndValue.getValue() != null) {
final Object value = nameAndValue.getValue();
xmlPropertyAdapters
.entrySet()
.stream()
.filter(pa -> pa.getValue().canMarshall(value.getClass()))
.findFirst()
.ifPresent(typeToAdapter -> {
resEntry.setType(typeToAdapter.getKey());
typeToAdapter.getValue().marshallValues(resEntry, value);
});
}

return resEntry;
})
.collect(Collectors.toList());

return adaptedProperties.toArray((V[]) Array.newInstance(propertyClass, adaptedProperties.size()));
}
}
Loading

0 comments on commit c9d7b55

Please sign in to comment.