Skip to content

Commit

Permalink
More tidy up + adds a way to customise component URLs without using a…
Browse files Browse the repository at this point in the history
… script.
  • Loading branch information
simonbrowndotje committed Sep 17, 2024
1 parent e12096e commit 3872cfe
Show file tree
Hide file tree
Showing 16 changed files with 204 additions and 42 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ bin
hs_err_pid*

structurizr-dsl/src/test/resources/dsl/spring-petclinic/.structurizr
structurizr-dsl/src/test/resources/dsl/spring-petclinic/workspace.json
structurizr-dsl/src/test/resources/dsl/spring-petclinic/workspace.json

**/structurizr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public Set<Component> findComponents() {
}
component.setDescription(discoveredComponent.getDescription());
component.setTechnology(discoveredComponent.getTechnology());
component.setUrl(discoveredComponent.getUrl());
componentMap.put(discoveredComponent, component);
componentSet.add(component);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package com.structurizr.component;

import com.structurizr.component.description.DefaultDescriptionStrategy;
import com.structurizr.component.description.DescriptionStrategy;
import com.structurizr.component.filter.TypeFilter;
import com.structurizr.component.matcher.TypeMatcher;
import com.structurizr.component.naming.NamingStrategy;
import com.structurizr.component.supporting.SupportingTypesStrategy;
import com.structurizr.component.url.UrlStrategy;
import com.structurizr.component.visitor.ComponentVisitor;
import com.structurizr.model.Component;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

