Skip to content

Commit ba3af32

Browse files
Merge pull request #18 from opendatamesh-initiative/16-architecture-refactor-to-improve-extensibility
refactor: Added support to extensions
2 parents 0d79ebf + 9ed9284 commit ba3af32

File tree

59 files changed

+1477
-101
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1477
-101
lines changed

src/main/java/org/opendatamesh/dpds/README.md

Whitespace-only changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.opendatamesh.dpds.extensions;
2+
3+
import com.fasterxml.jackson.core.JacksonException;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import org.opendatamesh.dpds.model.core.ComponentBase;
7+
8+
public interface ComponentBaseExtendedConverter<T extends ComponentBase> {
9+
10+
boolean supports(String key, Class<? extends ComponentBase> parentClass);
11+
12+
T deserialize(ObjectMapper defaultMapper, JsonNode jsonNode) throws JacksonException;
13+
14+
JsonNode serialize(ObjectMapper defaultMapper, T value) throws JacksonException;
15+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.opendatamesh.dpds.extensions;
2+
3+
import org.opendatamesh.dpds.model.core.ComponentBase;
4+
import org.opendatamesh.dpds.visitors.core.ComponentBaseVisitor;
5+
6+
public abstract class ComponentBaseExtendedVisitor<T extends ComponentBase> implements ComponentBaseVisitor {
7+
8+
private final Class<T> clazz;
9+
10+
protected ComponentBaseExtendedVisitor(Class<T> clazz) {
11+
this.clazz = clazz;
12+
}
13+
14+
@Override
15+
public void visit(ComponentBase componentBase) {
16+
if (clazz.isInstance(componentBase)) {
17+
visitExtension(clazz.cast(componentBase));
18+
}
19+
}
20+
21+
abstract void visitExtension(T componentBase);
22+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.opendatamesh.dpds.extensions;
2+
3+
import com.fasterxml.jackson.core.JacksonException;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import org.opendatamesh.dpds.model.core.ComponentBase;
7+
8+
public interface DefinitionConverter<T extends ComponentBase> {
9+
boolean supports(String specification, String specificationVersion);
10+
11+
T deserialize(ObjectMapper defaultMapper, JsonNode jsonNode) throws JacksonException;
12+
13+
JsonNode serialize(ObjectMapper defaultMapper, T value) throws JacksonException;
14+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.opendatamesh.dpds.extensions;
2+
3+
import org.opendatamesh.dpds.model.core.ComponentBase;
4+
import org.opendatamesh.dpds.model.core.ExternalDocs;
5+
import org.opendatamesh.dpds.visitors.core.StandardDefinitionVisitor;
6+
7+
public abstract class DefinitionVisitor<T extends ComponentBase> implements StandardDefinitionVisitor {
8+
9+
private final Class<T> clazz;
10+
11+
protected DefinitionVisitor(Class<T> clazz) {
12+
this.clazz = clazz;
13+
}
14+
15+
@Override
16+
public final void visit(ComponentBase definition) {
17+
if (clazz.isInstance(definition)) {
18+
visitDefinition(clazz.cast(definition));
19+
}
20+
}
21+
22+
@Override
23+
public void visit(ExternalDocs externalDocs) {
24+
}
25+
26+
abstract void visitDefinition(T definition);
27+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Extensions
2+
3+
## Overview
4+
5+
This package offers utilities an interfaces to extend the parsing of the Data Product Descriptor with
6+
custom extension and specification.
7+
8+
## Features
9+
10+
- Support for custom fields in the Data Product Descriptor specification.
11+
- Support for custom specifications under the `StandardDefinition` `definition` field.
12+
13+
## Project Structure
14+
15+
```
16+
├── ComponentBaseExtendedConverter.java (used to support custom fields in the Data Product Descriptor specification )
17+
├── ComponentBaseExtendedVisitor.java (used to implement visitor pattern on custom fields in the Data Product Descriptor specification )
18+
├── DefinitionConverter.java (used to support custom specifications under the StandardDefinition definition field)
19+
├── DefinitionVisitor.java (used to implement visitor pattern on custom specifications )
20+
├── visitorsimpl/ (contains implementations to handle DefinitionConverters and ComponentBaseExtendedConverters registered on the Parser )
21+
```
22+
23+
## Usage
24+
25+
See the `extensions` test package to have implementation examples.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.opendatamesh.dpds.extensions.visitorsimpl;
2+
3+
import org.opendatamesh.dpds.extensions.visitorsimpl.components.ComponentsExtensionVisitorImpl;
4+
import org.opendatamesh.dpds.extensions.visitorsimpl.info.InfoExtensionVisitorImpl;
5+
import org.opendatamesh.dpds.extensions.visitorsimpl.interfaces.InterfaceComponentsExtensionVisitorImpl;
6+
import org.opendatamesh.dpds.extensions.visitorsimpl.internals.InternalComponentsExtensionVisitorImpl;
7+
import org.opendatamesh.dpds.model.DataProductVersion;
8+
import org.opendatamesh.dpds.model.components.Components;
9+
import org.opendatamesh.dpds.model.core.ExternalDocs;
10+
import org.opendatamesh.dpds.model.info.Info;
11+
import org.opendatamesh.dpds.model.interfaces.InterfaceComponents;
12+
import org.opendatamesh.dpds.model.internals.InternalComponents;
13+
import org.opendatamesh.dpds.visitors.DataProductVersionVisitor;
14+
import org.opendatamesh.dpds.visitors.components.ComponentsVisitor;
15+
import org.opendatamesh.dpds.visitors.info.InfoVisitor;
16+
import org.opendatamesh.dpds.visitors.interfaces.InterfaceComponentsVisitor;
17+
import org.opendatamesh.dpds.visitors.internals.InternalComponentsVisitor;
18+
19+
import java.util.Collection;
20+
import java.util.Objects;
21+
import java.util.stream.Stream;
22+
23+
public class DataProductVersionExtensionVisitorImpl extends ExtensionVisitor implements DataProductVersionVisitor {
24+
25+
public DataProductVersionExtensionVisitorImpl(ExtensionHandler extensionHandler) {
26+
super(null);
27+
this.extensionHandler = extensionHandler;
28+
}
29+
30+
@Override
31+
public void visit(Info info) {
32+
extensionHandler.handleComponentBaseExtension(info, DataProductVersion.class);
33+
InfoVisitor infoVisitor = new InfoExtensionVisitorImpl(this);
34+
if (info.getOwner() != null) {
35+
infoVisitor.visit(info.getOwner());
36+
}
37+
if (info.getContactPoints() != null) {
38+
info.getContactPoints().forEach(infoVisitor::visit);
39+
}
40+
}
41+
42+
@Override
43+
public void visit(InterfaceComponents interfaceComponents) {
44+
extensionHandler.handleComponentBaseExtension(interfaceComponents, DataProductVersion.class);
45+
InterfaceComponentsVisitor visitor = new InterfaceComponentsExtensionVisitorImpl(this);
46+
Stream.of(
47+
interfaceComponents.getInputPorts(),
48+
interfaceComponents.getOutputPorts(),
49+
interfaceComponents.getDiscoveryPorts(),
50+
interfaceComponents.getObservabilityPorts(),
51+
interfaceComponents.getControlPorts()
52+
)
53+
.filter(Objects::nonNull)
54+
.flatMap(Collection::stream)
55+
.forEach(visitor::visit);
56+
}
57+
58+
@Override
59+
public void visit(InternalComponents internalComponents) {
60+
extensionHandler.handleComponentBaseExtension(internalComponents, DataProductVersion.class);
61+
InternalComponentsVisitor visitor = new InternalComponentsExtensionVisitorImpl(this);
62+
if (internalComponents.getApplicationComponents() != null) {
63+
internalComponents.getApplicationComponents().forEach(visitor::visit);
64+
}
65+
if (internalComponents.getInfrastructuralComponents() != null) {
66+
internalComponents.getInfrastructuralComponents().forEach(visitor::visit);
67+
}
68+
if (internalComponents.getLifecycleInfo() != null) {
69+
internalComponents.getLifecycleInfo()
70+
.forEach((stageName, tasks) -> tasks.forEach(visitor::visit)
71+
);
72+
}
73+
}
74+
75+
@Override
76+
public void visit(Components components) {
77+
extensionHandler.handleComponentBaseExtension(components, DataProductVersion.class);
78+
ComponentsVisitor visitor = new ComponentsExtensionVisitorImpl(this);
79+
Stream.of(
80+
components.getInputPorts(),
81+
components.getOutputPorts(),
82+
components.getDiscoveryPorts(),
83+
components.getObservabilityPorts(),
84+
components.getControlPorts()
85+
)
86+
.filter(Objects::nonNull)
87+
.flatMap(map -> map.entrySet().stream())
88+
.forEach(entry -> visitor.visit(entry.getValue()));
89+
90+
if (components.getApplicationComponents() != null) {
91+
components.getApplicationComponents().forEach((k, v) -> visitor.visit(v));
92+
}
93+
if (components.getInfrastructuralComponents() != null) {
94+
components.getInfrastructuralComponents().forEach((k, v) -> visitor.visit(v));
95+
}
96+
if (components.getApis() != null) {
97+
components.getApis().forEach((k, v) -> visitor.visit(v));
98+
}
99+
if (components.getTemplates() != null) {
100+
components.getTemplates().forEach((k, v) -> visitor.visit(v));
101+
}
102+
}
103+
104+
@Override
105+
public void visit(ExternalDocs externalDocs) {
106+
extensionHandler.handleComponentBaseExtension(externalDocs, DataProductVersion.class);
107+
}
108+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.opendatamesh.dpds.extensions.visitorsimpl;
2+
3+
import com.fasterxml.jackson.core.JacksonException;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import org.opendatamesh.dpds.extensions.ComponentBaseExtendedConverter;
7+
import org.opendatamesh.dpds.extensions.DefinitionConverter;
8+
import org.opendatamesh.dpds.model.core.ComponentBase;
9+
import org.opendatamesh.dpds.model.core.StandardDefinition;
10+
11+
import java.util.*;
12+
13+
public class ExtensionHandler {
14+
15+
private final ExtensionHandlerStatus status;
16+
private final List<ComponentBaseExtendedConverter<ComponentBase>> componentBaseExtendedConverters;
17+
private final List<DefinitionConverter<ComponentBase>> definitionConverters;
18+
private final ObjectMapper mapper;
19+
20+
public ExtensionHandler(
21+
ExtensionHandlerStatus status,
22+
List<ComponentBaseExtendedConverter<ComponentBase>> componentBaseExtendedConverters,
23+
List<DefinitionConverter<ComponentBase>> definitionConverters,
24+
ObjectMapper mapper
25+
) {
26+
this.status = status;
27+
this.componentBaseExtendedConverters = componentBaseExtendedConverters;
28+
this.definitionConverters = definitionConverters;
29+
this.mapper = mapper;
30+
}
31+
32+
public void handleComponentBaseExtension(ComponentBase componentBase, Class<? extends ComponentBase> parentClazz) {
33+
try {
34+
switch (status) {
35+
case SERIALIZING:
36+
for (Map.Entry<String, ComponentBase> parsedProperty : componentBase.getParsedProperties().entrySet()) {
37+
Optional<ComponentBaseExtendedConverter<ComponentBase>> extendedComponentBaseConverter = findSupportedExtensionConverter(parentClazz, parsedProperty.getKey());
38+
if (extendedComponentBaseConverter.isPresent()) {
39+
componentBase.addAdditionalProperty(
40+
parsedProperty.getKey(),
41+
extendedComponentBaseConverter.get()
42+
.serialize(mapper, parsedProperty.getValue())
43+
);
44+
} else {
45+
throw new IllegalStateException("No ComponentBaseExtendedConverter has been registered on the Parser that can handle this property: " + parsedProperty.getKey() + " " + mapper.writeValueAsString(parsedProperty.getValue()));
46+
}
47+
}
48+
break;
49+
case DESERIALIZING:
50+
Set<String> keys = new HashSet<>(componentBase.getAdditionalProperties().keySet());
51+
for (String key : keys) {
52+
Optional<ComponentBaseExtendedConverter<ComponentBase>> extendedComponentBaseConverter = findSupportedExtensionConverter(parentClazz, key);
53+
if (extendedComponentBaseConverter.isPresent()) {
54+
ComponentBase c = extendedComponentBaseConverter.get().
55+
deserialize(mapper, componentBase.getAdditionalProperties().remove(key));
56+
componentBase.addParsedProperty(key, c);
57+
}
58+
}
59+
break;
60+
}
61+
} catch (JacksonException e) {
62+
throw new RuntimeException(e);
63+
}
64+
}
65+
66+
private Optional<ComponentBaseExtendedConverter<ComponentBase>> findSupportedExtensionConverter(
67+
Class<? extends ComponentBase> parentClazz, String key
68+
) {
69+
return componentBaseExtendedConverters.stream()
70+
.filter(e -> e.supports(key, parentClazz))
71+
.findFirst();
72+
}
73+
74+
private Optional<DefinitionConverter<ComponentBase>> findSupportedDefinitionConverter(
75+
String definition, String definitionVersion
76+
) {
77+
return definitionConverters.stream()
78+
.filter(e -> e.supports(definition, definitionVersion))
79+
.findFirst();
80+
}
81+
82+
public void handleDefinition(StandardDefinition standardDefinition) {
83+
Optional<DefinitionConverter<ComponentBase>> supportedDefinitionConverter = findSupportedDefinitionConverter(standardDefinition.getSpecification(), standardDefinition.getSpecificationVersion());
84+
if (supportedDefinitionConverter.isEmpty()) {
85+
return;
86+
}
87+
try {
88+
JsonNode rawDefinition;
89+
switch (status) {
90+
case SERIALIZING:
91+
rawDefinition = supportedDefinitionConverter.get().serialize(mapper, standardDefinition.getDefinition());
92+
standardDefinition.setDefinition(mapper.treeToValue(rawDefinition, ComponentBase.class));
93+
break;
94+
case DESERIALIZING:
95+
rawDefinition = mapper.valueToTree(standardDefinition.getDefinition());
96+
standardDefinition.setDefinition(supportedDefinitionConverter.get().deserialize(mapper, rawDefinition));
97+
break;
98+
}
99+
} catch (JacksonException e) {
100+
throw new RuntimeException(e);
101+
}
102+
}
103+
104+
public enum ExtensionHandlerStatus {
105+
SERIALIZING,
106+
DESERIALIZING
107+
}
108+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.opendatamesh.dpds.extensions.visitorsimpl;
2+
3+
public abstract class ExtensionVisitor {
4+
protected ExtensionVisitor parent;
5+
protected ExtensionHandler extensionHandler;
6+
7+
protected ExtensionVisitor(ExtensionVisitor parent) {
8+
this.parent = parent;
9+
if (parent != null) {
10+
this.extensionHandler = parent.extensionHandler;
11+
}
12+
}
13+
}

0 commit comments

Comments
 (0)