blob: cb16ad92792b1ea4e9a4c396904e8494a966325f [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
package org.eclipse.mdm.businessobjects.utils;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.mdm.api.base.file.FileService.FileServiceType;
import org.eclipse.mdm.api.base.model.EnumRegistry;
import org.eclipse.mdm.api.base.model.Enumeration;
import org.eclipse.mdm.api.base.model.EnumerationValue;
import org.eclipse.mdm.api.base.model.FileLink;
import org.eclipse.mdm.api.base.model.MDMFile;
import org.eclipse.mdm.api.base.model.MimeType;
import org.eclipse.mdm.api.base.model.Value;
import org.eclipse.mdm.api.base.model.ValueType;
import org.eclipse.mdm.businessobjects.control.MDMEntityAccessException;
import org.eclipse.mdm.businessobjects.control.MDMFileAccessException;
import org.eclipse.mdm.businessobjects.entity.MDMFileLinkExt;
import org.eclipse.mdm.templatequery.entity.MDMDeserializerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Strings;
/**
* Serializer for values.
*/
public final class Serializer {
private static final Logger LOGGER = LoggerFactory.getLogger(Serializer.class);
public static DateTimeFormatter[] formatter = new DateTimeFormatter[] {
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]'Z'"),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSS]'Z'"),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSSSSS]'Z'") };
public static DateTimeFormatter parser = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSSSSS][.SSSSSS][.SSS]'Z'");
private Serializer() {
}
public static Object serializeValue(Value value) {
if (value.getValueType().isDate()) {
return formatDate(value.extract());
} else if (value.getValueType().isFileLink()) {
return serializeFileLink(value.extract(ValueType.FILE_LINK));
} else if (value.getValueType().isFileLinkSequence()) {
return Stream.of(value.extract(ValueType.FILE_LINK_SEQUENCE)).map(Serializer::serializeFileLink)
.toArray(MDMFileLinkExt[]::new);
} else if (value.getValueType().isFileRelation()) {
return Stream.of(value.extract(ValueType.FILE_RELATION)).map(Serializer::serializeFileLink)
.toArray(MDMFileLinkExt[]::new);
} else if (value.getValueType().isStringSequence()) {
return value.extract(ValueType.STRING_SEQUENCE);
} else if (value.getValueType().isIntegerSequence()) {
return value.extract(ValueType.INTEGER_SEQUENCE);
} else if (value.getValueType().isShortSequence()) {
return value.extract(ValueType.SHORT_SEQUENCE);
} else if (value.getValueType().isBooleanSequence()) {
return value.extract(ValueType.BOOLEAN_SEQUENCE);
} else if (value.getValueType().isByteSequence()) {
return value.extract(ValueType.BYTE_SEQUENCE);
} else if (value.getValueType().isDateSequence()) {
return Stream.of(value.extract(ValueType.DATE_SEQUENCE)).map(Serializer::formatDate).toArray(String[]::new);
} else if (value.getValueType().isDoubleSequence()) {
return value.extract(ValueType.DOUBLE_SEQUENCE);
} else if (value.getValueType().isFloatSequence()) {
return value.extract(ValueType.FLOAT_SEQUENCE);
} else if (value.getValueType().isLongSequence()) {
return value.extract(ValueType.LONG_SEQUENCE);
} else if (value.getValueType().isEnumerationSequence()) {
return Stream.of(value.extract(ValueType.ENUMERATION_SEQUENCE)).map(Serializer::serializeEnum)
.toArray(String[]::new);
} else if (value.getValueType().isEnumeration()) {
return serializeEnum(value.extract(ValueType.ENUMERATION));
} else {
return Objects.toString(value.extract(), "");
}
}
public static String formatDate(LocalDateTime date) {
int nanos = date.get(ChronoField.NANO_OF_SECOND);
if (nanos == 0) {
return formatter[0].format(date);
} else if (nanos % 1_000_000 == 0) {
return formatter[1].format(date);
} else if (nanos % 1_000 == 0) {
return formatter[2].format(date);
} else {
return formatter[3].format(date);
}
}
private static String serializeEnum(EnumerationValue value) {
if (value == null) {
return null;
} else {
return value.name();
}
}
public static MDMFileLinkExt serializeFileLink(FileLink fileLink) {
return new MDMFileLinkExt(getFileLinkIdentifier(fileLink), fileLink.getMimeType().toString(),
fileLink.getDescription(), getDisplayFileName(fileLink));
}
public static LocalDateTime parseDate(String value) {
return LocalDateTime.from(parser.parse(value));
}
public static void applyValue(org.eclipse.mdm.api.base.model.Value value, Object newValue, String sourceName) {
value.set(deserializeValue(value.getValueType(), newValue, sourceName, value.getEnumName()));
}
public static Object deserializeValue(ValueType<?> type, Object newValue) {
if (type.isEnumerationType()
&& !(newValue instanceof EnumerationValue || newValue instanceof EnumerationValue[])) {
throw new MDMDeserializerException(
"Cannot deserialize Enumeration type without sourceName. Please use Serializer.deserializeValue(ValueType<?> type, Object newValue, String sourceName)");
} else {
return deserializeValue(type, newValue, null, null);
}
}
public static Object deserializeValue(ValueType<?> type, Object newValue, String sourceName, String enumName) {
if (newValue == null || (!type.isString() && newValue instanceof String && "".equals((String) newValue))) {
return null;
}
if (type.isBoolean()) {
if (newValue instanceof Number) {
return ((Number) newValue).intValue() == 1;
} else if (newValue instanceof String) {
String stringVal = (String) newValue;
return getBooleanVal(stringVal);
}
} else if (type.isByte()) {
if (newValue instanceof Number) {
return (byte) ((Number) newValue).intValue();
} else if (newValue instanceof String) {
return Byte.parseByte((String) newValue);
}
} else if (type.isShort()) {
if (newValue instanceof Number) {
return (short) ((Number) newValue).intValue();
} else if (newValue instanceof String) {
return Short.parseShort((String) newValue);
}
} else if (type.isInteger()) {
if (newValue instanceof Number) {
return ((Number) newValue).intValue();
} else if (newValue instanceof String) {
return Integer.parseInt((String) newValue);
}
} else if (type.isLong()) {
if (newValue instanceof Number) {
return ((Number) newValue).longValue();
} else if (newValue instanceof String) {
return Long.parseLong((String) newValue);
}
} else if (type.isFloat()) {
if (newValue instanceof Number) {
return ((Number) newValue).floatValue();
} else if (newValue instanceof String) {
return Float.parseFloat((String) newValue);
}
} else if (type.isDouble()) {
if (newValue instanceof Number) {
return ((Number) newValue).doubleValue();
} else if (newValue instanceof String) {
return Double.parseDouble((String) newValue);
}
} else if (type.isDate()) {
if (newValue instanceof Number) {
return LocalDateTime.ofEpochSecond((long) newValue, 0, ZoneOffset.UTC);
} else if (newValue instanceof String) {
if (Strings.isNullOrEmpty((String) newValue)) {
return null;
} else {
return Serializer.parseDate((String) newValue);
}
}
} else if (type.isFileLink()) {
if (newValue instanceof FileLink) {
return newValue;
} else {
return deserializeFileLink(newValue);
}
} else if (type.isFileLinkSequence()) {
if (newValue instanceof FileLink[]) {
return newValue;
} else if (newValue instanceof List) {
if (((List<?>) newValue).isEmpty()) {
return new FileLink[0];
} else {
List<FileLink> fileLinks = new ArrayList<>();
for (Object o : (List<?>) newValue) {
fileLinks.add(deserializeFileLink(o));
}
return fileLinks.toArray(new FileLink[0]);
}
}
} else if (type.isEnumeration()) {
return deserializeEnumerationValue(newValue, sourceName, enumName);
} else if (type.isEnumerationSequence()) {
if (newValue instanceof EnumerationValue[]) {
return newValue;
} else if (newValue instanceof List) {
if (((List<?>) newValue).isEmpty()) {
return new EnumerationValue[0];
} else {
return ((List<?>) newValue).stream().map(v -> deserializeEnumerationValue(v, sourceName, enumName))
.toArray(EnumerationValue[]::new);
}
}
} else if (type.isStringSequence()) {
if (newValue instanceof String[]) {
return newValue;
} else if (newValue instanceof List<?>) {
List<?> listValue = (List<?>) newValue;
return listValue.toArray(new String[listValue.size()]);
}
} else if (type.isShortSequence()) {
Number[] numArray = new Number[0];
if (newValue instanceof Number[]) {
numArray = (Number[]) newValue;
} else if (newValue instanceof List<?>) {
List<?> listValue = (List<?>) newValue;
numArray = listValue.toArray(new Number[listValue.size()]);
}
short[] shortVals = new short[numArray.length];
for (int i = 0; i < numArray.length; i++) {
shortVals[i] = ((Number) numArray[i]).shortValue();
}
return shortVals;
} else if (type.isBooleanSequence()) {
if (newValue instanceof Number) {
return ((Number) newValue).intValue() == 1;
} else if (newValue instanceof String) {
String stringVal = (String) newValue;
return getBooleanVal(stringVal);
}
if (newValue instanceof Number[]) {
Number[] numVals = (Number[]) newValue;
boolean[] bolVals = new boolean[numVals.length];
for (int i = 0; i < numVals.length; i++) {
bolVals[i] = numVals[i].intValue() == 1;
}
return bolVals;
} else if (newValue instanceof String[]) {
String[] stringVals = (String[]) newValue;
boolean[] bolVals = new boolean[stringVals.length];
for (int i = 0; i < stringVals.length; i++) {
bolVals[i] = getBooleanVal(stringVals[i]);
}
return bolVals;
} else if (newValue instanceof List<?>) {
List<?> listValue = (List<?>) newValue;
boolean[] bolVals = new boolean[listValue.size()];
for (int i = 0; i < listValue.size(); i++) {
bolVals[i] = getBooleanVal(listValue.get(i).toString());
}
return bolVals;
}
} else if (type.isByteSequence()) {
Number[] numArray = new Number[0];
if (newValue instanceof Number[]) {
numArray = (Number[]) newValue;
} else if (newValue instanceof List<?>) {
List<?> listValue = (List<?>) newValue;
numArray = listValue.toArray(new Number[listValue.size()]);
}
byte[] byteVals = new byte[numArray.length];
for (int i = 0; i < numArray.length; i++) {
byteVals[i] = ((Number) numArray[i]).byteValue();
}
return byteVals;
} else if (type.isDateSequence()) {
List<LocalDateTime> ldtList = new ArrayList<>();
if (newValue instanceof Number[]) {
for (Number n : (Number[]) newValue) {
ldtList.add(LocalDateTime.ofEpochSecond((long) n, 0, ZoneOffset.UTC));
}
} else if (newValue instanceof String[]) {
for (String s : (String[]) newValue) {
if (Strings.isNullOrEmpty((String) s)) {
ldtList.add(null);
} else {
ldtList.add(Serializer.parseDate(s));
}
}
} else if (newValue instanceof List<?>) {
List<?> listValue = (List<?>) newValue;
for (Object o : listValue) {
if (o instanceof Number) {
ldtList.add(LocalDateTime.ofEpochSecond((long) o, 0, ZoneOffset.UTC));
} else if (o instanceof String) {
if (Strings.isNullOrEmpty((String) o)) {
ldtList.add(null);
} else {
ldtList.add(Serializer.parseDate((String) o));
}
}
}
}
return ldtList.toArray(new LocalDateTime[ldtList.size()]);
} else if (type.isDoubleSequence()) {
Number[] numArray = new Number[0];
if (newValue instanceof Number[]) {
numArray = (Number[]) newValue;
} else if (newValue instanceof List<?>) {
List<?> listValue = (List<?>) newValue;
numArray = listValue.toArray(new Number[listValue.size()]);
}
double[] doubleVals = new double[numArray.length];
for (int i = 0; i < numArray.length; i++) {
doubleVals[i] = ((Number) numArray[i]).doubleValue();
}
return doubleVals;
} else if (type.isFloatSequence()) {
Number[] numArray = new Number[0];
if (newValue instanceof Number[]) {
numArray = (Number[]) newValue;
} else if (newValue instanceof List<?>) {
List<?> listValue = (List<?>) newValue;
numArray = listValue.toArray(new Number[listValue.size()]);
}
float[] floatVals = new float[numArray.length];
for (int i = 0; i < numArray.length; i++) {
floatVals[i] = ((Number) numArray[i]).floatValue();
}
return floatVals;
} else if (type.isIntegerSequence()) {
Number[] numArray = new Number[0];
if (newValue instanceof Number[]) {
numArray = (Number[]) newValue;
} else if (newValue instanceof List<?>) {
List<?> listValue = (List<?>) newValue;
numArray = listValue.toArray(new Number[listValue.size()]);
}
int[] intVals = new int[numArray.length];
for (int i = 0; i < numArray.length; i++) {
intVals[i] = ((Number) numArray[i]).intValue();
}
return intVals;
} else if (type.isLongSequence()) {
Number[] numArray = new Number[0];
if (newValue instanceof Number[]) {
numArray = (Number[]) newValue;
} else if (newValue instanceof List<?>) {
List<?> listValue = (List<?>) newValue;
numArray = listValue.toArray(new Number[listValue.size()]);
}
long[] longVals = new long[numArray.length];
for (int i = 0; i < numArray.length; i++) {
longVals[i] = ((Number) numArray[i]).longValue();
}
return longVals;
}
LOGGER.warn("Deserialize of {} not implemented yet!", type.name());
// TODO mkoller on 2018-12-06: Missing ValueTypes: ByteStream, Blob,
// FloatComplex, DoubleComplex and all sequence ValueTypes
return newValue;
}
private static boolean getBooleanVal(String stringVal) {
if (StringUtils.isNumeric(stringVal)) {
return Integer.parseInt(stringVal) == 1;
} else {
return Boolean.valueOf(stringVal);
}
}
public static FileLink deserializeFileLink(Object newValue) {
if (newValue == null || newValue instanceof String && ((String) newValue).trim().isEmpty()) {
return null;
} else if (newValue instanceof Map<?, ?>) {
Map<?, ?> map = (Map<?, ?>) newValue;
String remotePath = Objects.toString(map.get("remotePath"));
MimeType mimeType = new MimeType(Objects.toString(map.get("mimeType")));
String description = Objects.toString(map.get("description"));
FileServiceType fileServiceType = FileServiceType
.valueOf(Objects.toString(map.get("fileServiceType"), "EXTREF"));
return FileLink.newRemote(remotePath, mimeType, description, -1, null, fileServiceType);
} else if (newValue instanceof JsonNode) {
JsonNode node = (JsonNode) newValue;
String remotePath = node.get("remotePath").asText();
MimeType mimeType = new MimeType(node.get("mimeType").asText());
String description = node.get("description").asText();
FileServiceType fileServiceType = FileServiceType.valueOf(node.get("fileServiceType").asText("EXTREF"));
return FileLink.newRemote(remotePath, mimeType, description, -1, null, fileServiceType);
}
throw new MDMEntityAccessException("Cannot deserialize FILE_LINK: " + newValue);
}
public static EnumerationValue deserializeEnumerationValue(Object newValue, String sourceName, String enumName) {
if (newValue instanceof EnumerationValue) {
return (EnumerationValue) newValue;
}
if (sourceName == null) {
throw new MDMEntityAccessException("Cannot deserialize enumeration value, if sourceName is null.");
}
if (enumName == null) {
throw new MDMEntityAccessException("Cannot deserialize enumeration value, if enumName is null.");
}
Enumeration<?> enumeration = EnumRegistry.getInstance().get(sourceName, enumName);
if (enumeration == null) {
throw new IllegalArgumentException(
"Enumeration with name '" + enumName + "' not found in source '" + sourceName + "'.");
}
if (newValue instanceof String) {
String value = (String) newValue;
return enumeration.valueOf(value);
} else if (newValue instanceof Number) {
Number ordinal = (Number) newValue;
return enumeration.valueOf(ordinal.intValue());
} else if (newValue instanceof java.util.Map) {
@SuppressWarnings("unchecked")
java.util.Map<String, Object> map = (java.util.Map<String, Object>) newValue;
String value = (String) map.get("value");
Object ordinal = map.get("ordinal");
if (value != null) {
return enumeration.valueOf(value);
} else if (ordinal != null && ordinal instanceof Number) {
return enumeration.valueOf(((Number) ordinal).intValue());
} else {
throw new IllegalArgumentException(
"Both properties 'value' and 'ordinal' are null for the enumeration value.");
}
} else {
throw new IllegalArgumentException(
"Cannot deserialize Enumeration from Object '" + newValue + "' for source '" + sourceName + "'.");
}
}
public static String getFileLinkIdentifier(FileLink fileLink) {
String ident;
switch (fileLink.getFileServiceType()) {
case EXTREF:
ident = fileLink.getRemotePath();
break;
case AOFILE:
ident = (fileLink.getRemoteObject() == null ? "0:0"
: String.format("%1$s:%2$s", ((MDMFile) fileLink.getRemoteObject()).getTypeName(),
((MDMFile) fileLink.getRemoteObject()).getID()));
break;
default:
throw new MDMFileAccessException(
String.format("Unknown FileServiceType %s!", fileLink.getFileServiceType().name()));
}
return String.format("%s:%s", fileLink.getFileServiceType().name(), Strings.nullToEmpty(ident));
}
public static String getDisplayFileName(FileLink fileLink) {
switch (fileLink.getFileServiceType()) {
case EXTREF: {
String remotePath = Strings.nullToEmpty(fileLink.getRemotePath()).trim();
char sep = remotePath.contains("\\") ? '\\' : '/';
String filename = remotePath.substring(Math.max(0, remotePath.lastIndexOf(sep)), remotePath.length());
if (filename.length() > 0 && filename.charAt(0) == sep) {
filename = (filename.length() > 1 ? filename.substring(1) : "");
}
return (Strings.isNullOrEmpty(filename) ? remotePath : filename);
}
case AOFILE: {
return ((MDMFile) fileLink.getRemoteObject()).getOriginalFileName();
}
default: {
throw new MDMFileAccessException(
String.format("Unknown FileServiceType %s!", fileLink.getFileServiceType().name()));
}
}
}
}