Package org.springframework.boot.cli.command.jar

Source Code of org.springframework.boot.cli.command.jar.JarCommand$GrabAnnotationTransform

/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed 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.springframework.boot.cli.command.jar;

import groovy.lang.Grab;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.Manifest;

import joptsimple.OptionSet;
import joptsimple.OptionSpec;

import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.OptionParsingCommand;
import org.springframework.boot.cli.command.jar.ResourceMatcher.MatchedResource;
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
import org.springframework.boot.cli.command.options.SourceOptions;
import org.springframework.boot.cli.command.status.ExitStatus;
import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
import org.springframework.boot.cli.jar.PackagedSpringApplicationLauncher;
import org.springframework.boot.loader.tools.JarWriter;
import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.Layouts;
import org.springframework.boot.loader.tools.Library;
import org.springframework.boot.loader.tools.LibraryScope;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;

/**
* {@link Command} to create a self-contained executable jar file from a CLI application
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
public class JarCommand extends OptionParsingCommand {

  private static final Layout LAYOUT = new Layouts.Jar();

  public JarCommand() {
    super("jar", "Create a self-contained "
        + "executable jar file from a Spring Groovy script",
        new JarOptionHandler());
  }

  @Override
  public String getUsageHelp() {
    return "[options] <jar-name> <files>";
  }

  private static final class JarOptionHandler extends CompilerOptionHandler {

    private OptionSpec<String> includeOption;

    private OptionSpec<String> excludeOption;

    @Override
    protected void doOptions() {
      this.includeOption = option(
          "include",
          "Pattern applied to directories on the classpath to find files to include in the resulting jar")
          .withRequiredArg().withValuesSeparatedBy(",").defaultsTo("");
      this.excludeOption = option(
          "exclude",
          "Pattern applied to directories on the claspath to find files to exclude from the resulting jar")
          .withRequiredArg().withValuesSeparatedBy(",").defaultsTo("");
    }

    @Override
    protected ExitStatus run(OptionSet options) throws Exception {
      List<?> nonOptionArguments = new ArrayList<Object>(
          options.nonOptionArguments());
      Assert.isTrue(nonOptionArguments.size() >= 2,
          "The name of the resulting jar and at least one source file must be specified");

      File output = new File((String) nonOptionArguments.remove(0));
      Assert.isTrue(output.getName().toLowerCase().endsWith(".jar"), "The output '"
          + output + "' is not a JAR file.");
      deleteIfExists(output);

      GroovyCompiler compiler = createCompiler(options);

      List<URL> classpath = getClassPathUrls(compiler);
      List<MatchedResource> classpathEntries = findMatchingClasspathEntries(
          classpath, options);

      String[] sources = new SourceOptions(nonOptionArguments).getSourcesArray();
      Class<?>[] compiledClasses = compiler.compile(sources);

      List<URL> dependencies = getClassPathUrls(compiler);
      dependencies.removeAll(classpath);

      writeJar(output, compiledClasses, classpathEntries, dependencies);
      return ExitStatus.OK;
    }

    private void deleteIfExists(File file) {
      if (file.exists() && !file.delete()) {
        throw new IllegalStateException("Failed to delete existing file "
            + file.getPath());
      }
    }

    private GroovyCompiler createCompiler(OptionSet options) {
      List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
          .createDefaultRepositoryConfiguration();
      GroovyCompilerConfiguration configuration = new OptionSetGroovyCompilerConfiguration(
          options, this, repositoryConfiguration);
      GroovyCompiler groovyCompiler = new GroovyCompiler(configuration);
      groovyCompiler.getAstTransformations().add(0, new GrabAnnotationTransform());
      return groovyCompiler;
    }

    private List<URL> getClassPathUrls(GroovyCompiler compiler) {
      return new ArrayList<URL>(Arrays.asList(compiler.getLoader().getURLs()));
    }

    private List<MatchedResource> findMatchingClasspathEntries(List<URL> classpath,
        OptionSet options) throws IOException {
      ResourceMatcher matcher = new ResourceMatcher(
          options.valuesOf(this.includeOption),
          options.valuesOf(this.excludeOption));
      List<File> roots = new ArrayList<File>();
      for (URL classpathEntry : classpath) {
        roots.add(new File(URI.create(classpathEntry.toString())));
      }
      return matcher.find(roots);
    }

    private void writeJar(File file, Class<?>[] compiledClasses,
        List<MatchedResource> classpathEntries, List<URL> dependencies)
        throws FileNotFoundException, IOException, URISyntaxException {
      JarWriter writer = new JarWriter(file);
      try {
        addManifest(writer, compiledClasses);
        addCliClasses(writer);
        for (Class<?> compiledClass : compiledClasses) {
          addClass(writer, compiledClass);
        }
        addClasspathEntries(writer, classpathEntries);
        addDependencies(writer, dependencies);
        writer.writeLoaderClasses();
      }
      finally {
        writer.close();
      }
    }

    private void addManifest(JarWriter writer, Class<?>[] compiledClasses)
        throws IOException {
      Manifest manifest = new Manifest();
      manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
      manifest.getMainAttributes().putValue("Main-Class",
          LAYOUT.getLauncherClassName());
      manifest.getMainAttributes().putValue("Start-Class",
          PackagedSpringApplicationLauncher.class.getName());
      manifest.getMainAttributes().putValue(
          PackagedSpringApplicationLauncher.SOURCE_ENTRY,
          commaDelimitedClassNames(compiledClasses));
      writer.writeManifest(manifest);
    }

    private String commaDelimitedClassNames(Class<?>[] classes) {
      StringBuilder builder = new StringBuilder();
      for (int i = 0; i < classes.length; i++) {
        builder.append(i == 0 ? "" : ",");
        builder.append(classes[i].getName());
      }
      return builder.toString();
    }

    private void addCliClasses(JarWriter writer) throws IOException {
      addClass(writer, PackagedSpringApplicationLauncher.class);
      Resource[] resources = new PathMatchingResourcePatternResolver()
          .getResources("org/springframework/boot/groovy/**");
      for (Resource resource : resources) {
        String url = resource.getURL().toString();
        addResource(writer, resource,
            url.substring(url.indexOf("org/springframework/boot/groovy/")));
      }
    }

    private void addClass(JarWriter writer, Class<?> sourceClass) throws IOException {
      String name = sourceClass.getName().replace(".", "/") + ".class";
      InputStream stream = sourceClass.getResourceAsStream("/" + name);
      writer.writeEntry(name, stream);
    }

    private void addResource(JarWriter writer, Resource resource, String name)
        throws IOException {
      InputStream stream = resource.getInputStream();
      writer.writeEntry(name, stream);
    }

    private void addClasspathEntries(JarWriter writer, List<MatchedResource> entries)
        throws IOException {
      for (MatchedResource entry : entries) {
        if (entry.isRoot()) {
          addDependency(writer, entry.getFile());
        }
        else {
          writer.writeEntry(entry.getName(),
              new FileInputStream(entry.getFile()));
        }
      }
    }

    private void addDependencies(JarWriter writer, List<URL> urls)
        throws IOException, URISyntaxException, FileNotFoundException {
      for (URL url : urls) {
        addDependency(writer, new File(url.toURI()));
      }
    }

    private void addDependency(JarWriter writer, File dependency)
        throws FileNotFoundException, IOException {
      if (dependency.isFile()) {
        writer.writeNestedLibrary("lib/", new Library(dependency,
            LibraryScope.COMPILE));
      }
    }

  }

  /**
   * {@link ASTTransformation} to change {@code @Grab} annotation values.
   */
  private static class GrabAnnotationTransform implements ASTTransformation {

    @Override
    public void visit(ASTNode[] nodes, SourceUnit source) {
      for (ASTNode node : nodes) {
        if (node instanceof ModuleNode) {
          visitModule((ModuleNode) node);
        }
      }
    }

    private void visitModule(ModuleNode module) {
      for (ClassNode classNode : module.getClasses()) {
        AnnotationNode annotation = new AnnotationNode(new ClassNode(Grab.class));
        annotation.addMember("value", new ConstantExpression("groovy"));
        classNode.addAnnotation(annotation);
        // We only need to do it at most once
        break;
      }
      // Remove GrabReolvers because all the dependencies are local now
      removeGrabResolver(module.getClasses());
      removeGrabResolver(module.getImports());
    }

    private void removeGrabResolver(List<? extends AnnotatedNode> nodes) {
      for (AnnotatedNode classNode : nodes) {
        List<AnnotationNode> annotations = classNode.getAnnotations();
        for (AnnotationNode node : new ArrayList<AnnotationNode>(annotations)) {
          if (node.getClassNode().getNameWithoutPackage()
              .equals("GrabResolver")) {
            annotations.remove(node);
          }
        }
      }
    }

  }

}
TOP

Related Classes of org.springframework.boot.cli.command.jar.JarCommand$GrabAnnotationTransform

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.