From 79244faf5e7ac2db73eb5d2136d3e44a0f7697c0 Mon Sep 17 00:00:00 2001 From: Florian Bossert Date: Thu, 30 Nov 2023 21:01:43 +0100 Subject: [PATCH] Add Zuul Proxy Rule --- .../analyzer/rules/model/Requirements.java | 19 ++- .../plugin.xml | 8 ++ .../analyzer/rules/impl/SpringRules.xtend | 96 ++------------ .../analyzer/rules/impl/SpringZuulRules.xtend | 125 ++++++++++++++++++ .../rules/impl/util/SpringHelper.xtend | 94 +++++++++++++ 5 files changed, 248 insertions(+), 94 deletions(-) create mode 100644 bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/SpringZuulRules.xtend create mode 100644 bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/util/SpringHelper.xtend diff --git a/bundles/org.palladiosimulator.somox.analyzer.rules.engine/src/org/palladiosimulator/somox/analyzer/rules/model/Requirements.java b/bundles/org.palladiosimulator.somox.analyzer.rules.engine/src/org/palladiosimulator/somox/analyzer/rules/model/Requirements.java index ce0844d7..770586fe 100644 --- a/bundles/org.palladiosimulator.somox.analyzer.rules.engine/src/org/palladiosimulator/somox/analyzer/rules/model/Requirements.java +++ b/bundles/org.palladiosimulator.somox.analyzer.rules.engine/src/org/palladiosimulator/somox/analyzer/rules/model/Requirements.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; @@ -69,17 +68,15 @@ public Map> simplified() { .flatMap(x -> x.stream()) .collect(Collectors.toList())); for (OperationInterface member : groupedRequirements.get(root)) { - simplifiedRoot.addAll(member.simplified() - .values() - .stream() - .flatMap(x -> x.stream()) - .collect(Collectors.toList())); + simplifiedRoot.addAll(member.simplified() + .values() + .stream() + .flatMap(x -> x.stream()) + .collect(Collectors.toList())); } - simplifiedInterfaces.add(Map.of( - root, - simplifiedRoot.stream() - .distinct() - .collect(Collectors.toList()))); + simplifiedInterfaces.add(Map.of(root, simplifiedRoot.stream() + .distinct() + .collect(Collectors.toList()))); } return MapMerger.merge(simplifiedInterfaces); } diff --git a/bundles/org.palladiosimulator.somox.analyzer.rules.impl/plugin.xml b/bundles/org.palladiosimulator.somox.analyzer.rules.impl/plugin.xml index 4cbc6bb6..54db5af7 100644 --- a/bundles/org.palladiosimulator.somox.analyzer.rules.impl/plugin.xml +++ b/bundles/org.palladiosimulator.somox.analyzer.rules.impl/plugin.xml @@ -49,5 +49,13 @@ class="org.palladiosimulator.somox.analyzer.rules.impl.SpringRules"> + + + + diff --git a/bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/SpringRules.xtend b/bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/SpringRules.xtend index 05474b4f..fd374f8e 100644 --- a/bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/SpringRules.xtend +++ b/bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/SpringRules.xtend @@ -6,7 +6,6 @@ import java.nio.file.Path import org.eclipse.jdt.core.dom.CompilationUnit import java.util.Map; import org.jdom2.Document -import java.util.stream.Collectors import org.apache.log4j.Logger import org.eclipse.jdt.core.dom.MethodDeclaration import org.eclipse.jdt.core.dom.ITypeBinding @@ -20,6 +19,7 @@ import org.palladiosimulator.somox.analyzer.rules.model.CompUnitOrName import java.util.function.Function import java.util.Set import org.palladiosimulator.somox.analyzer.rules.engine.Rule +import org.palladiosimulator.somox.analyzer.rules.impl.util.SpringHelper class SpringRules implements Rule { static final Logger LOG = Logger.getLogger(SpringRules) @@ -30,6 +30,7 @@ class SpringRules implements Rule { public static final String YAML_MAPPERS_KEY = YAML_DISCOVERER_ID + ".mappers" public static final String XML_DISCOVERER_ID = "org.palladiosimulator.somox.discoverer.xml" public static final String PROPERTIES_DISCOVERER_ID = "org.palladiosimulator.somox.discoverer.properties" + public static final String ZUUL_RULE_ID = "org.palladiosimulator.somox.analyzer.rules.impl.spring.zuul"; override processRules(RuleEngineBlackboard blackboard, Path path) { val unit = blackboard.getDiscoveredFiles(JAVA_DISCOVERER_ID, typeof(CompilationUnit)).get(path) @@ -40,102 +41,31 @@ class SpringRules implements Rule { val poms = blackboard.getDiscoveredFiles(XML_DISCOVERER_ID, typeof(Document)) val propertyFiles = blackboard.getDiscoveredFiles(PROPERTIES_DISCOVERER_ID, typeof(Properties)) - val projectRoot = findProjectRoot(path, poms) - val configRoot = findConfigRoot(poms) + val projectRoot = SpringHelper.findProjectRoot(path, poms) + val configRoot = SpringHelper.findConfigRoot(poms) val bootstrapYaml = yamlMappers.get( - findFile(yamlMappers.keySet, projectRoot.resolve("src/main/resources"), + SpringHelper.findFile(yamlMappers.keySet, projectRoot.resolve("src/main/resources"), Set.of("bootstrap.yaml", "bootstrap.yml"))) val applicationProperties = propertyFiles.get( - findFile(propertyFiles.keySet, projectRoot.resolve("src/main/resources"), Set.of("application.properties"))) + SpringHelper.findFile(propertyFiles.keySet, projectRoot.resolve("src/main/resources"), + Set.of("application.properties"))) - val applicationName = getFromYamlOrProperties("spring.application.name", bootstrapYaml, applicationProperties) + val applicationName = SpringHelper.getFromYamlOrProperties("spring.application.name", bootstrapYaml, + applicationProperties) val projectConfigYaml = yamlMappers.get( - findFile(yamlMappers.keySet, configRoot, Set.of(applicationName + ".yaml", applicationName + ".yml"))) + SpringHelper.findFile(yamlMappers.keySet, configRoot.resolve("src/main/resources/shared"), + Set.of(applicationName + ".yaml", applicationName + ".yml"))) val contextPathOption = Optional.ofNullable(projectConfigYaml).flatMap[x|x.apply("server.servlet.context-path")] var contextPath = contextPathOption.orElse("/") val rawApplicationYaml = rawYamls.get( - findFile(yamlMappers.keySet, projectRoot.resolve("src/main/resources"), + SpringHelper.findFile(yamlMappers.keySet, projectRoot.resolve("src/main/resources"), Set.of("application.yaml", "application.yml"))) val contextVariables = collectContextVariables(rawApplicationYaml) processRuleForCompUnit(blackboard, unit, applicationName, contextPath, contextVariables) } - def findProjectRoot(Path currentPath, Map poms) { - if (currentPath === null || poms === null) { - return null - } - val closestPom = poms.entrySet.stream.map([entry|entry.key]) // Only keep poms above the current compilation unit. - .filter([path|currentPath.startsWith(path.parent)]) // Take the longest path, which is the pom.xml closest to the compilation unit - .max([a, b|a.size.compareTo(b.size)]) - - if (closestPom.present) { - return closestPom.get.parent - } else { - return null - } - } - - def findConfigRoot(Map poms) { - if (poms === null) { - return null - } - val configRoots = poms.entrySet.stream.map [ entry | - entry.key -> entry.value.rootElement.getChild("dependencies", entry.value.rootElement.namespace) - ].filter[entry|entry.value !== null].map [ entry | - entry.key -> entry.value.getChildren("dependency", entry.value.namespace) - ].filter[entry|!entry.value.empty].filter [ entry | - entry.value.filter [ dependency | - dependency.getChildTextTrim("groupId", dependency.namespace) == "org.springframework.cloud" - ].exists [ dependency | - dependency.getChildTextTrim("artifactId", dependency.namespace) == "spring-cloud-config-server" - ] - ].collect(Collectors.toList) - - if (configRoots.size > 1) { - LOG.warn("Multiple Spring config servers, choosing \"" + configRoots.get(0).key.parent + "\" arbitrarily") - } else if (configRoots.empty) { - return null - } - return configRoots.get(0).key.parent - } - - def findFile(Set paths, Path directory, Set possibleNames) { - if (paths === null || directory === null || possibleNames === null) { - return null - } - val candidates = paths.stream.filter[path|path.parent == directory].filter [ path | - possibleNames.contains(path.fileName.toString) - ].collect(Collectors.toList) - - if (candidates.size > 1) { - // fileName must exist since candidates were found - val fileName = possibleNames.iterator.next; - LOG.warn( - "Multiple " + fileName + " in " + directory + ", choosing " + directory.relativize(candidates.get(0)) + - " arbitrarily") - } else if (candidates.empty) { - return null - } - return candidates.get(0) - } - - def getFromYamlOrProperties(String key, Function> yamlMapper, Properties properties) { - if (yamlMapper !== null) { - val result = yamlMapper.apply(key) - if (result.present) { - return result.get() - } - } - - if (properties !== null) { - return properties.getProperty(key) - } - - return null - } - def Map collectContextVariables(Iterable> applicationYaml) { val result = new HashMap(); if (applicationYaml === null || applicationYaml.empty) { @@ -428,7 +358,7 @@ class SpringRules implements Rule { } override getRequiredServices() { - return Set.of(JAVA_DISCOVERER_ID, YAML_DISCOVERER_ID, XML_DISCOVERER_ID, PROPERTIES_DISCOVERER_ID) + return Set.of(JAVA_DISCOVERER_ID, YAML_DISCOVERER_ID, XML_DISCOVERER_ID, PROPERTIES_DISCOVERER_ID, ZUUL_RULE_ID) } } diff --git a/bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/SpringZuulRules.xtend b/bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/SpringZuulRules.xtend new file mode 100644 index 00000000..fd1badc5 --- /dev/null +++ b/bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/SpringZuulRules.xtend @@ -0,0 +1,125 @@ +package org.palladiosimulator.somox.analyzer.rules.impl + +import java.nio.file.Path +import java.util.ArrayList +import java.util.HashMap +import java.util.List +import java.util.Map +import java.util.Optional +import java.util.Properties +import java.util.Set +import java.util.function.Function +import org.apache.log4j.Logger +import org.jdom2.Document +import org.palladiosimulator.somox.analyzer.rules.blackboard.RuleEngineBlackboard +import org.palladiosimulator.somox.analyzer.rules.engine.Rule +import org.palladiosimulator.somox.analyzer.rules.impl.util.SpringHelper + +class SpringZuulRules implements Rule { + static final Logger LOG = Logger.getLogger(SpringZuulRules) + + public static final String RULE_ID = "org.palladiosimulator.somox.analyzer.rules.impl.spring.zuul" + public static final String JAVA_DISCOVERER_ID = "org.palladiosimulator.somox.discoverer.java" + public static final String YAML_DISCOVERER_ID = "org.palladiosimulator.somox.discoverer.yaml" + public static final String YAML_MAPPERS_KEY = YAML_DISCOVERER_ID + ".mappers" + public static final String XML_DISCOVERER_ID = "org.palladiosimulator.somox.discoverer.xml" + public static final String PROPERTIES_DISCOVERER_ID = "org.palladiosimulator.somox.discoverer.properties" + + override processRules(RuleEngineBlackboard blackboard, Path path) { + val rawYamls = blackboard.getPartition(YAML_DISCOVERER_ID) as Map>> + val yamlMappers = blackboard.getPartition(YAML_MAPPERS_KEY) as Map>> + val poms = blackboard.getDiscoveredFiles(XML_DISCOVERER_ID, typeof(Document)) + val propertyFiles = blackboard.getDiscoveredFiles(PROPERTIES_DISCOVERER_ID, typeof(Properties)) + + val projectRoot = SpringHelper.findProjectRoot(path, poms) + val configRoot = SpringHelper.findConfigRoot(poms) + + var routeMap = blackboard.getPartition(RULE_ID) as Map> + if (routeMap === null) { + routeMap = new HashMap>() + } + + // Execute only once for each Spring application/service + if(routeMap.containsKey(projectRoot)) return + + val bootstrapYaml = yamlMappers.get( + SpringHelper.findFile(yamlMappers.keySet, projectRoot.resolve("src/main/resources"), + Set.of("bootstrap.yaml", "bootstrap.yml"))) + val applicationProperties = propertyFiles.get( + SpringHelper.findFile(propertyFiles.keySet, projectRoot.resolve("src/main/resources"), + Set.of("application.properties"))) + val applicationName = SpringHelper.getFromYamlOrProperties("spring.application.name", bootstrapYaml, + applicationProperties) + val projectConfigYaml = rawYamls.get( + SpringHelper.findFile(rawYamls.keySet, configRoot.resolve("src/main/resources/shared"), + Set.of(applicationName + ".yaml", applicationName + ".yml"))) + + // Query zuul.routes in config server only (for now) + val routes = collectRoutes(projectConfigYaml) + for (route : routes) { + LOG.warn("Route in " + applicationName + ": " + route.path + " -> " + route.serviceId) + } + routeMap.put(projectRoot, routes) + blackboard.addPartition(RULE_ID, routeMap) + } + + def List collectRoutes(Iterable> applicationYamlIter) { + val result = new ArrayList() + + if(applicationYamlIter === null || applicationYamlIter.empty) return result + + val applicationYaml = applicationYamlIter.get(0) as Map + if(applicationYaml === null) return result + + val zuulObject = applicationYaml.get("zuul") + if(!(zuulObject instanceof Map)) return result + val zuul = zuulObject as Map + + val routesObject = zuul.get("routes") + if(!(routesObject instanceof Map)) return result + val routes = routesObject as Map> + + for (route : routes.values) { + val pathObject = route.get("path") + val serviceIdObject = route.get("serviceId") + if (pathObject !== null && serviceIdObject !== null && pathObject instanceof String && + serviceIdObject instanceof String) { + val path = pathObject as String + val serviceId = serviceIdObject as String + result.add(new Route(path, serviceId)) + } + } + + return result; + } + + override isBuildRule() { + return false + } + + override getConfigurationKeys() { + return Set.of + } + + override getID() { + return RULE_ID + } + + override getName() { + return "Spring Zuul Rules" + } + + override getRequiredServices() { + return Set.of(JAVA_DISCOVERER_ID, YAML_DISCOVERER_ID, XML_DISCOVERER_ID, PROPERTIES_DISCOVERER_ID) + } + + static class Route { + public String path + public String serviceId + + new(String path, String serviceId) { + this.path = path + this.serviceId = serviceId + } + } +} diff --git a/bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/util/SpringHelper.xtend b/bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/util/SpringHelper.xtend new file mode 100644 index 00000000..315ed4a2 --- /dev/null +++ b/bundles/org.palladiosimulator.somox.analyzer.rules.impl/src/org/palladiosimulator/somox/analyzer/rules/impl/util/SpringHelper.xtend @@ -0,0 +1,94 @@ +package org.palladiosimulator.somox.analyzer.rules.impl.util + +import java.nio.file.Path +import java.util.Map +import java.util.Optional +import java.util.Properties +import java.util.Set +import java.util.function.Function +import java.util.stream.Collectors +import org.jdom2.Document +import org.apache.log4j.Logger + +final class SpringHelper { + static final Logger LOG = Logger.getLogger(SpringHelper) + + new() { + throw new IllegalAccessException() + } + + static def findProjectRoot(Path currentPath, Map poms) { + if (currentPath === null || poms === null) { + return null + } + val closestPom = poms.entrySet.stream.map([entry|entry.key]) // Only keep poms above the current compilation unit. + .filter([path|currentPath.startsWith(path.parent)]) // Take the longest path, which is the pom.xml closest to the compilation unit + .max([a, b|a.size.compareTo(b.size)]) + + if (closestPom.present) { + return closestPom.get.parent + } else { + return null + } + } + + static def findConfigRoot(Map poms) { + if (poms === null) { + return null + } + val configRoots = poms.entrySet.stream.map [ entry | + entry.key -> entry.value.rootElement.getChild("dependencies", entry.value.rootElement.namespace) + ].filter[entry|entry.value !== null].map [ entry | + entry.key -> entry.value.getChildren("dependency", entry.value.namespace) + ].filter[entry|!entry.value.empty].filter [ entry | + entry.value.filter [ dependency | + dependency.getChildTextTrim("groupId", dependency.namespace) == "org.springframework.cloud" + ].exists [ dependency | + dependency.getChildTextTrim("artifactId", dependency.namespace) == "spring-cloud-config-server" + ] + ].collect(Collectors.toList) + + if (configRoots.size > 1) { + LOG.warn("Multiple Spring config servers, choosing \"" + configRoots.get(0).key.parent + "\" arbitrarily") + } else if (configRoots.empty) { + return null + } + return configRoots.get(0).key.parent + } + + static def findFile(Set paths, Path directory, Set possibleNames) { + if (paths === null || directory === null || possibleNames === null) { + return null + } + val candidates = paths.stream.filter[path|path.parent == directory].filter [ path | + possibleNames.contains(path.fileName.toString) + ].collect(Collectors.toList) + + if (candidates.size > 1) { + // fileName must exist since candidates were found + val fileName = possibleNames.iterator.next; + LOG.warn( + "Multiple " + fileName + " in " + directory + ", choosing " + directory.relativize(candidates.get(0)) + + " arbitrarily") + } else if (candidates.empty) { + return null + } + return candidates.get(0) + } + + static def getFromYamlOrProperties(String key, Function> yamlMapper, + Properties properties) { + if (yamlMapper !== null) { + val result = yamlMapper.apply(key) + if (result.present) { + return result.get() + } + } + + if (properties !== null) { + return properties.getProperty(key) + } + + return null + } +}