Skip to content

Commit

Permalink
Merge pull request #3397 from ia3andy/fix-3388
Browse files Browse the repository at this point in the history
Fix Deadlocks using CreateProject with multiple threads
  • Loading branch information
gsmet authored Aug 2, 2019
2 parents 0fa2e58 + ce25ac4 commit 2a3d9dd
Show file tree
Hide file tree
Showing 16 changed files with 385 additions and 246 deletions.
11 changes: 8 additions & 3 deletions devtools/common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand All @@ -83,10 +87,11 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package io.quarkus.cli.commands;

import static io.quarkus.generators.ProjectGenerator.ADDITIONAL_GITIGNORE_ENTRIES;
import static io.quarkus.generators.ProjectGenerator.CLASS_NAME;
import static io.quarkus.generators.ProjectGenerator.PACKAGE_NAME;
import static io.quarkus.generators.ProjectGenerator.PROJECT_ARTIFACT_ID;
import static io.quarkus.generators.ProjectGenerator.PROJECT_GROUP_ID;
import static io.quarkus.generators.ProjectGenerator.PROJECT_VERSION;
import static io.quarkus.generators.ProjectGenerator.QUARKUS_VERSION;
import static io.quarkus.generators.ProjectGenerator.SOURCE_TYPE;
import static io.quarkus.maven.utilities.MojoUtils.QUARKUS_VERSION_PROPERTY;
import static io.quarkus.maven.utilities.MojoUtils.configuration;
import static io.quarkus.maven.utilities.MojoUtils.getBomArtifactId;
import static io.quarkus.maven.utilities.MojoUtils.getPluginArtifactId;
import static io.quarkus.maven.utilities.MojoUtils.getPluginGroupId;
import static io.quarkus.maven.utilities.MojoUtils.getPluginVersion;
import static io.quarkus.maven.utilities.MojoUtils.plugin;
import static io.quarkus.templates.QuarkusTemplate.ADDITIONAL_GITIGNORE_ENTRIES;
import static io.quarkus.templates.QuarkusTemplate.CLASS_NAME;
import static io.quarkus.templates.QuarkusTemplate.PACKAGE_NAME;
import static io.quarkus.templates.QuarkusTemplate.PROJECT_ARTIFACT_ID;
import static io.quarkus.templates.QuarkusTemplate.PROJECT_GROUP_ID;
import static io.quarkus.templates.QuarkusTemplate.PROJECT_VERSION;
import static io.quarkus.templates.QuarkusTemplate.QUARKUS_VERSION;
import static io.quarkus.templates.QuarkusTemplate.SOURCE_TYPE;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
Expand All @@ -38,12 +38,12 @@
import org.apache.maven.model.Profile;

import io.quarkus.cli.commands.writer.ProjectWriter;
import io.quarkus.generators.BuildTool;
import io.quarkus.generators.ProjectGeneratorRegistry;
import io.quarkus.generators.SourceType;
import io.quarkus.generators.rest.BasicRestProjectGenerator;
import io.quarkus.maven.utilities.MojoUtils;
import io.quarkus.maven.utilities.MojoUtils.Element;
import io.quarkus.templates.BuildTool;
import io.quarkus.templates.SourceType;
import io.quarkus.templates.TemplateRegistry;
import io.quarkus.templates.rest.BasicRest;

