| /******************************************************************************** |
| * 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())); |
| } |
| } |
| } |
| } |