TransformMojo.java
package io.swagger.v3.oas.transformer;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
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.eclipse.transformer.Transformer;
import org.eclipse.transformer.jakarta.JakartaTransformer;
/**
*
* Adapted from https://github.com/eclipse/transformer/blob/main/org.eclipse.transformer.maven
* not to add the transformed JAR as an attached artifact to the project.
*
* To be replaced with Eclipse original Transformer plugin when/if optional attachment is supported.
*
* This is a Maven plugin which runs the Eclipse Transformer on build artifacts
* as part of the build.
*/
@Mojo(name = "run", requiresDependencyResolution = ResolutionScope.RUNTIME_PLUS_SYSTEM, defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true)
public class TransformMojo extends AbstractMojo {
@Parameter(defaultValue = "${project}", readonly = true, required = true)
private MavenProject project;
@Parameter(defaultValue = "false", property = "transformer-plugin.invert", required = true)
private Boolean invert;
@Parameter(defaultValue = "true", property = "transformer-plugin.overwrite", required = true)
private Boolean overwrite;
@Parameter(property = "transformer-plugin.renames", defaultValue = "")
private String rulesRenamesUri;
@Parameter(property = "transformer-plugin.versions", defaultValue = "")
private String rulesVersionUri;
@Parameter(property = "transformer-plugin.bundles", defaultValue = "")
private String rulesBundlesUri;
@Parameter(property = "transformer-plugin.direct", defaultValue = "")
private String rulesDirectUri;
@Parameter(property = "transformer-plugin.per-class-constant", defaultValue = "")
private String rulesPerClassConstantUri;
@Parameter(property = "transformer-plugin.xml", defaultValue = "")
private String rulesXmlsUri;
@Parameter(defaultValue = "transformed")
private String classifier;
private static final int MAX_LOG_BUFFER_SIZE = 16 * 1024;
@Parameter(defaultValue = "${project.build.directory}", required = true)
private File outputDirectory;
@Parameter(defaultValue = "true", property = "transformer-plugin.attach", required = true)
private Boolean attach;
@Component
private MavenProjectHelper projectHelper;
/**
* Main execution point of the plugin. This looks at the attached artifacts,
* and runs the transformer on them.
*
* @throws MojoFailureException Thrown if there is an error during plugin
* execution
*/
@Override
public void execute() throws MojoFailureException {
final Transformer transformer = getTransformer();
final Artifact[] sourceArtifacts = getSourceArtifacts();
for (final Artifact sourceArtifact : sourceArtifacts) {
transform(transformer, sourceArtifact);
}
}
/**
* This runs the transformation process on the source artifact with the
* transformer provided. The transformed artifact is attached to the
* project.
*
* @param transformer The Transformer to use for the transformation
* @param sourceArtifact The Artifact to transform
* @throws MojoFailureException if plugin execution fails
*/
public void transform(final Transformer transformer, final Artifact sourceArtifact) throws MojoFailureException {
final String sourceClassifier = sourceArtifact.getClassifier();
final String targetClassifier = (sourceClassifier == null || sourceClassifier.length() == 0) ? this.classifier
: sourceClassifier + "-" + this.classifier;
final File targetFile = new File(outputDirectory, sourceArtifact.getArtifactId() + "-" + targetClassifier + "-"
+ sourceArtifact.getVersion() + "." + sourceArtifact.getType());
final List<String> args = new ArrayList<>();
args.add(sourceArtifact.getFile()
.getAbsolutePath());
args.add(targetFile.getAbsolutePath());
if (this.overwrite) {
args.add("-o");
}
transformer.setArgs(args.toArray(new String[0]));
int rc = transformer.run();
if (rc != 0) {
throw new MojoFailureException("Transformer failed with an error: " + Transformer.RC_DESCRIPTIONS[rc]);
}
if (attach) {
projectHelper.attachArtifact(project, sourceArtifact.getType(), targetClassifier, targetFile);
}
}
/**
* Builds a configured transformer for the specified source and target
* artifacts
*
* @return A configured transformer
*/
public Transformer getTransformer() {
final Transformer transformer = new Transformer(
createLoggingPrintStream(getLog()::info),
createLoggingPrintStream(getLog()::error));
transformer.setOptionDefaults(JakartaTransformer.class, getOptionDefaults());
return transformer;
}
/**
* Gets the source artifacts that should be transformed
*
* @return an array to artifacts to be transformed
*/
public Artifact[] getSourceArtifacts() {
List<Artifact> artifactList = new ArrayList<>();
if (project.getArtifact() != null && project.getArtifact()
.getFile() != null) {
artifactList.add(project.getArtifact());
}
for (final Artifact attachedArtifact : project.getAttachedArtifacts()) {
if (attachedArtifact.getFile() != null) {
artifactList.add(attachedArtifact);
}
}
return artifactList.toArray(new Artifact[0]);
}
private Map<Transformer.AppOption, String> getOptionDefaults() {
Map<Transformer.AppOption, String> optionDefaults = new HashMap<>();
optionDefaults.put(Transformer.AppOption.RULES_RENAMES,
isEmpty(rulesRenamesUri) ? "jakarta-renames.properties" : rulesRenamesUri);
optionDefaults.put(Transformer.AppOption.RULES_VERSIONS,
isEmpty(rulesVersionUri) ? "jakarta-versions.properties" : rulesVersionUri);
optionDefaults.put(Transformer.AppOption.RULES_BUNDLES,
isEmpty(rulesBundlesUri) ? "jakarta-bundles.properties" : rulesBundlesUri);
optionDefaults.put(Transformer.AppOption.RULES_DIRECT,
isEmpty(rulesDirectUri) ? "jakarta-direct.properties" : rulesDirectUri);
optionDefaults.put(Transformer.AppOption.RULES_MASTER_TEXT,
isEmpty(rulesXmlsUri) ? "jakarta-text-master.properties" : rulesXmlsUri);
optionDefaults.put(Transformer.AppOption.RULES_PER_CLASS_CONSTANT,
isEmpty(rulesPerClassConstantUri) ? "jakarta-per-class-constant-master.properties" : rulesPerClassConstantUri);
return optionDefaults;
}
private PrintStream createLoggingPrintStream(Consumer<String> logConsumer) {
try {
return new PrintStream(new LoggingOutputStream(logConsumer, MAX_LOG_BUFFER_SIZE), true, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 encoding is not supported", e);
}
}
private static final class LoggingOutputStream extends OutputStream {
private final Consumer<String> logConsumer;
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final int maxBufferSize;
private LoggingOutputStream(Consumer<String> logConsumer, int maxBufferSize) {
this.logConsumer = logConsumer;
this.maxBufferSize = maxBufferSize > 0 ? maxBufferSize : Integer.MAX_VALUE;
}
@Override
public void write(int b) {
if (b == '\n') {
flushBuffer();
} else if (b != '\r') {
buffer.write(b);
if (buffer.size() >= this.maxBufferSize) {
flushBuffer();
}
}
}
@Override
public void flush() {
flushBuffer();
}
@Override
public void close() throws IOException {
flushBuffer();
}
private void flushBuffer() {
if (buffer.size() == 0) {
return;
}
logConsumer.accept(new String(buffer.toByteArray(), StandardCharsets.UTF_8));
buffer.reset();
}
}
private boolean isEmpty(final String input) {
return input == null || input.trim()
.length() == 0;
}
void setProject(MavenProject project) {
this.project = project;
}
void setClassifier(String classifier) {
this.classifier = classifier;
}
MavenProjectHelper getProjectHelper() {
return projectHelper;
}
void setProjectHelper(MavenProjectHelper projectHelper) {
this.projectHelper = projectHelper;
}
void setOverwrite(Boolean overwrite) {
this.overwrite = overwrite;
}
void setOutputDirectory(File outputDirectory) {
this.outputDirectory = outputDirectory;
}
void setAttach(Boolean attach) {
this.attach = attach;
}
}