Expand All @@ -30,15 +29,17 @@ public final class ComponentFinderStrategy {
private final SupportingTypesStrategy supportingTypesStrategy;
private final NamingStrategy namingStrategy;
private final DescriptionStrategy descriptionStrategy;
private final UrlStrategy urlStrategy;
private final ComponentVisitor componentVisitor;

ComponentFinderStrategy(String technology, TypeMatcher typeMatcher, TypeFilter typeFilter, SupportingTypesStrategy supportingTypesStrategy, NamingStrategy namingStrategy, DescriptionStrategy descriptionStrategy, ComponentVisitor componentVisitor) {
ComponentFinderStrategy(String technology, TypeMatcher typeMatcher, TypeFilter typeFilter, SupportingTypesStrategy supportingTypesStrategy, NamingStrategy namingStrategy, DescriptionStrategy descriptionStrategy, UrlStrategy urlStrategy, ComponentVisitor componentVisitor) {
this.technology = technology;
this.typeMatcher = typeMatcher;
this.typeFilter = typeFilter;
this.supportingTypesStrategy = supportingTypesStrategy;
this.namingStrategy = namingStrategy;
this.descriptionStrategy = descriptionStrategy;
this.urlStrategy = urlStrategy;
this.componentVisitor = componentVisitor;
}

Expand All @@ -51,6 +52,7 @@ Set<DiscoveredComponent> findComponents(TypeRepository typeRepository) {
DiscoveredComponent component = new DiscoveredComponent(namingStrategy.nameOf(type), type);
component.setDescription(descriptionStrategy.descriptionOf(type));
component.setTechnology(this.technology);
component.setUrl(urlStrategy.urlOf(type));
component.setComponentFinderStrategy(this);
components.add(component);

Expand All @@ -76,6 +78,7 @@ public String toString() {
", supportingTypesStrategy=" + supportingTypesStrategy +
", namingStrategy=" + namingStrategy +
", descriptionStrategy=" + descriptionStrategy +
", urlStrategy=" + urlStrategy +
", componentVisitor=" + componentVisitor +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.structurizr.component.naming.NamingStrategy;
import com.structurizr.component.supporting.DefaultSupportingTypesStrategy;
import com.structurizr.component.supporting.SupportingTypesStrategy;
import com.structurizr.component.url.DefaultUrlStrategy;
import com.structurizr.component.url.UrlStrategy;
import com.structurizr.component.visitor.ComponentVisitor;
import com.structurizr.component.visitor.DefaultComponentVisitor;
import com.structurizr.util.StringUtils;
Expand All @@ -18,12 +20,13 @@
*/
public final class ComponentFinderStrategyBuilder {

private String technology;
private TypeMatcher typeMatcher;
private TypeFilter typeFilter;
private SupportingTypesStrategy supportingTypesStrategy;
private NamingStrategy namingStrategy;
private DescriptionStrategy descriptionStrategy;
private String technology;
private UrlStrategy urlStrategy;
private ComponentVisitor componentVisitor;

public ComponentFinderStrategyBuilder() {
Expand Down Expand Up @@ -99,7 +102,7 @@ public ComponentFinderStrategyBuilder withDescription(DescriptionStrategy descri
return this;
}

public ComponentFinderStrategyBuilder forTechnology(String technology) {
public ComponentFinderStrategyBuilder withTechnology(String technology) {
if (StringUtils.isNullOrEmpty(technology)) {
throw new IllegalArgumentException("A technology must be provided");
}
Expand All @@ -113,6 +116,20 @@ public ComponentFinderStrategyBuilder forTechnology(String technology) {
return this;
}

public ComponentFinderStrategyBuilder withUrl(UrlStrategy urlStrategy) {
if (urlStrategy == null) {
throw new IllegalArgumentException("A URL strategy must be provided");
}

if (this.urlStrategy != null) {
throw new IllegalArgumentException("A url strategy has already been configured");
}

this.urlStrategy = urlStrategy;

return this;
}

public ComponentFinderStrategyBuilder forEach(ComponentVisitor componentVisitor) {
if (componentVisitor == null) {
throw new IllegalArgumentException("A component visitor must be provided");
Expand Down Expand Up @@ -148,11 +165,15 @@ public ComponentFinderStrategy build() {
descriptionStrategy = new DefaultDescriptionStrategy();
}

if (urlStrategy == null) {
urlStrategy = new DefaultUrlStrategy();
}

if (componentVisitor == null) {
componentVisitor = new DefaultComponentVisitor();
}

return new ComponentFinderStrategy(technology, typeMatcher, typeFilter, supportingTypesStrategy, namingStrategy, descriptionStrategy, componentVisitor);
return new ComponentFinderStrategy(technology, typeMatcher, typeFilter, supportingTypesStrategy, namingStrategy, descriptionStrategy, urlStrategy, componentVisitor);
}

@Override
Expand All @@ -164,6 +185,7 @@ public String toString() {
", supportingTypesStrategy=" + supportingTypesStrategy +
", namingStrategy=" + namingStrategy +
", descriptionStrategy=" + descriptionStrategy +
", urlStrategy=" + urlStrategy +
", componentVisitor=" + componentVisitor +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ final class DiscoveredComponent {
private final String name;
private String description;
private String technology;
private String url;
private final Set<Type> supportingTypes = new HashSet<>();

private ComponentFinderStrategy componentFinderStrategy;
Expand Down Expand Up @@ -46,6 +47,14 @@ void setTechnology(String technology) {
this.technology = technology;
}

String getUrl() {
return url;
}

void setUrl(String url) {
this.url = url;
}

Set<Type> getSupportingTypes() {
return new HashSet<>(supportingTypes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void visit(ClassOrInterfaceDeclaration n, Object arg) {
if (n.getFullyQualifiedName().isPresent()) {
String fullyQualifiedName = n.getFullyQualifiedName().get();
Type type = new Type(fullyQualifiedName);
type.setSource(path.getAbsolutePath());
type.setSource(relativePath(path));

if (n.getComment().isPresent() && n.getComment().get() instanceof JavadocComment) {
JavadocComment javadocComment = (JavadocComment) n.getComment().get();
Expand All @@ -93,7 +93,7 @@ public void visit(PackageDeclaration n, Object arg) {
String fullyQualifiedName = n.getName().asString() + PACKAGE_INFO_SUFFIX;

Type type = new Type(fullyQualifiedName);
type.setSource(path.getAbsolutePath());
type.setSource(relativePath(path));

Node rootNode = n.findRootNode();
if (rootNode != null && rootNode.getComment().isPresent() && rootNode.getComment().get() instanceof JavadocComment) {
Expand All @@ -116,4 +116,16 @@ public void visit(PackageDeclaration n, Object arg) {
}
}

private String relativePath(File path) {
String relativePath = path.getAbsolutePath().replace(directory.getAbsolutePath(), "");

String pathSeparator = System.getProperty("file.separator");

if (relativePath.startsWith(pathSeparator)) {
relativePath = relativePath.substring(1);
}

return relativePath;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.structurizr.component.url;

import com.structurizr.component.Type;

public class DefaultUrlStrategy implements UrlStrategy {

@Override
public String urlOf(Type type) {
return null;
}

@Override
public String toString() {
return "DefaultUrlStrategy{}";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.structurizr.component.url;

import com.structurizr.component.Type;
import com.structurizr.util.StringUtils;

public class PrefixUrlStrategy implements UrlStrategy {

private final String prefix;

public PrefixUrlStrategy(String prefix) {
if (StringUtils.isNullOrEmpty(prefix)) {
throw new IllegalArgumentException("A prefix must be supplied");
}

if (!prefix.endsWith("/")) {
prefix = prefix + "/";
}

this.prefix = prefix;
}

@Override
public String urlOf(Type type) {
return prefix + type.getSource();
}

@Override
public String toString() {
return "PrefixUrlStrategy{" +
"prefix='" + prefix + '\'' +
'}';
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.structurizr.component.url;

import com.structurizr.component.Type;

public interface UrlStrategy {

String urlOf(Type type);

}
Original file line number Diff line number Diff line change
Expand Up @@ -116,29 +116,29 @@ void withDescription_ThrowsAnException_WhenCalledTwice() {
}

@Test
void forTechnology_ThrowsAnException_WhenPassedNull() {
void withTechnology_ThrowsAnException_WhenPassedNull() {
try {
new ComponentFinderStrategyBuilder().forTechnology(null);
new ComponentFinderStrategyBuilder().withTechnology(null);
fail();
} catch (Exception e) {
assertEquals("A technology must be provided", e.getMessage());
}
}

@Test
void forTechnology_ThrowsAnException_WhenPassedAnEmptyString() {
void withTechnology_ThrowsAnException_WhenPassedAnEmptyString() {
try {
new ComponentFinderStrategyBuilder().forTechnology("");
new ComponentFinderStrategyBuilder().withTechnology("");
fail();
} catch (Exception e) {
assertEquals("A technology must be provided", e.getMessage());
}
}

@Test
void forTechnology_ThrowsAnException_WhenCalledTwice() {
void withTechnology_ThrowsAnException_WhenCalledTwice() {
try {
new ComponentFinderStrategyBuilder().forTechnology("X").forTechnology("Y");
new ComponentFinderStrategyBuilder().withTechnology("X").withTechnology("Y");
fail();
} catch (Exception e) {
assertEquals("A technology has already been configured", e.getMessage());
Expand All @@ -158,14 +158,14 @@ void build_ThrowsAnException_WhenATypeMatcherHasNotBeenConfigured() {
@Test
void build() {
ComponentFinderStrategy strategy = new ComponentFinderStrategyBuilder()
.forTechnology("Spring MVC Controller")
.withTechnology("Spring MVC Controller")
.matchedBy(new NameSuffixTypeMatcher("Controller"))
.filteredBy(new IncludeTypesByRegexFilter("com.example.web.\\.*"))
.withName(new TypeNamingStrategy())
.withDescription(new FirstSentenceDescriptionStrategy())
.build();

assertEquals("ComponentFinderStrategy{technology='Spring MVC Controller', typeMatcher=NameSuffixTypeMatcher{suffix='Controller'}, typeFilter=IncludeTypesByRegexFilter{regex='com.example.web.\\.*'}, supportingTypesStrategy=DefaultSupportingTypesStrategy{}, namingStrategy=TypeNamingStrategy{}, descriptionStrategy=FirstSentenceDescriptionStrategy{}, componentVisitor=DefaultComponentVisitor{}}", strategy.toString());
assertEquals("ComponentFinderStrategy{technology='Spring MVC Controller', typeMatcher=NameSuffixTypeMatcher{suffix='Controller'}, typeFilter=IncludeTypesByRegexFilter{regex='com.example.web.\\.*'}, supportingTypesStrategy=DefaultSupportingTypesStrategy{}, namingStrategy=TypeNamingStrategy{}, descriptionStrategy=FirstSentenceDescriptionStrategy{}, urlStrategy=DefaultUrlStrategy{}, componentVisitor=DefaultComponentVisitor{}}", strategy.toString());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.structurizr.component.naming.TypeNamingStrategy;
import com.structurizr.component.naming.FullyQualifiedNamingStrategy;
import com.structurizr.component.supporting.*;
import com.structurizr.component.url.PrefixUrlStrategy;

import java.io.File;
import java.util.List;
Expand Down Expand Up @@ -51,13 +52,17 @@ final class ComponentFinderStrategyParser extends AbstractParser {
private static final String DESCRIPTION_GRAMMAR = "description <" + String.join("|", List.of(DESCRIPTION_FIRST_SENTENCE, DESCRIPTION_TRUNCATED)) + ">";
private static final String DESCRIPTION_TRUNCATED_GRAMMAR = "description truncated <maxLength>";

private static final String URL_PREFIX = "prefix";
private static final String URL_GRAMMAR = "url <" + String.join("|", List.of(URL_PREFIX)) + ">";
private static final String URL_PREFIX_GRAMMAR = "url prefix <prefix>";

void parseTechnology(ComponentFinderStrategyDslContext context, Tokens tokens) {
if (tokens.size() != 2) {
throw new RuntimeException("Expected: " + TECHNOLOGY_GRAMMAR);
}

String name = tokens.get(1);
context.getComponentFinderStrategyBuilder().forTechnology(name);
context.getComponentFinderStrategyBuilder().withTechnology(name);
}

void parseMatcher(ComponentFinderStrategyDslContext context, Tokens tokens, File dslFile) {
Expand Down Expand Up @@ -236,4 +241,24 @@ void parseDescription(ComponentFinderStrategyDslContext context, Tokens tokens,
}
}

void parseUrl(ComponentFinderStrategyDslContext context, Tokens tokens, File dslFile) {
if (tokens.size() < 2) {
throw new RuntimeException("Too few tokens, expected: " + URL_GRAMMAR);
}

String type = tokens.get(1).toLowerCase();
switch (type) {
case URL_PREFIX:
if (tokens.size() < 3) {
throw new RuntimeException("Too few tokens, expected: " + URL_PREFIX_GRAMMAR);
}

String prefix = tokens.get(2);
context.getComponentFinderStrategyBuilder().withUrl(new PrefixUrlStrategy(prefix));
break;
default:
throw new IllegalArgumentException("Unknown URL strategy: " + type);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,9 @@ void parse(List<String> lines, File dslFile, boolean fragment, boolean includeIn
} else if (COMPONENT_FINDER_STRATEGY_DESCRIPTION_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
new ComponentFinderStrategyParser().parseDescription(getContext(ComponentFinderStrategyDslContext.class), tokens, dslFile);

} else if (COMPONENT_FINDER_STRATEGY_URL_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
new ComponentFinderStrategyParser().parseUrl(getContext(ComponentFinderStrategyDslContext.class), tokens, dslFile);

} else if (COMPONENT_FINDER_STRATEGY_FOREACH_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
if (shouldStartContext(tokens)) {
startContext(new ComponentFinderStrategyForEachDslContext(getContext(ComponentFinderStrategyDslContext.class), this));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class StructurizrDslTokens {
static final String COMPONENT_FINDER_STRATEGY_SUPPORTING_TYPES_TOKEN = "supportingTypes";
static final String COMPONENT_FINDER_STRATEGY_NAME_TOKEN = "name";
static final String COMPONENT_FINDER_STRATEGY_DESCRIPTION_TOKEN = "description";
static final String COMPONENT_FINDER_STRATEGY_URL_TOKEN = "url";
static final String COMPONENT_FINDER_STRATEGY_FOREACH_TOKEN = "forEach";

}
Loading

0 comments on commit 3872cfe

Please sign in to comment.