blob: 1619acd9044f353469378507dacdd9a8edfa4e39 [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
*
* https://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.avro.mojo;
import java.io.File;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.List;
import org.apache.avro.AvroRuntimeException;
import org.apache.avro.reflect.ReflectData;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
/**
* Generate Avro files (.avsc and .avpr) from Java classes or interfaces
*
* @goal induce
* @phase process-classes
* @threadSafe
*/
public class InduceMojo extends AbstractMojo {
/**
* The Java source directories.
*
* @parameter property="javaSourceDirectories"
* default-value="${basedir}/src/main/java"
*/
private File[] javaSourceDirectories;
/**
* Directory where to output Avro schemas (.avsc) or protocols (.avpr).
*
* @parameter property="avroOutputDirectory"
* default-value="${project.build.directory}/generated-resources/avro"
*/
private File avroOutputDirectory;
/**
* The output encoding.
*
* @parameter default-value="${project.build.sourceEncoding}"
*/
private String encoding;
/**
* Whether to use ReflectData.AllowNull.
*
* @parameter default-value="false"
*/
private boolean allowNull;
/**
* Override the default ReflectData implementation with an extension. Must be a
* subclass of ReflectData.
*
* @parameter property="reflectDataImplementation"
*/
private String reflectDataImplementation;
/**
* The current Maven project.
*
* @parameter default-value="${project}"
* @readonly
* @required
*/
protected MavenProject project;
private ClassLoader classLoader;
private ReflectData reflectData;
public void execute() throws MojoExecutionException {
classLoader = getClassLoader();
reflectData = getReflectData();
if (encoding == null) {
encoding = Charset.defaultCharset().name();
getLog().warn("Property project.build.sourceEncoding not set, using system default " + encoding);
}
for (File sourceDirectory : javaSourceDirectories) {
induceClasses(sourceDirectory);
}
}
private void induceClasses(File sourceDirectory) throws MojoExecutionException {
File[] files = sourceDirectory.listFiles();
if (files == null) {
throw new MojoExecutionException("Unable to list files from directory: " + sourceDirectory.getName());
}
for (File inputFile : files) {
if (inputFile.isDirectory()) {
induceClasses(inputFile);
continue;
}
String className = parseClassName(inputFile.getPath());
if (className == null) {
continue; // Not a java file, continue
}
Class<?> klass = loadClass(classLoader, className);
String fileName = getOutputFileName(klass);
File outputFile = new File(fileName);
outputFile.getParentFile().mkdirs();
try (PrintWriter writer = new PrintWriter(fileName, encoding)) {
if (klass.isInterface()) {
writer.println(reflectData.getProtocol(klass).toString(true));
} else {
writer.println(reflectData.getSchema(klass).toString(true));
}
} catch (AvroRuntimeException e) {
throw new MojoExecutionException("Failed to resolve schema or protocol for class " + klass.getCanonicalName(),
e);
} catch (Exception e) {
throw new MojoExecutionException("Failed to write output file for class " + klass.getCanonicalName(), e);
}
}
}
private String parseClassName(String fileName) {
String indentifier = "java" + File.separator;
int index = fileName.lastIndexOf(indentifier);
String namespacedFileName = fileName.substring(index + indentifier.length());
if (!namespacedFileName.endsWith(".java")) {
return null;
}
return namespacedFileName.replace(File.separator, ".").replaceFirst("\\.java$", "");
}
private String getOutputFileName(Class klass) {
String filename = avroOutputDirectory.getPath() + File.separator + klass.getName().replace(".", File.separator);
if (klass.isInterface()) {
return filename.concat(".avpr");
} else {
return filename.concat(".avsc");
}
}
private ReflectData getReflectData() throws MojoExecutionException {
if (reflectDataImplementation == null) {
return allowNull ? ReflectData.AllowNull.get() : ReflectData.get();
}
try {
Constructor<? extends ReflectData> constructor = loadClass(classLoader, reflectDataImplementation)
.asSubclass(ReflectData.class).getConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
} catch (Exception e) {
throw new MojoExecutionException(String.format(
"Could not load ReflectData custom implementation %s. Make sure that it has a no-args constructor",
reflectDataImplementation), e);
}
}
private Class<?> loadClass(ClassLoader classLoader, String className) throws MojoExecutionException {
try {
return classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
throw new MojoExecutionException("Failed to load class " + className, e);
}
}
private ClassLoader getClassLoader() throws MojoExecutionException {
ClassLoader classLoader;
try {
List<String> classpathElements = project.getRuntimeClasspathElements();
if (null == classpathElements) {
return Thread.currentThread().getContextClassLoader();
}
URL[] urls = new URL[classpathElements.size()];
for (int i = 0; i < classpathElements.size(); ++i) {
urls[i] = new File(classpathElements.get(i)).toURI().toURL();
}
classLoader = new URLClassLoader(urls, getClass().getClassLoader());
} catch (Exception e) {
throw new MojoExecutionException("Failed to obtain ClassLoader", e);
}
return classLoader;
}
}