blob: c76ccaf21efbcd62dffd55aa7d945341608751fa [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2020 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.nodeprovider.utils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.mdm.api.base.ServiceNotProvidedException;
import org.eclipse.mdm.api.base.adapter.Attribute;
import org.eclipse.mdm.api.base.adapter.EntityType;
import org.eclipse.mdm.api.base.adapter.ModelManager;
import org.eclipse.mdm.api.base.query.Filter;
import org.eclipse.mdm.api.dflt.ApplicationContext;
import org.eclipse.mdm.api.dflt.EntityManager;
import org.eclipse.mdm.businessobjects.control.FilterParser;
import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
import org.eclipse.mdm.connector.boundary.ConnectorService;
import org.eclipse.mdm.nodeprovider.control.MDMExpressionLanguageService;
import org.eclipse.mdm.nodeprovider.control.NodeProviderException;
import org.eclipse.mdm.nodeprovider.entity.NodeLevel;
import org.eclipse.mdm.nodeprovider.entity.NodeProviderRoot;
import org.eclipse.mdm.nodeprovider.entity.SortAttribute;
import org.eclipse.mdm.nodeprovider.utils.dto.NodeLevelDTO;
import org.eclipse.mdm.nodeprovider.utils.dto.NodeProviderRootDTO;
import org.eclipse.mdm.protobuf.Mdm;
import org.eclipse.mdm.protobuf.Mdm.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.protobuf.InvalidProtocolBufferException;
/**
* Utility class for converting and (de-)serializing {@link Node},
* {@link NodeLevel} and {@link NodeProviderRoot}. Deserializing is a two step
* process: First we deserialize the JSON string into a DTO. Secondly we convert
* the DTO checking its {@link EntityType} and {@link Attribute}s against the
* actual {@link ApplicationContext}.
*
*
*/
public class SerializationUtil {
private static final Logger LOG = LoggerFactory.getLogger(SerializationUtil.class);
private static ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
/**
* Create a node with the specified parameters and sets the serial.
*
* @param source source name
* @param type source type
* @param id ID of the Node
* @param idAttribute name of the ID attribute
* @param filter filter
* @param label Label of the Node
* @return {@link Node}
*/
public static Node createNode(String source, String type, String id, String idAttribute, Filter filter,
String label) {
return createNode(source, type, id, idAttribute, FilterParser.toString(filter), label);
}
/**
* Create a node with the specified parameters and sets the serial.
*
* @param source source name
* @param type source type
* @param id ID of the Node
* @param idAttribute name of the ID attribute
* @param filter filter
* @param label Label of the Node
* @return {@link Node}
*/
public static Node createNode(String source, String type, String id, String idAttribute, String filter,
String label) {
String nodeId = id == null ? "" : id;
String nodeLabel = label == null ? "" : label;
Node node = Node.newBuilder().setSource(source).setType(type).setId(nodeId)
.setIdAttribute(idAttribute).setFilter(filter).setLabel(nodeLabel).build();
byte[] bytes = node.toByteArray();
return node.toBuilder().setSerial(Base64.getUrlEncoder().encodeToString(bytes)).build();
}
/**
* Serializes a node to a base64 encoded protobuf {@link Node} object
*
* @param node
* @return base64 encoded protobuf Node
*/
public static String serializeNode(Node node) {
return Base64.getUrlEncoder().encodeToString(node.toByteArray());
}
/**
* Deserialize a base64 encoded protobuf object into a {@link Node}.
*
* @param base64Protobuf
* @return {@link Node}
*/
public static Node deserializeNode(String base64Protobuf) {
try {
return Mdm.Node.parseFrom(Base64.getUrlDecoder().decode(base64Protobuf.getBytes(StandardCharsets.UTF_8)));
} catch (InvalidProtocolBufferException e) {
throw new NodeProviderException("Cannot deserialize Node: " + base64Protobuf, e);
}
}
/**
* Serialize a {@link NodeLevel} and convert it to a {@link NodeLevelDTO} JSON
* string.
*
* @return NodeLevel
*/
public static String serializeNodeLevel(NodeLevel nl) {
try {
return mapper.writeValueAsString(SerializationUtil.convert(nl));
} catch (IOException e) {
throw new NodeProviderException("Cannot serialize NodeLevel: " + nl, e);
}
}
/**
* Deserialize a {@link NodeLevelDTO} given as JSON string and convert it to a
* {@link NodeLevel}
*
* @param context
* @param json
* @return NodeLevel
*/
public static NodeLevel deserializeNodeLevel(ApplicationContext context, String json) {
try {
NodeLevelDTO readNld = mapper.readValue(json, NodeLevelDTO.class);
return SerializationUtil.convert(context, readNld);
} catch (IOException e) {
throw new NodeProviderException("Cannot deserialize NodeLevel: " + json, e);
}
}
/**
* Deserialize a {@link NodeProviderRootDTO} given as JSON string and convert it
* to a {@link NodeProviderRoot}.
*
* @param connectorService
* @param json
* @return
*/
public static NodeProviderRoot deserializeNodeProviderRoot(ConnectorService connectorService, String json) {
try {
NodeProviderRootDTO dto = mapper.readValue(json, NodeProviderRootDTO.class);
NodeProviderRoot npr = new NodeProviderRoot();
npr.setId(dto.getId());
npr.setName(dto.getName());
if (dto.getContexts().containsKey(NodeProviderRoot.WILDCARD)) {
NodeLevelDTO nlDTO = mapper.treeToValue(dto.getContexts().get(NodeProviderRoot.WILDCARD),
NodeLevelDTO.class);
for (ApplicationContext context : connectorService.getContexts()) {
String sourceName = context.getEntityManager()
.orElseThrow(() -> new ServiceNotProvidedException(EntityManager.class)).loadEnvironment()
.getSourceName();
if (dto.getContexts().containsKey(sourceName)) {
continue;
}
try {
npr.getContexts().put(sourceName, convert(context, nlDTO));
} catch (Exception e) {
LOG.warn("Cannot use node provider definition {} for datasource {}", NodeProviderRoot.WILDCARD,
sourceName);
}
}
}
for (Map.Entry<String, JsonNode> e : dto.getContexts().entrySet()) {
if (NodeProviderRoot.WILDCARD.equals(e.getKey())) {
continue;
}
ApplicationContext context = connectorService.getContextByName(e.getKey());
NodeLevelDTO nlDTO = mapper.treeToValue(e.getValue(), NodeLevelDTO.class);
try {
npr.getContexts().put(e.getKey(), convert(context, nlDTO));
} catch (Exception ex) {
LOG.warn("Cannot use node provider definition {} for datasource {}", e.getKey(), e.getKey());
}
}
return npr;
} catch (IOException e) {
throw new NodeProviderException("Cannot deserialize NodeProviderRoot: " + json, e);
}
}
/**
* Serialize a {@link NodeProviderRoot} and convert it to a
* {@link NodeProviderRootDTO} JSON string.
*
* @param npr
* @return
*/
public static String serializeNodeProviderRoot(NodeProviderRoot npr) {
try {
NodeProviderRootDTO dto = new NodeProviderRootDTO();
dto.setId(npr.getId());
dto.setName(npr.getName());
for (Map.Entry<String, NodeLevel> e : npr.getContexts().entrySet()) {
dto.getContexts().put(e.getKey(), mapper.valueToTree(convert(e.getValue())));
}
return mapper.writeValueAsString(dto);
} catch (IOException e) {
throw new NodeProviderException("Cannot serialize NodeProviderRoot: " + npr, e);
}
}
/**
* Converts a {@link NodeLevel} into a {@link NodeLevelDTO}.
*
* @param nl a {@link NodeLevel}
* @return the {@link NodeLevelDTO}
*/
public static NodeLevelDTO convert(NodeLevel nl) {
NodeLevelDTO nld = new NodeLevelDTO();
nld.setType(nl.getEntityType().getName());
nld.setFilterAttributes(Arrays.asList(getFilterAttribute(nl)));
nld.setLabelAttributes(Arrays.asList(getLabelAttribute(nl)));
if (nl.getLabelExpression() != null) {
nld.setLabelExpression(nl.getLabelExpression().getExpressionString());
}
nld.setContextState(nl.getContextState());
nld.setVirtual(nl.isVirtual());
nld.setOrderAttributes(nl.getOrderAttributes().stream()
.collect(Collectors.toMap(o -> o.getAttribute().getName(), o -> o.getOrder())));
if (nl.getChild() != null) {
nld.setChild(convert(nl.getChild()));
}
nld.setValuePrecision(nl.getValuePrecision());
return nld;
}
/**
* Converts a {@link NodeLevelDTO} into a {@link NodeLevel} using the given
* {@link ApplicationContext}.
*
* @param context application context
* @param nld {@link NodeLevelDTO}
* @return the {@link NodeLevel}
*/
public static NodeLevel convert(ApplicationContext context, NodeLevelDTO nld) {
ModelManager mm = context.getModelManager().get();
EntityType e = mm.getEntityType(ServiceUtils.invertMapping(nld.getType()));
List<Attribute> filterAttributes = nld.getFilterAttributes().stream().map(e::getAttribute)
.collect(Collectors.toList());
List<Attribute> labelAttributes = nld.getLabelAttributes().stream().map(e::getAttribute)
.collect(Collectors.toList());
NodeLevel nl = new NodeLevel(e, filterAttributes, labelAttributes);
nl.setContextState(nld.getContextState());
nl.setVirtual(nld.isVirtual());
nl.getOrderAttributes().addAll(nld.getOrderAttributes().entrySet().stream()
.map(x -> new SortAttribute(e.getAttribute(x.getKey()), x.getValue())).collect(Collectors.toList()));
MDMExpressionLanguageService inst = new MDMExpressionLanguageService();
nl.setLabelExpression(inst.parseValueExpression(nld.getLabelExpression()));
nl.setValuePrecision(nld.getValuePrecision());
if (nld.getChild() != null) {
nl.setChild(convert(context, nld.getChild()));
}
return nl;
}
private static String getFilterAttribute(NodeLevel nodeLevel) {
return nodeLevel.getFilterAttributes().stream().map(Attribute::getName).findFirst()
.orElseThrow(() -> new IllegalStateException("woops?!"));
}
private static String getLabelAttribute(NodeLevel nodeLevel) {
return nodeLevel.getLabelAttributes().stream().map(Attribute::getName).findFirst()
.orElseThrow(() -> new IllegalStateException("woops?!"));
}
}