Skip to content

Commit

Permalink
resolves #1268 support Structurizr themes (#1289)
Browse files Browse the repository at this point in the history
  • Loading branch information
ggrossetie authored Jul 19, 2022
1 parent eb1f6f2 commit 5bd5fa1
Show file tree
Hide file tree
Showing 9 changed files with 4,863 additions and 1 deletion.
139 changes: 138 additions & 1 deletion server/src/main/java/io/kroki/server/service/Structurizr.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
import com.structurizr.dsl.StructurizrDslParserException;
import com.structurizr.export.Diagram;
import com.structurizr.export.plantuml.StructurizrPlantUMLExporter;
import com.structurizr.view.Border;
import com.structurizr.view.ComponentView;
import com.structurizr.view.ContainerView;
import com.structurizr.view.DeploymentView;
import com.structurizr.view.DynamicView;
import com.structurizr.view.ElementStyle;
import com.structurizr.view.RelationshipStyle;
import com.structurizr.view.Shape;
import com.structurizr.view.SystemContextView;
import com.structurizr.view.SystemLandscapeView;
import com.structurizr.view.View;
import com.structurizr.view.ViewSet;
import io.kroki.server.decode.DiagramSource;
import io.kroki.server.decode.SourceDecoder;
import io.kroki.server.error.BadRequestException;
Expand All @@ -20,13 +25,22 @@
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public class Structurizr implements DiagramService {

Expand All @@ -37,6 +51,12 @@ public class Structurizr implements DiagramService {
// same as PlantUML since we convert Structurizr DSL to PlantUML
private static final List<FileFormat> SUPPORTED_FORMATS = Arrays.asList(FileFormat.PNG, FileFormat.SVG, FileFormat.JPEG, FileFormat.BASE64, FileFormat.TXT, FileFormat.UTXT);

private static final String aws = read("structurizr/amazon-web-services.json");
private static final String gcp = read("structurizr/google-cloud-platform.json");
private static final String k8s = read("structurizr/kubernetes.json");
private static final String azure = read("structurizr/microsoft-azure.json");
private static final String oracleCloud = read("structurizr/oracle-cloud-infrastructure.json");

public Structurizr(Vertx vertx) {
this.vertx = vertx;
this.structurizrPlantUMLExporter = new StructurizrPlantUMLExporter();
Expand Down Expand Up @@ -79,7 +99,8 @@ static byte[] convert(String source, FileFormat fileFormat, StructurizrPlantUMLE
StructurizrDslParser parser = new StructurizrDslParser();
try {
parser.parse(source);
Collection<View> views = parser.getWorkspace().getViews().getViews();
ViewSet viewSet = parser.getWorkspace().getViews();
Collection<View> views = viewSet.getViews();
if (views.isEmpty()) {
throw new BadRequestException("Empty diagram, does not have any view.");
}
Expand All @@ -95,6 +116,14 @@ static byte[] convert(String source, FileFormat fileFormat, StructurizrPlantUMLE
// take the first view if not specified
selectedView = views.iterator().next();
}
for (String url : viewSet.getConfiguration().getThemes()) {
if (url.startsWith("https://static.structurizr.com/themes/")) {
String themeContent = getThemeContent(url);
if (themeContent != null) {
applyTheme(viewSet, themeContent);
}
}
}
final Diagram diagram;
if (selectedView instanceof DynamicView) {
diagram = structurizrPlantUMLExporter.export((DynamicView) selectedView);
Expand Down Expand Up @@ -123,4 +152,112 @@ static byte[] convert(String source, FileFormat fileFormat, StructurizrPlantUMLE
throw new BadRequestException(message, e);
}
}

private static void applyTheme(ViewSet viewSet, String themeContent) {
Object value = Json.decodeValue(themeContent);
if (value instanceof JsonObject) {
List<ElementStyle> elementStyles = getElementStyles((JsonObject) value);
for (ElementStyle elementStyle : elementStyles) {
viewSet.getConfiguration().getStyles().add(elementStyle);
}
List<RelationshipStyle> relationshipStyles = getRelationshipStyle((JsonObject) value);
for (RelationshipStyle relationshipStyle : relationshipStyles) {
viewSet.getConfiguration().getStyles().add(relationshipStyle);
}
}
}

private static List<ElementStyle> getElementStyles(JsonObject value) {
List<ElementStyle> result = new ArrayList<>();
Object elementsObject = value.getValue("elements");
if (elementsObject instanceof JsonArray) {
for (Object elementObject : ((JsonArray) elementsObject).getList()) {
if (elementObject instanceof Map) {
JsonObject element = new JsonObject((Map<String, Object>) elementObject);
ElementStyle elementStyle = new ElementStyle(
element.getString("tag"),
element.getInteger("width"),
element.getInteger("height"),
element.getString("background", "#FFFFFF"), // remind: cannot pass a null value
element.getString("color", "#000000"), // remind: cannot pass a null value
element.getInteger("fontSize")
);
elementStyle.setBorder(getBorder(element));
elementStyle.setStroke(element.getString("stroke"));
elementStyle.setShape(getShape(element));
elementStyle.setIcon(element.getString("icon"));
elementStyle.setOpacity(element.getInteger("opacity"));
elementStyle.setMetadata(element.getBoolean("metadata"));
elementStyle.setDescription(element.getBoolean("description"));
result.add(elementStyle);
}
}
}
return result;
}

private static String getThemeContent(String url) {
if (url.contains("amazon-web-services")) {
return aws;
}
if (url.contains("google-cloud-platform")) {
return gcp;
}
if (url.contains("kubernetes")) {
return k8s;
}
if (url.contains("microsoft-azure")) {
return azure;
}
if (url.contains("oracle-cloud-infrastructure")) {
return oracleCloud;
}
return null;
}

private static List<RelationshipStyle> getRelationshipStyle(JsonObject value) {
List<RelationshipStyle> result = new ArrayList<>();
// remind: RelationshipStyle does not have a public constructor, as a result, we cannot instantiate it.
return result;
}

private static String read(String resource) {
InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
try {
if (input == null) {
throw new IOException("Unable to get resource: " + resource);
}
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(input))) {
return buffer.lines().collect(Collectors.joining("\n"));
}
} catch (IOException e) {
throw new RuntimeException("Unable to initialize the Structurizr service", e);
}
}

private static Shape getShape(JsonObject element) {
String shapeValue = element.getString("shape");
if (shapeValue == null) {
return null;
}
try {
return Shape.valueOf(shapeValue);
} catch (IllegalArgumentException e) {
// ignore!
return null;
}
}

private static Border getBorder(JsonObject element) {
String borderValue = element.getString("border");
if (borderValue == null) {
return null;
}
try {
return Border.valueOf(borderValue);
} catch (IllegalArgumentException e) {
// ignore!
return null;
}
}
}
Loading

0 comments on commit 5bd5fa1

Please sign in to comment.