/******************************************************************************** | |
* 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?!")); | |
} | |
} |