/**
* @author <a href="mailto:[email protected]">Ståle Pedersen</a>
Expand Down Expand Up @@ -124,7 +124,7 @@ public boolean doCreateProject(final Map<String, Object> context) throws IOExcep
context.put(CLASS_NAME, className);
}

TemplateRegistry.createTemplateWith(BasicRest.TEMPLATE_NAME).generate(writer, context);
ProjectGeneratorRegistry.get(BasicRestProjectGenerator.NAME).generate(writer, context);

final byte[] pom = writer.getContent(POM_PATH);
model = MojoUtils.readPom(new ByteArrayInputStream(pom));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,9 @@ public String mkdirs(String path) {
dirToCreate.mkdirs();
}
if (path.isEmpty()) {
if (root.getPath().isEmpty()) {
return "";
}
return "/";
return "";
}
return dirToCreate.getPath().substring(root.getPath().length() + 1) + "/";
return dirToCreate.getPath().substring(root.getPath().length() + 1);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package io.quarkus.cli.commands.writer;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -40,12 +41,12 @@ public String mkdirs(String path) throws IOException {
zos.putNextEntry(ze);
zos.closeEntry();
dirs.add(dirPath);
return dirPath;
return path;
}

@Override
public void write(String path, String content) throws IOException {
byte[] contentBytes = content.getBytes("UTF-8");
byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
contentByPath.put(path, contentBytes);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.templates;
package io.quarkus.generators;

/**
* An enum of build tools, such as Maven and Gradle.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package io.quarkus.templates;
package io.quarkus.generators;

import java.io.IOException;
import java.util.Map;

import io.quarkus.cli.commands.writer.ProjectWriter;

public interface QuarkusTemplate {
public interface ProjectGenerator {
String PROJECT_GROUP_ID = "project_groupId";
String PROJECT_ARTIFACT_ID = "project_artifactId";
String PROJECT_VERSION = "project_version";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.quarkus.generators;

import java.util.Map;
import java.util.NoSuchElementException;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;

/**
* @author <a href="[email protected]">Christophe Laprun</a>
*/
public class ProjectGeneratorRegistry {

private static final Map<String, ProjectGenerator> generators = new ConcurrentHashMap<>(7);
private static final ProjectGeneratorRegistry INSTANCE = new ProjectGeneratorRegistry();

private ProjectGeneratorRegistry() {
loadGenerators();
}

public static ProjectGeneratorRegistry getInstance() {
return INSTANCE;
}

public static ProjectGenerator get(String name) throws NoSuchElementException {
final ProjectGenerator generator = generators.get(name);
if (generator == null) {
throw new NoSuchElementException("Unknown generator: " + name);
}

return generator;
}

private static void register(ProjectGenerator generator) {
if (generator != null) {
generators.put(generator.getName(), generator);
} else {
throw new NullPointerException("Cannot register null generator");
}
}

private static void loadGenerators() {
ServiceLoader<ProjectGenerator> serviceLoader = ServiceLoader.load(ProjectGenerator.class);
serviceLoader.iterator().forEachRemaining(ProjectGeneratorRegistry::register);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.templates;
package io.quarkus.generators;

import io.quarkus.maven.utilities.MojoUtils;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package io.quarkus.generators.rest;

import static java.lang.String.format;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.apache.maven.model.Model;

import io.quarkus.cli.commands.writer.ProjectWriter;
import io.quarkus.generators.ProjectGenerator;
import io.quarkus.generators.SourceType;
import io.quarkus.maven.utilities.MojoUtils;

public class BasicRestProjectGenerator implements ProjectGenerator {

public static final String NAME = "basic-rest";

public BasicRestProjectGenerator() {
}

@Override
public String getName() {
return NAME;
}

@Override
public void generate(final ProjectWriter writer, Map<String, Object> parameters) throws IOException {
final BasicRestProject project = new BasicRestProject(writer, parameters);

project.initProject();
project.setupContext();

project.createClasses();

project.createIndexPage();
project.createDockerFiles();
project.createDockerIgnore();
project.createApplicationConfig();

project.createGitIgnore();
}

private class BasicRestProject {
private Map<String, Object> context;
private String path = "/hello";
private ProjectWriter writer;
private String srcMainPath;
private String testMainPath;
private SourceType type;

private BasicRestProject(final ProjectWriter writer, final Map<String, Object> parameters) {
this.writer = writer;
this.context = parameters;
this.type = (SourceType) context.get(SOURCE_TYPE);
}

@SuppressWarnings("unchecked")
private <T> T get(final String key, final String defaultValue) {
return (T) context.getOrDefault(key, defaultValue);
}

private boolean initProject() throws IOException {
boolean newProject = !writer.exists("pom.xml");
if (newProject) {
generate(type.getPomResourceTemplate(getName()), context, "pom.xml", "pom.xml");
} else {
final Model model = MojoUtils.readPom(new ByteArrayInputStream(writer.getContent("pom.xml")));
context.put(PROJECT_GROUP_ID, model.getGroupId());
context.put(PROJECT_ARTIFACT_ID, model.getArtifactId());
}

path = get(RESOURCE_PATH, path);

srcMainPath = writer.mkdirs(type.getSrcDir());
testMainPath = writer.mkdirs(type.getTestSrcDir());

return newProject;
}

private void generate(final String templateName, final Map<String, Object> context, final String outputFilePath,
final String resourceType)
throws IOException {
if (!writer.exists(outputFilePath)) {
String path = templateName.startsWith("/") ? templateName : "/" + templateName;
try (final BufferedReader stream = new BufferedReader(
new InputStreamReader(getClass().getResourceAsStream(path), StandardCharsets.UTF_8))) {
String template = stream.lines().collect(Collectors.joining("\n"));
for (Entry<String, Object> e : context.entrySet()) {
if (e.getValue() != null) { // Exclude null values (classname and path can be null)
template = template.replace(format("${%s}", e.getKey()), e.getValue().toString());
}
}
writer.write(outputFilePath, template);
}
}
}

private void createIndexPage() throws IOException {
// Generate index page
String resources = "src/main/resources/META-INF/resources";
String index = writer.mkdirs(resources) + "/index.html";
if (!writer.exists(index)) {
generate("templates/index.ftl", context, index, "welcome page");
}

}

private void createDockerFiles() throws IOException {
String dockerRoot = "src/main/docker";
String dockerRootDir = writer.mkdirs(dockerRoot);
generate("templates/dockerfile-native.ftl", context, dockerRootDir + "/Dockerfile.native",
"native docker file");
generate("templates/dockerfile-jvm.ftl", context, dockerRootDir + "/Dockerfile.jvm", "jvm docker file");
}

private void createDockerIgnore() throws IOException {
String docker = writer.mkdirs("") + ".dockerignore";
generate("templates/dockerignore.ftl", context, docker, "docker ignore");
}

private void createGitIgnore() throws IOException {
String gitignore = writer.mkdirs("") + ".gitignore";
generate("templates/gitignore.ftl", context, gitignore, "git ignore");
}

private void createApplicationConfig() throws IOException {
String meta = "src/main/resources";
String file = writer.mkdirs(meta) + "/application.properties";
if (!writer.exists(file)) {
writer.write(file, "# Configuration file" + System.lineSeparator() + "# key = value");
}
}

private void setupContext() throws IOException {
if (context.get(CLASS_NAME) != null) {
String packageName = (String) context.get(PACKAGE_NAME);

if (packageName != null) {
String packageDir = srcMainPath + '/' + packageName.replace('.', '/');
String testPackageDir = testMainPath + '/' + packageName.replace('.', '/');
srcMainPath = writer.mkdirs(packageDir);
testMainPath = writer.mkdirs(testPackageDir);
} else {
throw new NullPointerException("Need a non-null package name");
}
}
}

private void createClasses() throws IOException {
Object className = context.get(CLASS_NAME);
// If className is null we disable the generation of the JAX-RS resource.
if (className != null) {
String extension = type.getExtension();
String classFile = srcMainPath + '/' + className + extension;
String testClassFile = testMainPath + '/' + className + "Test" + extension;
String itTestClassFile = testMainPath + '/' + "Native" + className + "IT" + extension;
String name = getName();
generate(type.getSrcResourceTemplate(name), context, classFile, "resource code");
generate(type.getTestResourceTemplate(name), context, testClassFile, "test code");
generate(type.getNativeTestResourceTemplate(name), context, itTestClassFile, "IT code");
}
}
}
}
Loading

0 comments on commit 2a3d9dd

Please sign in to comment.