blob: 0ca8459741804c4c29fbff24acbb278cb0d29970 [file] [log] [blame]
/*
* 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.camel.quarkus.maven;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.repository.RepositorySystem;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.mvel2.templates.TemplateRuntime;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import static org.apache.camel.maven.packaging.PackageHelper.loadText;
/**
* Prepares the Quarkus provider camel catalog to include component it supports
*/
@Mojo(name = "prepare-catalog-quarkus", threadSafe = true, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class PrepareCatalogQuarkusMojo extends AbstractMojo {
private static final Set<String> EXCLUDE_EXTENSIONS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("http-common", "support")));
/**
* The maven project.
*/
@Parameter(property = "project", required = true, readonly = true)
protected MavenProject project;
@Component
private RepositorySystem repositorySystem;
@Component
private ProjectBuilder mavenProjectBuilder;
@Parameter(defaultValue = "${session}", readonly = true)
private MavenSession session;
@Component
private MavenProjectHelper projectHelper;
/**
* The output directory for components catalog
*/
@Parameter(defaultValue = "${project.build.directory}/classes/org/apache/camel/catalog/quarkus/components")
protected File componentsOutDir;
/**
* The output directory for dataformats catalog
*/
@Parameter(defaultValue = "${project.build.directory}/classes/org/apache/camel/catalog/quarkus/dataformats")
protected File dataFormatsOutDir;
/**
* The output directory for languages catalog
*/
@Parameter(defaultValue = "${project.build.directory}/classes/org/apache/camel/catalog/quarkus/languages")
protected File languagesOutDir;
/**
* The output directory for others catalog
*/
@Parameter(defaultValue = "${project.build.directory}/classes/org/apache/camel/catalog/quarkus/others")
protected File othersOutDir;
/**
* The directory where all quarkus extension starters are
*/
@Parameter(defaultValue = "${project.build.directory}/../../../extensions")
protected File extensionsDir;
/**
* Execute goal.
*
* @throws MojoExecutionException execution of the main class or one of the
* threads it generated failed.
* @throws MojoFailureException something bad happened...
*/
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
final List<CamelQuarkusExtension> extensions = findExtensionModules();
final CamelCatalog camelCatalog = CamelCatalog.load();
for (Kind kind : Kind.values()) {
doExecute(extensions, kind, camelCatalog);
}
appendOthers(extensions, camelCatalog);
}
protected void doExecute(List<CamelQuarkusExtension> extensions, Kind kind, CamelCatalog catalog) throws MojoExecutionException {
final Path outsDir = kind.getPath(this);
// make sure to create out dir
try {
Files.createDirectories(outsDir);
} catch (IOException e) {
throw new MojoExecutionException("Could not create " + outsDir, e);
}
final Gson gson = new GsonBuilder().enableComplexMapKeySerialization().setPrettyPrinting().create();
final Set<String> names = new TreeSet<>();
for (CamelQuarkusExtension ext : extensions) {
final String artifactId = ext.getCamelComponentArtifactId();
for (JsonObject catalogEntry : catalog.getByArtifactId(kind, artifactId)) {
final JsonObject newCatalogEntry = catalogEntry.deepCopy();
final JsonObject kindObject = newCatalogEntry.get(kind.getSingularName()).getAsJsonObject();
final String firstVersion = ext.getFirstVersion().orElseThrow(() -> new MojoExecutionException(
"firstVersion property is missing in " + ext.getRuntimePomXmlPath()));
// lets use the camel-quarkus version as first version instead of Apache Camel version
kindObject.addProperty("firstVersion", firstVersion);
// update json metadata to adapt to camel-quarkus-catalog
kindObject.addProperty("groupId", "org.apache.camel.quarkus");
kindObject.addProperty("artifactId", ext.getRuntimeArtifactId());
kindObject.addProperty("version", project.getVersion());
final String name = kind.getName(newCatalogEntry);
names.add(name);
final Path out = outsDir.resolve(name + ".json");
try (Writer w = Files.newBufferedWriter(out, StandardCharsets.UTF_8)) {
gson.toJson(newCatalogEntry, w);
} catch (IOException e) {
throw new MojoExecutionException("Could not write to " + out);
}
}
}
final Path newCatalog = outsDir.resolve("../" + kind + ".properties");
try {
Files.write(newCatalog, names.stream().collect(Collectors.joining("\n")).getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new MojoExecutionException("Could not write to " + newCatalog);
}
}
protected void appendOthers(List<CamelQuarkusExtension> extensions, CamelCatalog catalog) throws MojoExecutionException, MojoFailureException {
// make sure to create out dir
othersOutDir.mkdirs();
final Path othersPropertiesPath = othersOutDir.toPath().resolve("../others.properties");
Set<String> names;
try {
names = Files.lines(othersPropertiesPath).collect(Collectors.toCollection(TreeSet::new));
} catch (IOException e) {
throw new RuntimeException("Could not read " + othersPropertiesPath, e);
}
for (CamelQuarkusExtension ext : extensions) {
// skip if the extension is already one of the following
if (ext.getCamelComponentArtifactId() == null || !catalog.getKind(ext.getCamelComponentArtifactId()).isPresent()) {
final Map<String, String> model = new HashMap<>();
String firstVersion = ext.getFirstVersion().orElseThrow(() -> new MojoExecutionException(
"firstVersion property is missing in " + ext.getRuntimePomXmlPath()));
model.put("firstVersion", firstVersion);
final String name = ext.getRuntimeArtifactId().replace("camel-quarkus-", "");
names.add(name);
model.put("name", name);
final String title = ext.getName().orElseThrow(() -> new MojoExecutionException(
"name is missing in " + ext.getRuntimePomXmlPath()));
model.put("title", title);
model.put("description", ext.getDescription().orElseThrow(() -> new MojoExecutionException(
"description is missing in " + ext.getRuntimePomXmlPath())));
if (title.contains("(deprecated)")) {
model.put("deprecated", "true");
} else {
model.put("deprecated", "false");
}
model.put("label", ext.getLabel().orElse("quarkus"));
model.put("groupId", "org.apache.camel.quarkus");
model.put("artifactId", ext.getRuntimeArtifactId());
model.put("version", project.getVersion());
final String text = templateOther(model);
// write new json file
Path to = othersOutDir.toPath().resolve(name + ".json");
try {
Files.write(to, text.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException("Could not write to " + to, e);
}
}
}
try {
Files.write(othersPropertiesPath, names.stream().collect(Collectors.joining("\n")).getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException("Could not write to " + othersPropertiesPath, e);
}
}
private String templateOther(Map<?, ?> model) throws MojoExecutionException {
try {
String template = loadText(getClass().getClassLoader().getResourceAsStream("other-template.mvel"));
String out = (String) TemplateRuntime.eval(template, model);
return out;
} catch (Exception e) {
throw new MojoExecutionException("Error processing mvel template. Reason: " + e, e);
}
}
private List<CamelQuarkusExtension> findExtensionModules() {
try {
return Files.list(extensionsDir.toPath())
.filter(Files::isDirectory)
.filter(path -> !EXCLUDE_EXTENSIONS.contains(path.getFileName().toString()))
.map(path -> path.resolve("pom.xml"))
.filter(Files::exists)
.map(CamelQuarkusExtension::read)
.collect(Collectors.toList());
} catch (IOException e) {
throw new RuntimeException("Could not list " + extensionsDir, e);
}
}
enum Kind {
components() {
@Override
public String getName(JsonObject json) {
return json.get(getSingularName()).getAsJsonObject().get("scheme").getAsString();
}
@Override
public Path getPath(PrepareCatalogQuarkusMojo mojo) {
return mojo.componentsOutDir.toPath();
}
},
languages() {
@Override
public String getName(JsonObject json) {
return json.get(getSingularName()).getAsJsonObject().get("name").getAsString();
}
@Override
public Path getPath(PrepareCatalogQuarkusMojo mojo) {
return mojo.languagesOutDir.toPath();
}
},
dataformats() {
@Override
public String getName(JsonObject json) {
return json.get(getSingularName()).getAsJsonObject().get("name").getAsString();
}
@Override
public Path getPath(PrepareCatalogQuarkusMojo mojo) {
return mojo.dataFormatsOutDir.toPath();
}
},
others() {
@Override
public String getName(JsonObject json) {
return json.get(getSingularName()).getAsJsonObject().get("name").getAsString();
}
@Override
public Path getPath(PrepareCatalogQuarkusMojo mojo) {
return mojo.othersOutDir.toPath();
}
}
;
public abstract String getName(JsonObject json);
public abstract Path getPath(PrepareCatalogQuarkusMojo mojo);
public String getSingularName() {
return name().substring(0, name().length() - 1);
}
}
static class CamelCatalog {
public static CamelCatalog load() {
Map<Kind, Map<String, List<JsonObject>>> entriesByKindByArtifactId = new EnumMap<>(Kind.class);
for (Kind kind : Kind.values()) {
final String resourcePath = "org/apache/camel/catalog/" + kind + ".properties";
final URL url = PrepareCatalogQuarkusMojo.class.getClassLoader().getResource(resourcePath);
try (BufferedReader propsReader = new BufferedReader(
new InputStreamReader(
url.openStream(),
StandardCharsets.UTF_8))) {
/* Load the catalog entries */
final JsonParser jsonParser = new JsonParser();
final Map<String, List<JsonObject>> entries = new HashMap<>();
propsReader.lines()
.map(name -> {
final String rPath = "org/apache/camel/catalog/" + kind + "/" + name + ".json";
try (Reader r = new InputStreamReader(PrepareCatalogQuarkusMojo.class.getClassLoader()
.getResourceAsStream(rPath ), StandardCharsets.UTF_8)) {
return jsonParser.parse(r).getAsJsonObject();
} catch (IOException e) {
throw new RuntimeException("Could not load resource " + rPath + " from class path", e);
}
})
.forEach(json -> {
String aid = json.get(kind.getSingularName()).getAsJsonObject().get("artifactId").getAsString();
List<JsonObject> jsons = entries.get(aid);
if (jsons == null) {
jsons = new ArrayList<JsonObject>();
entries.put(aid, jsons);
}
jsons.add(json);
});
entriesByKindByArtifactId.put(kind, entries);
} catch (IOException e) {
throw new RuntimeException("Could not load resource " + resourcePath + " from class path", e);
}
}
return new CamelCatalog(entriesByKindByArtifactId);
}
private final Map<Kind, Map<String, List<JsonObject>>> entriesByKindByArtifactId;
public CamelCatalog(Map<Kind, Map<String, List<JsonObject>>> entriesByKindByArtifactId2) {
super();
this.entriesByKindByArtifactId = entriesByKindByArtifactId2;
}
public List<JsonObject> getByArtifactId(Kind kind, String artifactId) {
final Map<String, List<JsonObject>> kindEntries = entriesByKindByArtifactId.get(kind);
List<JsonObject> result = kindEntries != null ? kindEntries.get(artifactId) : null;
return result == null ? Collections.emptyList() : result;
}
public Optional<Kind> getKind(String artifactId) {
return entriesByKindByArtifactId.entrySet().stream()
.filter(en -> en.getValue().containsKey(artifactId))
.map(Entry::getKey)
.findFirst();
}
}
static class CamelQuarkusExtension {
public static CamelQuarkusExtension read(Path parentPomXmlPath) {
final Path runtimePomXmlPath = parentPomXmlPath.getParent().resolve("runtime/pom.xml").toAbsolutePath().normalize();
try (Reader parentReader = Files.newBufferedReader(parentPomXmlPath, StandardCharsets.UTF_8);
Reader runtimeReader = Files.newBufferedReader(runtimePomXmlPath, StandardCharsets.UTF_8)) {
final MavenXpp3Reader rxppReader = new MavenXpp3Reader();
final Model parentPom = rxppReader.read(parentReader);
final Model runtimePom = rxppReader.read(runtimeReader);
final List<Dependency> deps = runtimePom.getDependencies();
final String aid = runtimePom.getArtifactId();
String camelComponentArtifactId = null;
if (aid.equals("camel-quarkus-core")) {
camelComponentArtifactId = "camel-base";
} else if (deps != null && !deps.isEmpty()) {
Optional<Dependency> artifact = deps.stream()
.filter(dep ->
"org.apache.camel".equals(dep.getGroupId()) &&
("compile".equals(dep.getScope()) || dep.getScope() == null))
.findFirst();
if (artifact.isPresent()) {
camelComponentArtifactId = artifact.get().getArtifactId();
}
}
final Properties props = runtimePom.getProperties() != null ? runtimePom.getProperties() : new Properties();
String name = props.getProperty("title");
if (name == null) {
name = parentPom.getName().replace("Camel Quarkus :: ", "");
}
return new CamelQuarkusExtension(
parentPomXmlPath,
runtimePomXmlPath,
camelComponentArtifactId,
(String) props.get("firstVersion"),
aid,
name,
runtimePom.getDescription(),
props.getProperty("label")
);
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException("Could not read "+ parentPomXmlPath, e);
}
}
private final String label;
private final String description;
private final String runtimeArtifactId;
private final Path parentPomXmlPath;
private final Path runtimePomXmlPath;
private final String camelComponentArtifactId;
private final String firstVersion;
private final String name;
public CamelQuarkusExtension(
Path pomXmlPath,
Path runtimePomXmlPath,
String camelComponentArtifactId,
String firstVersion,
String runtimeArtifactId,
String name,
String description,
String label) {
super();
this.parentPomXmlPath = pomXmlPath;
this.runtimePomXmlPath = runtimePomXmlPath;
this.camelComponentArtifactId = camelComponentArtifactId;
this.firstVersion = firstVersion;
this.runtimeArtifactId = runtimeArtifactId;
this.name = name;
this.description = description;
this.label = label;
}
public Path getParentPomXmlPath() {
return parentPomXmlPath;
}
public Optional<String> getFirstVersion() {
return Optional.ofNullable(firstVersion);
}
public Path getRuntimePomXmlPath() {
return runtimePomXmlPath;
}
public Optional<String> getLabel() {
return Optional.ofNullable(label);
}
public Optional<String> getDescription() {
return Optional.ofNullable(description);
}
public String getRuntimeArtifactId() {
return runtimeArtifactId;
}
public String getCamelComponentArtifactId() {
return camelComponentArtifactId;
}
public Optional<String> getName() {
return Optional.ofNullable(name);
}
}
}