summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorjfarrell <jfarrell@apache.org>2014-10-08 23:07:33 -0400
committerjfarrell <jfarrell@apache.org>2014-10-08 23:07:33 -0400
commitf98a67bb24569ac5dee0cf94d711dd3d63316447 (patch)
tree01123b37e5dc189f8eff5fea1fccfdd11620cde5 /contrib
parenta9ddab5fa791b223f526e07a9530fd292ff96646 (diff)
downloadthrift-f98a67bb24569ac5dee0cf94d711dd3d63316447.tar.gz
THRIFT-1536: Maven thrift plugin
Client: build Patch: David Trott Maven thrift plugin
Diffstat (limited to 'contrib')
-rw-r--r--contrib/thrift-maven-plugin/pom.xml86
-rw-r--r--contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/AbstractThriftMojo.java377
-rw-r--r--contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/Thrift.java262
-rw-r--r--contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftCompileMojo.java78
-rw-r--r--contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftTestCompileMojo.java74
-rw-r--r--contrib/thrift-maven-plugin/src/test/java/org/apache/thrift/maven/TestThrift.java163
-rw-r--r--contrib/thrift-maven-plugin/src/test/resources/idl/shared.thrift36
-rw-r--r--contrib/thrift-maven-plugin/src/test/resources/idl/tutorial.thrift152
8 files changed, 1228 insertions, 0 deletions
diff --git a/contrib/thrift-maven-plugin/pom.xml b/contrib/thrift-maven-plugin/pom.xml
new file mode 100644
index 000000000..5bc100414
--- /dev/null
+++ b/contrib/thrift-maven-plugin/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+<project
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.thrift</groupId>
+ <artifactId>thrift-maven-plugin</artifactId>
+ <packaging>maven-plugin</packaging>
+ <name>thrift-maven-plugin</name>
+ <version>1.0-SNAPSHOT</version>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.7</source>
+ <target>1.7</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.14.1</version>
+ <configuration>
+ <systemPropertyVariables>
+ <thriftExecutable>${thrift.compiler}</thriftExecutable>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.11</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-plugin-api</artifactId>
+ <version>3.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-project</artifactId>
+ <version>2.2.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>14.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-utils</artifactId>
+ <version>3.0.14</version>
+ </dependency>
+ </dependencies>
+ <properties>
+ <thrift.root>${basedir}/../..</thrift.root>
+ <thrift.compiler>${thrift.root}/compiler/cpp/thrift</thrift.compiler>
+ <thrift.test.home>${thrift.root}/test</thrift.test.home>
+ </properties>
+</project>
diff --git a/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/AbstractThriftMojo.java b/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/AbstractThriftMojo.java
new file mode 100644
index 000000000..a3b5af6c4
--- /dev/null
+++ b/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/AbstractThriftMojo.java
@@ -0,0 +1,377 @@
+/*
+ * 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.thrift.maven;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.MavenProjectHelper;
+import org.codehaus.plexus.util.cli.CommandLineException;
+import org.codehaus.plexus.util.io.RawInputStreamFacade;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Sets.newHashSet;
+import static java.lang.String.format;
+import static java.util.Arrays.asList;
+import static java.util.Collections.list;
+import static org.codehaus.plexus.util.FileUtils.cleanDirectory;
+import static org.codehaus.plexus.util.FileUtils.copyStreamToFile;
+import static org.codehaus.plexus.util.FileUtils.getFiles;
+
+/**
+ * Abstract Mojo implementation.
+ * <p/>
+ * This class is extended by {@link org.apache.thrift.maven.ThriftCompileMojo} and
+ * {@link org.apache.thrift.maven.ThriftTestCompileMojo} in order to override the specific configuration for
+ * compiling the main or test classes respectively.
+ */
+abstract class AbstractThriftMojo extends AbstractMojo {
+
+ private static final String THRIFT_FILE_SUFFIX = ".thrift";
+
+ private static final String DEFAULT_INCLUDES = "**/*" + THRIFT_FILE_SUFFIX;
+
+ /**
+ * The current Maven project.
+ *
+ * @parameter default-value="${project}"
+ * @readonly
+ * @required
+ */
+ protected MavenProject project;
+
+ /**
+ * A helper used to add resources to the project.
+ *
+ * @component
+ * @required
+ */
+ protected MavenProjectHelper projectHelper;
+
+ /**
+ * This is the path to the {@code thrift} executable. By default it will search the {@code $PATH}.
+ *
+ * @parameter default-value="thrift"
+ * @required
+ */
+ private String thriftExecutable;
+
+ /**
+ * This string is passed to the {@code --gen} option of the {@code thrift} parameter. By default
+ * it will generate Java output. The main reason for this option is to be able to add options
+ * to the Java generator - if you generate something else, you're on your own.
+ *
+ * @parameter default-value="java:hashcode"
+ */
+ private String generator;
+
+ /**
+ * @parameter
+ */
+ private File[] additionalThriftPathElements = new File[]{};
+
+ /**
+ * Since {@code thrift} cannot access jars, thrift files in dependencies are extracted to this location
+ * and deleted on exit. This directory is always cleaned during execution.
+ *
+ * @parameter property="${project.build.directory}/thrift-dependencies"
+ * @required
+ */
+ private File temporaryThriftFileDirectory;
+
+ /**
+ * This is the path to the local maven {@code repository}.
+ *
+ * @parameter default-value="${localRepository}"
+ * @required
+ */
+ private ArtifactRepository localRepository;
+
+ /**
+ * Set this to {@code false} to disable hashing of dependent jar paths.
+ * <p/>
+ * This plugin expands jars on the classpath looking for embedded .thrift files.
+ * Normally these paths are hashed (MD5) to avoid issues with long file names on windows.
+ * However if this property is set to {@code false} longer paths will be used.
+ *
+ * @parameter default-value="true"
+ * @required
+ */
+ private boolean hashDependentPaths;
+
+ /**
+ * @parameter
+ */
+ private Set<String> includes = ImmutableSet.of(DEFAULT_INCLUDES);
+
+ /**
+ * @parameter
+ */
+ private Set<String> excludes = ImmutableSet.of();
+
+ /**
+ * @parameter
+ */
+ private long staleMillis = 0;
+
+ /**
+ * @parameter
+ */
+ private boolean checkStaleness = false;
+
+ /**
+ * Executes the mojo.
+ */
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ checkParameters();
+ final File thriftSourceRoot = getThriftSourceRoot();
+ if (thriftSourceRoot.exists()) {
+ try {
+ ImmutableSet<File> thriftFiles = findThriftFilesInDirectory(thriftSourceRoot);
+ final File outputDirectory = getOutputDirectory();
+ ImmutableSet<File> outputFiles = findGeneratedFilesInDirectory(getOutputDirectory());
+
+ if (thriftFiles.isEmpty()) {
+ getLog().info("No thrift files to compile.");
+ } else if (checkStaleness && ((lastModified(thriftFiles) + staleMillis) < lastModified(outputFiles))) {
+ getLog().info("Skipping compilation because target directory newer than sources.");
+ attachFiles();
+ } else {
+ ImmutableSet<File> derivedThriftPathElements =
+ makeThriftPathFromJars(temporaryThriftFileDirectory, getDependencyArtifactFiles());
+ outputDirectory.mkdirs();
+
+ // Quick fix to fix issues with two mvn installs in a row (ie no clean)
+ // cleanDirectory(outputDirectory);
+
+ Thrift thrift = new Thrift.Builder(thriftExecutable, outputDirectory)
+ .setGenerator(generator)
+ .addThriftPathElement(thriftSourceRoot)
+ .addThriftPathElements(derivedThriftPathElements)
+ .addThriftPathElements(asList(additionalThriftPathElements))
+ .addThriftFiles(thriftFiles)
+ .build();
+ final int exitStatus = thrift.compile();
+ if (exitStatus != 0) {
+ getLog().error("thrift failed output: " + thrift.getOutput());
+ getLog().error("thrift failed error: " + thrift.getError());
+ throw new MojoFailureException(
+ "thrift did not exit cleanly. Review output for more information.");
+ }
+ attachFiles();
+ }
+ } catch (IOException e) {
+ throw new MojoExecutionException("An IO error occured", e);
+ } catch (IllegalArgumentException e) {
+ throw new MojoFailureException("thrift failed to execute because: " + e.getMessage(), e);
+ } catch (CommandLineException e) {
+ throw new MojoExecutionException("An error occurred while invoking thrift.", e);
+ }
+ } else {
+ getLog().info(format("%s does not exist. Review the configuration or consider disabling the plugin.",
+ thriftSourceRoot));
+ }
+ }
+
+ ImmutableSet<File> findGeneratedFilesInDirectory(File directory) throws IOException {
+ if (directory == null || !directory.isDirectory())
+ return ImmutableSet.of();
+
+ List<File> javaFilesInDirectory = getFiles(directory, "**/*.java", null);
+ return ImmutableSet.copyOf(javaFilesInDirectory);
+ }
+
+ private long lastModified(ImmutableSet<File> files) {
+ long result = 0;
+ for (File file : files) {
+ if (file.lastModified() > result)
+ result = file.lastModified();
+ }
+ return result;
+ }
+
+ private void checkParameters() {
+ checkNotNull(project, "project");
+ checkNotNull(projectHelper, "projectHelper");
+ checkNotNull(thriftExecutable, "thriftExecutable");
+ checkNotNull(generator, "generator");
+ final File thriftSourceRoot = getThriftSourceRoot();
+ checkNotNull(thriftSourceRoot);
+ checkArgument(!thriftSourceRoot.isFile(), "thriftSourceRoot is a file, not a diretory");
+ checkNotNull(temporaryThriftFileDirectory, "temporaryThriftFileDirectory");
+ checkState(!temporaryThriftFileDirectory.isFile(), "temporaryThriftFileDirectory is a file, not a directory");
+ final File outputDirectory = getOutputDirectory();
+ checkNotNull(outputDirectory);
+ checkState(!outputDirectory.isFile(), "the outputDirectory is a file, not a directory");
+ }
+
+ protected abstract File getThriftSourceRoot();
+
+ protected abstract List<Artifact> getDependencyArtifacts();
+
+ protected abstract File getOutputDirectory();
+
+ protected abstract void attachFiles();
+
+ /**
+ * Gets the {@link File} for each dependency artifact.
+ *
+ * @return A set of all dependency artifacts.
+ */
+ private ImmutableSet<File> getDependencyArtifactFiles() {
+ Set<File> dependencyArtifactFiles = newHashSet();
+ for (Artifact artifact : getDependencyArtifacts()) {
+ dependencyArtifactFiles.add(artifact.getFile());
+ }
+ return ImmutableSet.copyOf(dependencyArtifactFiles);
+ }
+
+ /**
+ * @throws IOException
+ */
+ ImmutableSet<File> makeThriftPathFromJars(File temporaryThriftFileDirectory, Iterable<File> classpathElementFiles)
+ throws IOException, MojoExecutionException {
+ checkNotNull(classpathElementFiles, "classpathElementFiles");
+ // clean the temporary directory to ensure that stale files aren't used
+ if (temporaryThriftFileDirectory.exists()) {
+ cleanDirectory(temporaryThriftFileDirectory);
+ }
+ Set<File> thriftDirectories = newHashSet();
+ for (File classpathElementFile : classpathElementFiles) {
+ // for some reason under IAM, we receive poms as dependent files
+ // I am excluding .xml rather than including .jar as there may be other extensions in use (sar, har, zip)
+ if (classpathElementFile.isFile() && classpathElementFile.canRead() &&
+ !classpathElementFile.getName().endsWith(".xml")) {
+
+ // create the jar file. the constructor validates.
+ JarFile classpathJar;
+ try {
+ classpathJar = new JarFile(classpathElementFile);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(format(
+ "%s was not a readable artifact", classpathElementFile));
+ }
+ for (JarEntry jarEntry : list(classpathJar.entries())) {
+ final String jarEntryName = jarEntry.getName();
+ if (jarEntry.getName().endsWith(THRIFT_FILE_SUFFIX)) {
+ final File uncompressedCopy =
+ new File(new File(temporaryThriftFileDirectory,
+ truncatePath(classpathJar.getName())), jarEntryName);
+ uncompressedCopy.getParentFile().mkdirs();
+ copyStreamToFile(new RawInputStreamFacade(classpathJar
+ .getInputStream(jarEntry)), uncompressedCopy);
+ thriftDirectories.add(uncompressedCopy.getParentFile());
+ }
+ }
+ } else if (classpathElementFile.isDirectory()) {
+ File[] thriftFiles = classpathElementFile.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.endsWith(THRIFT_FILE_SUFFIX);
+ }
+ });
+
+ if (thriftFiles.length > 0) {
+ thriftDirectories.add(classpathElementFile);
+ }
+ }
+ }
+ return ImmutableSet.copyOf(thriftDirectories);
+ }
+
+ ImmutableSet<File> findThriftFilesInDirectory(File directory) throws IOException {
+ checkNotNull(directory);
+ checkArgument(directory.isDirectory(), "%s is not a directory", directory);
+ List<File> thriftFilesInDirectory = getFiles(directory,
+ Joiner.on(",").join(includes),
+ Joiner.on(",").join(excludes));
+ return ImmutableSet.copyOf(thriftFilesInDirectory);
+ }
+
+ ImmutableSet<File> findThriftFilesInDirectories(Iterable<File> directories) throws IOException {
+ checkNotNull(directories);
+ Set<File> thriftFiles = newHashSet();
+ for (File directory : directories) {
+ thriftFiles.addAll(findThriftFilesInDirectory(directory));
+ }
+ return ImmutableSet.copyOf(thriftFiles);
+ }
+
+ /**
+ * Truncates the path of jar files so that they are relative to the local repository.
+ *
+ * @param jarPath the full path of a jar file.
+ * @return the truncated path relative to the local repository or root of the drive.
+ */
+ String truncatePath(final String jarPath) throws MojoExecutionException {
+
+ if (hashDependentPaths) {
+ try {
+ return toHexString(MessageDigest.getInstance("MD5").digest(jarPath.getBytes()));
+ } catch (NoSuchAlgorithmException e) {
+ throw new MojoExecutionException("Failed to expand dependent jar", e);
+ }
+ }
+
+ String repository = localRepository.getBasedir().replace('\\', '/');
+ if (!repository.endsWith("/")) {
+ repository += "/";
+ }
+
+ String path = jarPath.replace('\\', '/');
+ int repositoryIndex = path.indexOf(repository);
+ if (repositoryIndex != -1) {
+ path = path.substring(repositoryIndex + repository.length());
+ }
+
+ // By now the path should be good, but do a final check to fix windows machines.
+ int colonIndex = path.indexOf(':');
+ if (colonIndex != -1) {
+ // 2 = :\ in C:\
+ path = path.substring(colonIndex + 2);
+ }
+
+ return path;
+ }
+
+ private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
+
+ public static String toHexString(byte[] byteArray) {
+ final StringBuilder hexString = new StringBuilder(2 * byteArray.length);
+ for (final byte b : byteArray) {
+ hexString.append(HEX_CHARS[(b & 0xF0) >> 4]).append(HEX_CHARS[b & 0x0F]);
+ }
+ return hexString.toString();
+ }
+}
diff --git a/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/Thrift.java b/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/Thrift.java
new file mode 100644
index 000000000..6eea95448
--- /dev/null
+++ b/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/Thrift.java
@@ -0,0 +1,262 @@
+/*
+ * 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.thrift.maven;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.codehaus.plexus.util.cli.CommandLineException;
+import org.codehaus.plexus.util.cli.CommandLineUtils;
+import org.codehaus.plexus.util.cli.Commandline;
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Lists.newLinkedList;
+import static com.google.common.collect.Sets.newHashSet;
+
+/**
+ * This class represents an invokable configuration of the {@code thrift}
+ * compiler. The actual executable is invoked using the plexus
+ * {@link Commandline}.
+ * <p/>
+ * This class currently only supports generating java source files.
+ */
+final class Thrift {
+
+ final static String GENERATED_JAVA = "gen-java";
+
+ private final String executable;
+ private final String generator;
+ private final ImmutableSet<File> thriftPathElements;
+ private final ImmutableSet<File> thriftFiles;
+ private final File javaOutputDirectory;
+ private final CommandLineUtils.StringStreamConsumer output;
+ private final CommandLineUtils.StringStreamConsumer error;
+
+ /**
+ * Constructs a new instance. This should only be used by the {@link Builder}.
+ *
+ * @param executable The path to the {@code thrift} executable.
+ * @param generator The value for the {@code --gen} option.
+ * @param thriftPath The directories in which to search for imports.
+ * @param thriftFiles The thrift source files to compile.
+ * @param javaOutputDirectory The directory into which the java source files
+ * will be generated.
+ */
+ private Thrift(String executable, String generator, ImmutableSet<File> thriftPath,
+ ImmutableSet<File> thriftFiles, File javaOutputDirectory) {
+ this.executable = checkNotNull(executable, "executable");
+ this.generator = checkNotNull(generator, "generator");
+ this.thriftPathElements = checkNotNull(thriftPath, "thriftPath");
+ this.thriftFiles = checkNotNull(thriftFiles, "thriftFiles");
+ this.javaOutputDirectory = checkNotNull(javaOutputDirectory, "javaOutputDirectory");
+ this.error = new CommandLineUtils.StringStreamConsumer();
+ this.output = new CommandLineUtils.StringStreamConsumer();
+ }
+
+ /**
+ * Invokes the {@code thrift} compiler using the configuration specified at
+ * construction.
+ *
+ * @return The exit status of {@code thrift}.
+ * @throws CommandLineException
+ */
+ public int compile() throws CommandLineException {
+
+ for (File thriftFile : thriftFiles) {
+ Commandline cl = new Commandline();
+ cl.setExecutable(executable);
+ cl.addArguments(buildThriftCommand(thriftFile).toArray(new String[]{}));
+ final int result = CommandLineUtils.executeCommandLine(cl, null, output, error);
+
+ if (result != 0) {
+ return result;
+ }
+ }
+
+ // result will always be 0 here.
+ return 0;
+ }
+
+ /**
+ * Creates the command line arguments.
+ * <p/>
+ * This method has been made visible for testing only.
+ *
+ * @param thriftFile
+ * @return A list consisting of the executable followed by any arguments.
+ */
+ ImmutableList<String> buildThriftCommand(final File thriftFile) {
+ final List<String> command = newLinkedList();
+ // add the executable
+ for (File thriftPathElement : thriftPathElements) {
+ command.add("-I");
+ command.add(thriftPathElement.toString());
+ }
+ command.add("-out");
+ command.add(javaOutputDirectory.toString());
+ command.add("--gen");
+ command.add(generator);
+ command.add(thriftFile.toString());
+ return ImmutableList.copyOf(command);
+ }
+
+ /**
+ * @return the output
+ */
+ public String getOutput() {
+ return output.getOutput();
+ }
+
+ /**
+ * @return the error
+ */
+ public String getError() {
+ return error.getOutput();
+ }
+
+ /**
+ * This class builds {@link Thrift} instances.
+ */
+ static final class Builder {
+ private final String executable;
+ private final File javaOutputDirectory;
+ private Set<File> thriftPathElements;
+ private Set<File> thriftFiles;
+ private String generator;
+
+ /**
+ * Constructs a new builder. The two parameters are present as they are
+ * required for all {@link Thrift} instances.
+ *
+ * @param executable The path to the {@code thrift} executable.
+ * @param javaOutputDirectory The directory into which the java source files
+ * will be generated.
+ * @throws NullPointerException If either of the arguments are {@code null}.
+ * @throws IllegalArgumentException If the {@code javaOutputDirectory} is
+ * not a directory.
+ */
+ public Builder(String executable, File javaOutputDirectory) {
+ this.executable = checkNotNull(executable, "executable");
+ this.javaOutputDirectory = checkNotNull(javaOutputDirectory);
+ checkArgument(javaOutputDirectory.isDirectory());
+ this.thriftFiles = newHashSet();
+ this.thriftPathElements = newHashSet();
+ }
+
+ /**
+ * Adds a thrift file to be compiled. Thrift files must be on the thriftpath
+ * and this method will fail if a thrift file is added without first adding a
+ * parent directory to the thriftpath.
+ *
+ * @param thriftFile
+ * @return The builder.
+ * @throws IllegalStateException If a thrift file is added without first
+ * adding a parent directory to the thriftpath.
+ * @throws NullPointerException If {@code thriftFile} is {@code null}.
+ */
+ public Builder addThriftFile(File thriftFile) {
+ checkNotNull(thriftFile);
+ checkArgument(thriftFile.isFile());
+ checkArgument(thriftFile.getName().endsWith(".thrift"));
+ checkThriftFileIsInThriftPath(thriftFile);
+ thriftFiles.add(thriftFile);
+ return this;
+ }
+
+ /**
+ * Adds the option string for the Thrift executable's {@code --gen} parameter.
+ *
+ * @param generator
+ * @return The builder
+ * @throws NullPointerException If {@code generator} is {@code null}.
+ */
+ public Builder setGenerator(String generator) {
+ checkNotNull(generator);
+ this.generator = generator;
+ return this;
+ }
+
+ private void checkThriftFileIsInThriftPath(File thriftFile) {
+ assert thriftFile.isFile();
+ checkState(checkThriftFileIsInThriftPathHelper(thriftFile.getParentFile()));
+ }
+
+ private boolean checkThriftFileIsInThriftPathHelper(File directory) {
+ assert directory.isDirectory();
+ if (thriftPathElements.contains(directory)) {
+ return true;
+ } else {
+ final File parentDirectory = directory.getParentFile();
+ return (parentDirectory == null) ? false
+ : checkThriftFileIsInThriftPathHelper(parentDirectory);
+ }
+ }
+
+ /**
+ * @see #addThriftFile(File)
+ */
+ public Builder addThriftFiles(Iterable<File> thriftFiles) {
+ for (File thriftFile : thriftFiles) {
+ addThriftFile(thriftFile);
+ }
+ return this;
+ }
+
+ /**
+ * Adds the {@code thriftPathElement} to the thriftPath.
+ *
+ * @param thriftPathElement A directory to be searched for imported thrift message
+ * buffer definitions.
+ * @return The builder.
+ * @throws NullPointerException If {@code thriftPathElement} is {@code null}.
+ * @throws IllegalArgumentException If {@code thriftPathElement} is not a
+ * directory.
+ */
+ public Builder addThriftPathElement(File thriftPathElement) {
+ checkNotNull(thriftPathElement);
+ checkArgument(thriftPathElement.isDirectory());
+ thriftPathElements.add(thriftPathElement);
+ return this;
+ }
+
+ /**
+ * @see #addThriftPathElement(File)
+ */
+ public Builder addThriftPathElements(Iterable<File> thriftPathElements) {
+ for (File thriftPathElement : thriftPathElements) {
+ addThriftPathElement(thriftPathElement);
+ }
+ return this;
+ }
+
+ /**
+ * @return A configured {@link Thrift} instance.
+ * @throws IllegalStateException If no thrift files have been added.
+ */
+ public Thrift build() {
+ checkState(!thriftFiles.isEmpty());
+ return new Thrift(executable, generator, ImmutableSet.copyOf(thriftPathElements),
+ ImmutableSet.copyOf(thriftFiles), javaOutputDirectory);
+ }
+ }
+}
diff --git a/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftCompileMojo.java b/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftCompileMojo.java
new file mode 100644
index 000000000..b4f75714b
--- /dev/null
+++ b/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftCompileMojo.java
@@ -0,0 +1,78 @@
+/*
+ * 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.thrift.maven;
+
+import java.io.File;
+import java.util.List;
+import org.apache.maven.artifact.Artifact;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * This mojo executes the {@code thrift} compiler for generating java sources
+ * from thrift definitions. It also searches dependency artifacts for
+ * thrift files and includes them in the thriftPath so that they can be
+ * referenced. Finally, it adds the thrift files to the project as resources so
+ * that they are included in the final artifact.
+ *
+ * @phase generate-sources
+ * @goal compile
+ * @requiresDependencyResolution compile
+ */
+public final class ThriftCompileMojo extends AbstractThriftMojo {
+
+ /**
+ * The source directories containing the sources to be compiled.
+ *
+ * @parameter default-value="${basedir}/src/main/thrift"
+ * @required
+ */
+ private File thriftSourceRoot;
+
+ /**
+ * This is the directory into which the {@code .java} will be created.
+ *
+ * @parameter default-value="${project.build.directory}/generated-sources/thrift"
+ * @required
+ */
+ private File outputDirectory;
+
+ @Override
+ protected List<Artifact> getDependencyArtifacts() {
+ List<Artifact> compileArtifacts = project.getCompileArtifacts();
+ return compileArtifacts;
+ }
+
+ @Override
+ protected File getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ @Override
+ protected File getThriftSourceRoot() {
+ return thriftSourceRoot;
+ }
+
+ @Override
+ protected void attachFiles() {
+ project.addCompileSourceRoot(outputDirectory.getAbsolutePath());
+ projectHelper.addResource(project, thriftSourceRoot.getAbsolutePath(),
+ ImmutableList.of("**/*.thrift"), null);
+ }
+}
diff --git a/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftTestCompileMojo.java b/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftTestCompileMojo.java
new file mode 100644
index 000000000..fb89d966f
--- /dev/null
+++ b/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftTestCompileMojo.java
@@ -0,0 +1,74 @@
+/*
+ * 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.thrift.maven;
+
+import java.io.File;
+import java.util.List;
+import org.apache.maven.artifact.Artifact;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * @phase generate-test-sources
+ * @goal testCompile
+ * @requiresDependencyResolution test
+ */
+public final class ThriftTestCompileMojo extends AbstractThriftMojo {
+
+ /**
+ * The source directories containing the sources to be compiled.
+ *
+ * @parameter default-value="${basedir}/src/test/thrift"
+ * @required
+ */
+ private File thriftTestSourceRoot;
+
+ /**
+ * This is the directory into which the {@code .java} will be created.
+ *
+ * @parameter default-value="${project.build.directory}/generated-test-sources/thrift"
+ * @required
+ */
+ private File outputDirectory;
+
+ @Override
+ protected void attachFiles() {
+ project.addTestCompileSourceRoot(outputDirectory.getAbsolutePath());
+ projectHelper.addTestResource(project, thriftTestSourceRoot.getAbsolutePath(),
+ ImmutableList.of("**/*.thrift"), null);
+ }
+
+ @Override
+ protected List<Artifact> getDependencyArtifacts() {
+ // TODO(gak): maven-project needs generics
+ @SuppressWarnings("unchecked")
+ List<Artifact> testArtifacts = project.getTestArtifacts();
+ return testArtifacts;
+ }
+
+ @Override
+ protected File getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ @Override
+ protected File getThriftSourceRoot() {
+ return thriftTestSourceRoot;
+ }
+}
diff --git a/contrib/thrift-maven-plugin/src/test/java/org/apache/thrift/maven/TestThrift.java b/contrib/thrift-maven-plugin/src/test/java/org/apache/thrift/maven/TestThrift.java
new file mode 100644
index 000000000..3ecd094ea
--- /dev/null
+++ b/contrib/thrift-maven-plugin/src/test/java/org/apache/thrift/maven/TestThrift.java
@@ -0,0 +1,163 @@
+/*
+ * 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.thrift.maven;
+
+import org.codehaus.plexus.util.FileUtils;
+import org.codehaus.plexus.util.cli.CommandLineException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class TestThrift {
+
+ private File testRootDir;
+ private File idlDir;
+ private File genJavaDir;
+ private Thrift.Builder builder;
+
+ @Before
+ public void setup() throws Exception {
+ final File tmpDir = new File(System.getProperty("java.io.tmpdir"));
+ testRootDir = new File(tmpDir, "thrift-test");
+
+ if (testRootDir.exists()) {
+ FileUtils.cleanDirectory(testRootDir);
+ } else {
+ assertTrue("Failed to create output directory for test: " + testRootDir.getPath(), testRootDir.mkdir());
+ }
+
+ File testResourceDir = new File("src/test/resources");
+ assertTrue("Unable to find test resources", testRootDir.exists());
+
+ String thriftExecutable = System.getProperty("thriftExecutable", "thrift");
+ if (!(new File(thriftExecutable).exists())) {
+ thriftExecutable = "thrift";
+ }
+ System.out.println("Thrift compiler: " + thriftExecutable);
+
+ idlDir = new File(testResourceDir, "idl");
+ genJavaDir = new File(testRootDir, Thrift.GENERATED_JAVA);
+ builder = new Thrift.Builder(thriftExecutable, testRootDir);
+ builder
+ .setGenerator("java")
+ .addThriftPathElement(idlDir);
+ }
+
+ @Test
+ public void testThriftCompile() throws Exception {
+ executeThriftCompile();
+ }
+
+ @Test
+ public void testThriftCompileWithGeneratorOption() throws Exception {
+ builder.setGenerator("java:private-members,hashcode");
+ executeThriftCompile();
+ }
+
+ private void executeThriftCompile() throws CommandLineException {
+ final File thriftFile = new File(idlDir, "shared.thrift");
+
+ builder.addThriftFile(thriftFile);
+
+ final Thrift thrift = builder.build();
+
+ assertTrue("File not found: shared.thrift", thriftFile.exists());
+ assertFalse("gen-java directory should not exist", genJavaDir.exists());
+
+ // execute the compile
+ final int result = thrift.compile();
+ assertEquals(0, result);
+
+ assertFalse("gen-java directory was not removed", genJavaDir.exists());
+ assertTrue("generated java code doesn't exist",
+ new File(testRootDir, "shared/SharedService.java").exists());
+ }
+
+ @Test
+ public void testThriftMultipleFileCompile() throws Exception {
+ final File sharedThrift = new File(idlDir, "shared.thrift");
+ final File tutorialThrift = new File(idlDir, "tutorial.thrift");
+
+ builder.addThriftFile(sharedThrift);
+ builder.addThriftFile(tutorialThrift);
+
+ final Thrift thrift = builder.build();
+
+ assertTrue("File not found: shared.thrift", sharedThrift.exists());
+ assertFalse("gen-java directory should not exist", genJavaDir.exists());
+
+ // execute the compile
+ final int result = thrift.compile();
+ assertEquals(0, result);
+
+ assertFalse("gen-java directory was not removed", genJavaDir.exists());
+ assertTrue("generated java code doesn't exist",
+ new File(testRootDir, "shared/SharedService.java").exists());
+ assertTrue("generated java code doesn't exist",
+ new File(testRootDir, "tutorial/InvalidOperation.java").exists());
+ }
+
+ @Test
+ public void testBadCompile() throws Exception {
+ final File thriftFile = new File(testRootDir, "missing.thrift");
+ builder.addThriftPathElement(testRootDir);
+
+ // Hacking around checks in addThrift file.
+ assertTrue(thriftFile.createNewFile());
+ builder.addThriftFile(thriftFile);
+ assertTrue(thriftFile.delete());
+
+ final Thrift thrift = builder.build();
+
+ assertTrue(!thriftFile.exists());
+ assertFalse("gen-java directory should not exist", genJavaDir.exists());
+
+ // execute the compile
+ final int result = thrift.compile();
+ assertEquals(1, result);
+ }
+
+ @Test
+ public void testFileInPathPreCondition() throws Exception {
+ final File thriftFile = new File(testRootDir, "missing.thrift");
+
+ // Hacking around checks in addThrift file.
+ assertTrue(thriftFile.createNewFile());
+ try {
+ builder.addThriftFile(thriftFile);
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ }
+ }
+
+ @After
+ public void cleanup() throws Exception {
+ if (testRootDir.exists()) {
+ FileUtils.cleanDirectory(testRootDir);
+ assertTrue("Failed to delete output directory for test: " + testRootDir.getPath(), testRootDir.delete());
+ }
+ }
+}
diff --git a/contrib/thrift-maven-plugin/src/test/resources/idl/shared.thrift b/contrib/thrift-maven-plugin/src/test/resources/idl/shared.thrift
new file mode 100644
index 000000000..475e7f803
--- /dev/null
+++ b/contrib/thrift-maven-plugin/src/test/resources/idl/shared.thrift
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+/**
+ * This Thrift file can be included by other Thrift files that want to share
+ * these definitions.
+ */
+
+namespace cpp shared
+namespace java shared
+namespace perl shared
+
+struct SharedStruct {
+ 1: i32 key
+ 2: string value
+}
+
+service SharedService {
+ SharedStruct getStruct(1: i32 key)
+}
diff --git a/contrib/thrift-maven-plugin/src/test/resources/idl/tutorial.thrift b/contrib/thrift-maven-plugin/src/test/resources/idl/tutorial.thrift
new file mode 100644
index 000000000..86e433dd3
--- /dev/null
+++ b/contrib/thrift-maven-plugin/src/test/resources/idl/tutorial.thrift
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+# Thrift Tutorial
+# Mark Slee (mcslee@facebook.com)
+#
+# This file aims to teach you how to use Thrift, in a .thrift file. Neato. The
+# first thing to notice is that .thrift files support standard shell comments.
+# This lets you make your thrift file executable and include your Thrift build
+# step on the top line. And you can place comments like this anywhere you like.
+#
+# Before running this file, you will need to have installed the thrift compiler
+# into /usr/local/bin.
+
+/**
+ * The first thing to know about are types. The available types in Thrift are:
+ *
+ * bool Boolean, one byte
+ * byte Signed byte
+ * i16 Signed 16-bit integer
+ * i32 Signed 32-bit integer
+ * i64 Signed 64-bit integer
+ * double 64-bit floating point value
+ * string String
+ * binary Blob (byte array)
+ * map<t1,t2> Map from one type to another
+ * list<t1> Ordered list of one type
+ * set<t1> Set of unique elements of one type
+ *
+ * Did you also notice that Thrift supports C style comments?
+ */
+
+// Just in case you were wondering... yes. We support simple C comments too.
+
+/**
+ * Thrift files can reference other Thrift files to include common struct
+ * and service definitions. These are found using the current path, or by
+ * searching relative to any paths specified with the -I compiler flag.
+ *
+ * Included objects are accessed using the name of the .thrift file as a
+ * prefix. i.e. shared.SharedObject
+ */
+include "shared.thrift"
+
+/**
+ * Thrift files can namespace, package, or prefix their output in various
+ * target languages.
+ */
+namespace cpp tutorial
+namespace java tutorial
+namespace php tutorial
+namespace perl tutorial
+namespace smalltalk.category Thrift.Tutorial
+
+/**
+ * Thrift lets you do typedefs to get pretty names for your types. Standard
+ * C style here.
+ */
+typedef i32 MyInteger
+
+/**
+ * Thrift also lets you define constants for use across languages. Complex
+ * types and structs are specified using JSON notation.
+ */
+const i32 INT32CONSTANT = 9853
+const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
+
+/**
+ * You can define enums, which are just 32 bit integers. Values are optional
+ * and start at 1 if not supplied, C style again.
+ */
+enum Operation {
+ ADD = 1,
+ SUBTRACT = 2,
+ MULTIPLY = 3,
+ DIVIDE = 4
+}
+
+/**
+ * Structs are the basic complex data structures. They are comprised of fields
+ * which each have an integer identifier, a type, a symbolic name, and an
+ * optional default value.
+ *
+ * Fields can be declared "optional", which ensures they will not be included
+ * in the serialized output if they aren't set. Note that this requires some
+ * manual management in some languages.
+ */
+struct Work {
+ 1: i32 num1 = 0,
+ 2: i32 num2,
+ 3: Operation op,
+ 4: optional string comment,
+}
+
+/**
+ * Structs can also be exceptions, if they are nasty.
+ */
+exception InvalidOperation {
+ 1: i32 what,
+ 2: string why
+}
+
+/**
+ * Ahh, now onto the cool part, defining a service. Services just need a name
+ * and can optionally inherit from another service using the extends keyword.
+ */
+service Calculator extends shared.SharedService {
+
+ /**
+ * A method definition looks like C code. It has a return type, arguments,
+ * and optionally a list of exceptions that it may throw. Note that argument
+ * lists and exception lists are specified using the exact same syntax as
+ * field lists in struct or exception definitions.
+ */
+
+ void ping(),
+
+ i32 add(1:i32 num1, 2:i32 num2),
+
+ i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
+
+ /**
+ * This method has a oneway modifier. That means the client only makes
+ * a request and does not listen for any response at all. Oneway methods
+ * must be void.
+ */
+ oneway void zip()
+
+}
+
+/**
+ * That just about covers the basics. Take a look in the test/ folder for more
+ * detailed examples. After you run this file, your generated code shows up
+ * in folders with names gen-<language>. The generated code isn't too scary
+ * to look at. It even has pretty indentation.
+ */