blob: d78085988f938d5b42b7fc7680d8f1ef59bbb96d [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.springboot.maven;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.camel.maven.packaging.MvelHelper;
import org.apache.camel.springboot.maven.model.SpringBootAutoConfigureOptionModel;
import org.apache.camel.springboot.maven.model.SpringBootModel;
import org.apache.camel.tooling.util.Strings;
import org.apache.camel.util.json.DeserializationException;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsoner;
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.project.MavenProject;
import org.mvel2.templates.TemplateRuntime;
import org.sonatype.plexus.build.incremental.BuildContext;
import static org.apache.camel.tooling.util.PackageHelper.loadText;
import static org.apache.camel.tooling.util.PackageHelper.writeText;
/**
* For all the Camel components that has Spring Boot starter JAR, their documentation
* .adoc files in their component directory is updated to include spring boot auto configuration options.
*/
@Mojo(name = "update-spring-boot-auto-configuration-readme", threadSafe = true)
public class UpdateSpringBootAutoConfigurationReadmeMojo extends AbstractMojo {
/**
* The maven project.
*/
@Parameter(property = "project", required = true, readonly = true)
protected MavenProject project;
/**
* The project build directory
*
*/
@Parameter(defaultValue = "${project.build.directory}")
protected File buildDir;
/**
* Whether to fail the build fast if any Warnings was detected.
*/
@Parameter
protected Boolean failFast;
/**
* Whether to fail if an option has no documentation.
*/
@Parameter
protected Boolean failOnMissingDescription;
/**
* build context to check changed files and mark them for refresh (used for
* m2e compatibility)
*/
@Component
private BuildContext buildContext;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
try {
executeStarter(buildDir.getParentFile());
} catch (Exception e) {
throw new MojoFailureException("Error processing spring-configuration-metadata.json", e);
}
}
private void executeStarter(File starter) throws Exception {
File jsonFile = new File(buildDir, "classes/META-INF/spring-configuration-metadata.json");
// only if there is components we should update the documentation files
if (jsonFile.exists()) {
getLog().debug("Processing Spring Boot auto-configuration file: " + jsonFile);
Object js = Jsoner.deserialize(new FileReader(jsonFile));
if (js != null) {
String name = starter.getName();
if (!isValidStarter(name)) {
return;
}
if (name.startsWith("camel-")) {
// skip camel- and -starter in the end
name = name.substring(6);
}
boolean isStarter = false;
if (name.endsWith("-starter")) {
// skip camel- and -starter in the end
name = name.substring(0, name.length() - 8);
isStarter = true;
}
String componentName = name;
getLog().debug("Camel component: " + componentName);
File docFolder = new File(starter,"src/main/docs/");
File docFile = new File(docFolder, isStarter ? componentName + "-starter.adoc" : componentName + ".adoc");
List<SpringBootAutoConfigureOptionModel> models = parseSpringBootAutoConfigureModels(jsonFile, null);
// check for missing description on options
boolean noDescription = false;
for (SpringBootAutoConfigureOptionModel o : models) {
if (Strings.isEmpty(o.getDescription())) {
noDescription = true;
getLog().warn("Option " + o.getName() + " has no description");
}
}
if (noDescription && isFailOnNoDescription()) {
throw new MojoExecutionException("Failed build due failOnMissingDescription=true");
}
String changed = templateAutoConfigurationOptions(models, componentName);
boolean updated = updateAutoConfigureOptions(docFile, changed);
if (updated) {
getLog().info("Updated doc file: " + docFile);
} else {
getLog().debug("No changes to doc file: " + docFile);
}
}
}
}
// TODO: later
private static String asComponentName(String componentName) {
if ("fastjson".equals(componentName)) {
return "json-fastjson";
} else if ("gson".equals(componentName)) {
return "json-gson";
} else if ("jackson".equals(componentName)) {
return "json-jackson";
} else if ("johnzon".equals(componentName)) {
return "json-johnzon";
} else if ("snakeyaml".equals(componentName)) {
return "yaml-snakeyaml";
} else if ("cassandraql".equals(componentName)) {
return "cql";
} else if ("josql".equals(componentName)) {
return "sql";
} else if ("juel".equals(componentName)) {
return "el";
} else if ("jsch".equals(componentName)) {
return "scp";
} else if ("printer".equals(componentName)) {
return "lpr";
} else if ("saxon".equals(componentName)) {
return "xquery";
} else if ("stringtemplate".equals(componentName)) {
return "string-template";
} else if ("tagsoup".equals(componentName)) {
return "tidyMarkup";
}
return componentName;
}
private static boolean isValidStarter(String name) {
return true;
}
private List<SpringBootAutoConfigureOptionModel> parseSpringBootAutoConfigureModels(File file, String include) throws IOException, DeserializationException {
getLog().debug("Parsing Spring Boot AutoConfigureModel using include: " + include);
List<SpringBootAutoConfigureOptionModel> answer = new ArrayList<>();
JsonObject obj = (JsonObject) Jsoner.deserialize(new FileReader(file));
JsonArray arr = obj.getCollection("properties");
if (arr != null && !arr.isEmpty()) {
arr.forEach(e -> {
JsonObject row = (JsonObject) e;
String name = row.getString("name");
String javaType = row.getString("type");
String desc = row.getStringOrDefault("description", "");
String defaultValue = row.getStringOrDefault("defaultValue", "");
// is the option deprecated then include that as well in the description
String deprecated = row.getStringOrDefault("deprecated", "");
String deprecationNote = row.getStringOrDefault("deprecationNote", "");
if ("true".equals(deprecated)) {
desc = "*Deprecated* " + desc;
if (!Strings.isEmpty(deprecationNote)) {
if (!desc.endsWith(".")) {
desc = desc + ". Deprecation note: " + deprecationNote;
} else {
desc = desc + " Deprecation note: " + deprecationNote;
}
}
}
// skip this special option and also if not matching the filter
boolean skip = name.endsWith("customizer.enabled") || include != null && !name.contains("." + include + ".");
if (!skip) {
SpringBootAutoConfigureOptionModel model = new SpringBootAutoConfigureOptionModel();
model.setName(name);
model.setJavaType(javaType);
model.setDefaultValue(defaultValue);
model.setDescription(desc);
answer.add(model);
}
});
}
return answer;
}
private boolean updateAutoConfigureOptions(File file, String changed) throws MojoExecutionException {
try {
if (!file.exists()) {
// include markers for new files
changed = "// spring-boot-auto-configure options: START\n" + changed + "\n// spring-boot-auto-configure options: END\n";
writeText(file, changed);
return true;
}
String text = loadText(new FileInputStream(file));
String existing = Strings.between(text, "// spring-boot-auto-configure options: START", "// spring-boot-auto-configure options: END");
if (existing != null) {
// remove leading line breaks etc
existing = existing.trim();
changed = changed.trim();
if (existing.equals(changed)) {
return false;
} else {
String before = Strings.before(text, "// spring-boot-auto-configure options: START");
String after = Strings.after(text, "// spring-boot-auto-configure options: END");
text = before + "// spring-boot-auto-configure options: START\n" + changed + "\n// spring-boot-auto-configure options: END" + after;
writeText(file, text);
return true;
}
} else {
getLog().warn("Cannot find markers in file " + file);
getLog().warn("Add the following markers");
getLog().warn("\t// spring-boot-auto-configure options: START");
getLog().warn("\t// spring-boot-auto-configure options: END");
if (isFailFast()) {
throw new MojoExecutionException("Failed build due failFast=true");
}
return false;
}
} catch (Exception e) {
throw new MojoExecutionException("Error reading file " + file + " Reason: " + e, e);
}
}
private String templateAutoConfigurationOptions(List<SpringBootAutoConfigureOptionModel> options, String componentName) throws MojoExecutionException {
SpringBootModel model = new SpringBootModel();
model.setGroupId(project.getGroupId());
model.setArtifactId("camel-" + componentName + "-starter");
model.setVersion(project.getVersion());
model.setOptions(options);
model.setTitle(componentName);
try {
String template = loadText(UpdateSpringBootAutoConfigurationReadmeMojo.class.getClassLoader().getResourceAsStream("spring-boot-auto-configure-options.mvel"));
String out = (String) TemplateRuntime.eval(template, model, Collections.singletonMap("util", MvelHelper.INSTANCE));
return out;
} catch (Exception e) {
throw new MojoExecutionException("Error processing mvel template. Reason: " + e, e);
}
}
private boolean isFailFast() {
return failFast != null && failFast;
}
private boolean isFailOnNoDescription() {
return failOnMissingDescription != null && failOnMissingDescription;
}
}