You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2020/09/09 11:08:54 UTC
[isis] branch master updated: ISIS-2426: tooling: adds project tree
(modeling)
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/master by this push:
new 5d2468c ISIS-2426: tooling: adds project tree (modeling)
5d2468c is described below
commit 5d2468c4feaa4afe55b89f8c24a0eeef048282d5
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Sep 9 13:08:36 2020 +0200
ISIS-2426: tooling: adds project tree (modeling)
---
tooling/project-model/pom.xml | 18 +-
.../isis/tooling/projectmodel/ProjectNode.java | 46 +++++
.../tooling/projectmodel/ProjectNodeFactory.java | 116 +++++++++++++
.../isis/tooling/projectmodel/ProjectVisitor.java | 27 +++
.../projectmodel/maven/MavenModelFactory.java | 99 +++++++++++
.../projectmodel/maven/SimpleModelResolver.java | 190 +++++++++++++++++++++
.../project-model/src/main/resources/log4j2.xml | 16 ++
.../tooling/projectmodel/test/ProjectTreeTest.java | 71 ++++++++
8 files changed, 582 insertions(+), 1 deletion(-)
diff --git a/tooling/project-model/pom.xml b/tooling/project-model/pom.xml
index f08923e..8f4d7b1 100644
--- a/tooling/project-model/pom.xml
+++ b/tooling/project-model/pom.xml
@@ -19,7 +19,7 @@
<artifactId>isis-tooling</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
-
+
<artifactId>isis-tooling-project-model</artifactId>
<name>Apache Isis Tooling - Project Model</name>
@@ -31,6 +31,22 @@
</properties>
<dependencies>
+
+ <dependency>
+ <groupId>org.apache.isis.tooling</groupId>
+ <artifactId>isis-tooling-commons</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.gradle</groupId>
+ <artifactId>gradle-tooling-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-model-builder</artifactId>
+ </dependency>
+
</dependencies>
</project>
diff --git a/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/ProjectNode.java b/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/ProjectNode.java
new file mode 100644
index 0000000..5de56d3
--- /dev/null
+++ b/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/ProjectNode.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.tooling.projectmodel;
+
+import java.util.TreeSet;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.ToString;
+import lombok.val;
+
+@Data @Builder
+public class ProjectNode {
+
+ @ToString.Exclude private final ProjectNode parent;
+ @ToString.Exclude private final TreeSet<ProjectNode> children = new TreeSet<ProjectNode>(
+ (a,b)->a.getArtifactId().compareTo(b.getArtifactId()));
+
+ private final String artifactId;
+ private final String name;
+ private final String description;
+
+ public void depthFirst(ProjectVisitor projectVisitor) {
+ projectVisitor.accept(this);
+ for(val child : getChildren()){
+ child.depthFirst(projectVisitor);
+ }
+ }
+
+}
diff --git a/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/ProjectNodeFactory.java b/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/ProjectNodeFactory.java
new file mode 100644
index 0000000..a4ec7ab
--- /dev/null
+++ b/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/ProjectNodeFactory.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.tooling.projectmodel;
+
+import java.io.File;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.maven.model.Model;
+import org.gradle.tooling.GradleConnector;
+import org.gradle.tooling.model.GradleProject;
+
+import org.apache.isis.tooling.projectmodel.maven.MavenModelFactory;
+import org.apache.isis.tooling.projectmodel.maven.SimpleModelResolver;
+
+import lombok.val;
+
+public class ProjectNodeFactory {
+
+ public static ProjectNode maven(File projRootFolder) {
+ val modelResolver = new SimpleModelResolver(projRootFolder);
+ val rootModel = modelResolver.getRootModel();
+ val interpolate = false; //XXX experimental
+ return visitMavenProject(null, rootModel, modelResolver, interpolate);
+ }
+
+ public static ProjectNode gradle(File projRootFolder) {
+ try(val projectConnection = GradleConnector.newConnector().forProjectDirectory(projRootFolder).connect()) {
+ val rootProject = projectConnection.getModel(GradleProject.class);
+ val rootNode = visitGradleProject(null, rootProject);
+ return rootNode;
+ }
+ }
+
+ // -- HELPER MAVEN
+
+ private static ProjectNode visitMavenProject(
+ ProjectNode parent,
+ Model mavenProj,
+ SimpleModelResolver modelResolver,
+ boolean interpolate) {
+
+ val interpolatedProj = interpolate
+ ? MavenModelFactory.interpolateModel(mavenProj, modelResolver)
+ : mavenProj;
+ val projNode = toProjectNode(parent, interpolatedProj);
+ for(val child : childrenOf(interpolatedProj, modelResolver)){
+ visitMavenProject(projNode, child, modelResolver, interpolate);
+ }
+ return projNode;
+ }
+
+ private static ProjectNode toProjectNode(ProjectNode parent, Model mavenProj) {
+ val projNode = ProjectNode.builder()
+ .parent(parent)
+ .artifactId(mavenProj.getArtifactId())
+ .name(mavenProj.getName())
+ .build();
+
+ if(parent!=null) {
+ parent.getChildren().add(projNode);
+ }
+
+ return projNode;
+
+ }
+
+ private static Iterable<Model> childrenOf(Model mavenProj, SimpleModelResolver modelResolver) {
+ return mavenProj.getModules()
+ .stream()
+ .map(name->modelResolver.lookupCatalogForSubmoduleOf(mavenProj, name))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ // -- HELPER GRADLE
+
+ private static ProjectNode visitGradleProject(ProjectNode parent, GradleProject gradleProj) {
+ val projNode = toProjectNode(parent, gradleProj);
+ for(val child : gradleProj.getChildren()){
+ visitGradleProject(projNode, child);
+ }
+ return projNode;
+ }
+
+ private static ProjectNode toProjectNode(ProjectNode parent, GradleProject gradleProj) {
+ val projNode = ProjectNode.builder()
+ .parent(parent)
+ .artifactId(gradleProj.getProjectIdentifier().getProjectPath())
+ .name(gradleProj.getName())
+ .build();
+ if(parent!=null) {
+ parent.getChildren().add(projNode);
+ }
+ return projNode;
+ }
+
+
+
+}
diff --git a/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/ProjectVisitor.java b/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/ProjectVisitor.java
new file mode 100644
index 0000000..54dffff
--- /dev/null
+++ b/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/ProjectVisitor.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.tooling.projectmodel;
+
+import java.util.function.Consumer;
+
+public interface ProjectVisitor extends Consumer<ProjectNode> {
+
+
+
+}
diff --git a/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/maven/MavenModelFactory.java b/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/maven/MavenModelFactory.java
new file mode 100644
index 0000000..2c8f003
--- /dev/null
+++ b/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/maven/MavenModelFactory.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.tooling.projectmodel.maven;
+
+import java.io.File;
+import java.io.FileReader;
+import java.util.Optional;
+
+import org.apache.maven.model.Model;
+import org.apache.maven.model.building.DefaultModelBuilderFactory;
+import org.apache.maven.model.building.DefaultModelBuildingRequest;
+import org.apache.maven.model.building.ModelBuildingException;
+import org.apache.maven.model.building.ModelBuildingRequest;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.model.resolution.ModelResolver;
+
+import lombok.val;
+import lombok.experimental.UtilityClass;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@UtilityClass
+public class MavenModelFactory {
+
+ public static Model interpolateModel(final Model model, final ModelResolver modelResolver) {
+
+ val pomFile = model.getPomFile();
+
+ log.info("interpolating model {}", pomFile);
+
+ val modelBuildRequest = new DefaultModelBuildingRequest()
+ .setProcessPlugins(false)
+ .setPomFile(pomFile)
+ .setModelResolver(modelResolver)
+ .setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
+
+ try {
+ val modelBuilder = new DefaultModelBuilderFactory().newInstance();
+ return modelBuilder.build(modelBuildRequest).getEffectiveModel();
+ } catch (ModelBuildingException e) {
+ log.warn("maven model interpolation failed {}", pomFile, e);
+ //throw new RuntimeException(String.format("maven model building failed %s", pomFile), e);
+ }
+
+ // fallback to non interpolated
+ return model;
+
+ }
+
+
+ /** non interpolated read */
+ public static Model readModel(File pomFile) {
+ val reader = new MavenXpp3Reader();
+ try {
+ val model = reader.read(new FileReader(pomFile));
+ model.setPomFile(pomFile);
+ return model;
+ } catch (Exception e) {
+ log.error("failed to read {}", pomFile.getAbsolutePath(), e);
+ throw new RuntimeException(String.format("failed to read %s", pomFile.getAbsolutePath()), e);
+ }
+ }
+
+ public static String readArtifactKey(Model model) {
+ if(model==null) {
+ return null;
+ }
+ val artifactKey = String.format("%s:%s:%s",
+ getGroupId(model),
+ model.getArtifactId(),
+ getVersion(model));
+ return artifactKey;
+ }
+
+ private static String getGroupId(Model model) {
+ return Optional.ofNullable(model.getGroupId()).orElseGet(()->model.getParent().getGroupId());
+ }
+
+ private static String getVersion(Model model) {
+ return Optional.ofNullable(model.getVersion()).orElseGet(()->model.getParent().getVersion());
+ }
+
+}
diff --git a/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/maven/SimpleModelResolver.java b/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/maven/SimpleModelResolver.java
new file mode 100644
index 0000000..c7309ae
--- /dev/null
+++ b/tooling/project-model/src/main/java/org/apache/isis/tooling/projectmodel/maven/SimpleModelResolver.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.tooling.projectmodel.maven;
+
+import java.io.File;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Parent;
+import org.apache.maven.model.Repository;
+import org.apache.maven.model.building.FileModelSource;
+import org.apache.maven.model.building.ModelSource;
+import org.apache.maven.model.building.UrlModelSource;
+import org.apache.maven.model.resolution.InvalidRepositoryException;
+import org.apache.maven.model.resolution.ModelResolver;
+import org.apache.maven.model.resolution.UnresolvableModelException;
+
+import org.apache.isis.tooling._infra._Files;
+
+import lombok.Getter;
+import lombok.SneakyThrows;
+import lombok.val;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+public class SimpleModelResolver implements ModelResolver {
+
+ //non interpolated models
+ private final Map<String, Model> projectPomCatalog = new HashMap<>();
+ private final Map<String, String> pathToArtifactMap = new HashMap<>();
+ private final Map<String, Repository> repositories = new LinkedHashMap<>();
+
+ @Getter private Model rootModel;
+
+ public SimpleModelResolver(final File projectRoot) {
+ populateCatalogs(projectRoot);
+ }
+
+ @Override
+ public ModelSource resolveModel(String groupId, String artifactId, String version)
+ throws UnresolvableModelException {
+
+ val key = String.format("%s:%s:%s", groupId, artifactId, version);
+
+ log.info("resolveModel {}", key);
+
+ try {
+
+ val pomModel = projectPomCatalog.get(key);
+ if(pomModel!=null) {
+ return new FileModelSource(pomModel.getPomFile());
+ }
+
+ if(repositories.size()==0) {
+ throw new RuntimeException("no repo registered");
+ }
+
+ for(val entry : repositories.entrySet()) {
+ val repo = entry.getValue();
+
+ val pomUrl = new URL(String.format("%s/%s/%s/%s/%s-%s.pom",
+ repo.getUrl(),
+ groupId.replace('.', '/'),
+ artifactId,
+ version,
+ artifactId,
+ version));
+
+ try {
+ val urlConn = pomUrl.openConnection();
+ val is = urlConn.getInputStream(); // throws if not found
+ is.close();
+ return new UrlModelSource(pomUrl);
+ } catch (Exception e) {
+ // try next
+ }
+ }
+
+ log.warn("No repo found that serves {}", key);
+
+ throw new RuntimeException(String.format("No repo found that serves %s", key));
+
+ } catch (Exception ex) {
+ throw new UnresolvableModelException(ex.getMessage(), groupId, artifactId, version);
+ }
+ }
+
+ @Override
+ public ModelSource resolveModel(Parent parent) throws UnresolvableModelException {
+ log.info("resolveModel-parent");
+ return resolveModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
+ }
+
+ @Override
+ public ModelSource resolveModel(Dependency dependency) throws UnresolvableModelException {
+ log.info("resolveModel-dependency");
+ return resolveModel(
+ dependency.getGroupId(),
+ dependency.getArtifactId(),
+ dependency.getVersion());
+ }
+
+ @Override
+ public void addRepository(Repository repository) throws InvalidRepositoryException {
+ log.info("adding repository {}", repository.getUrl());
+ repositories.put(repository.getId(), repository);
+ }
+
+ @Override
+ public void addRepository(Repository repository, boolean replace) throws InvalidRepositoryException {
+ addRepository(repository);
+ }
+
+ @Override
+ public ModelResolver newCopy() {
+ return this;
+ }
+
+ public Model lookupCatalogForSubmoduleOf(Model mavenProj, String name) {
+
+ val localPath = new File(mavenProj.getPomFile().getParentFile(), name)
+ .getAbsolutePath();
+
+ val artifactKey = pathToArtifactMap.get(localPath);
+ if(artifactKey==null) {
+ return null;
+ }
+
+ val subProj = projectPomCatalog.get(artifactKey);
+ if(subProj==null) {
+ return null;
+ }
+ return subProj;
+ }
+
+ @SneakyThrows
+ private void populateCatalogs(final File projectRoot) {
+
+ val localRootPath = projectRoot.getAbsolutePath();
+
+ _Files.searchFiles(projectRoot,
+ file->!"target".equals(file.getName()),
+ file->"pom.xml".equals(file.getName()))
+ .stream()
+ .forEach(pomFile->{
+
+ val model = MavenModelFactory.readModel(pomFile);
+
+ val localPath = pomFile.getParentFile().getAbsolutePath();
+
+ if(localPath.equals(localRootPath)) {
+ rootModel = model;
+ }
+
+ val artifactKey = MavenModelFactory.readArtifactKey(model);
+ if(artifactKey!=null) {
+ log.debug("found {} at {}", artifactKey, model.getPomFile().getAbsolutePath());
+ projectPomCatalog.put(artifactKey, model);
+ pathToArtifactMap.put(localPath, artifactKey);
+ }
+ });
+ }
+
+
+
+
+
+}
diff --git a/tooling/project-model/src/main/resources/log4j2.xml b/tooling/project-model/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..5cea1c5
--- /dev/null
+++ b/tooling/project-model/src/main/resources/log4j2.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="warn">
+ <Appenders>
+ <Console name="console" target="SYSTEM_OUT">
+ <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n" />
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Logger name="org.apache.isis.tooling" level="info" additivity="true">
+ <appender-ref ref="console" />
+ </Logger>
+ <Root level="warn" additivity="false">
+ <appender-ref ref="console" />
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/tooling/project-model/src/test/java/org/apache/isis/tooling/projectmodel/test/ProjectTreeTest.java b/tooling/project-model/src/test/java/org/apache/isis/tooling/projectmodel/test/ProjectTreeTest.java
new file mode 100644
index 0000000..e2efec0
--- /dev/null
+++ b/tooling/project-model/src/test/java/org/apache/isis/tooling/projectmodel/test/ProjectTreeTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.tooling.projectmodel.test;
+
+import java.io.File;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.apache.isis.tooling.projectmodel.ProjectNodeFactory;
+import org.apache.isis.tooling.projectmodel.ProjectVisitor;
+
+import lombok.val;
+
+class ProjectTreeTest {
+
+ File projRootFolder;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ projRootFolder = new File("./").getAbsoluteFile().getParentFile().getParentFile().getParentFile();
+ System.out.println("running ProjectTreeTest at " + projRootFolder.getAbsolutePath());
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ }
+
+ @Test
+ void testGradle() {
+
+ val projTree = ProjectNodeFactory.gradle(projRootFolder);
+
+ ProjectVisitor projectVisitor = projModel -> {System.out.println(projModel);};
+
+ projTree.depthFirst(projectVisitor);
+
+ }
+
+ @Test
+ void testMaven() {
+
+ val projTree = ProjectNodeFactory.maven(projRootFolder);
+
+ ProjectVisitor projectVisitor = projModel -> {System.out.println(projModel);};
+
+ projTree.depthFirst(projectVisitor);
+
+
+
+ }
+
+
+}