com.google.api.server.spi.tools.EndpointsToolAction Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of endpoints-framework-tools Show documentation
Show all versions of endpoints-framework-tools Show documentation
Command line tools for supporting the Endpoints Framework.
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* 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 com.google.api.server.spi.tools;
import com.google.api.server.spi.config.ApiConfigException;
import com.google.appengine.tools.util.Action;
import com.google.appengine.tools.util.Option;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.xml.parsers.ParserConfigurationException;
/**
* Base class for {@link Action}s used by the Cloud Endpoints command line tool. Users can use
* Endpoints command line tool to generate client library bundles and discovery docs.
*/
public abstract class EndpointsToolAction extends Action {
private static Logger log = Logger.getLogger(EndpointsToolAction.class.getName());
private static final int MAX_WIDTH = 80;
private static final int OPTION_INDENT = 2;
private static final int OPTION_DESCRIPTION_INDENT = 2;
// short name of class path option
public static final String OPTION_CLASS_PATH_SHORT = "cp";
// long name of class path option
public static final String OPTION_CLASS_PATH_LONG = "classpath";
// short name of output directory option
public static final String OPTION_OUTPUT_DIR_SHORT = "o";
// long name of output directory option
public static final String OPTION_OUTPUT_DIR_LONG = "output";
// short name of input war
public static final String OPTION_WAR_SHORT = "w";
// long name of input war
public static final String OPTION_WAR_LONG = "war";
// short name of the language option
public static final String OPTION_LANGUAGE_SHORT = "l";
// long name of the language option
public static final String OPTION_LANGUAGE_LONG = "language";
// short name of the build system option
public static final String OPTION_BUILD_SYSTEM_SHORT = "bs";
// long name of the build system option
public static final String OPTION_BUILD_SYSTEM_LONG = "build-system";
// short name of the format option
public static final String OPTION_FORMAT_SHORT = "f";
// long name of the format option
public static final String OPTION_FORMAT_LONG = "format";
public static final String OPTION_DEBUG = "debug";
// short name of the format option
public static final String OPTION_HOSTNAME_SHORT = "h";
// long name of the format option
public static final String OPTION_HOSTNAME_LONG = "hostname";
// short name of the base path option
public static final String OPTION_BASE_PATH_SHORT = "p";
// long name of the base path option
public static final String OPTION_BASE_PATH_LONG = "path";
@VisibleForTesting
static final String DEFAULT_WAR_PATH = "./war";
@VisibleForTesting
static final String DEFAULT_OUTPUT_PATH = "./";
@VisibleForTesting
static final String DEFAULT_WAR_OUTPUT_PATH_SUFFIX = "WEB-INF";
@VisibleForTesting
static final String DEFAULT_SWAGGER_OUTPUT_PATH = "./swagger.json";
@VisibleForTesting
static final String DEFAULT_CLASS_PATH = "";
@VisibleForTesting
static final String DEFAULT_LANGUAGE = "java";
@VisibleForTesting
static final String DEFAULT_BUILD_SYSTEM = null;
@VisibleForTesting
static final String DEFAULT_FORMAT = "rest";
@VisibleForTesting
static final String DEFAULT_HOSTNAME = "myapi.appspot.com";
@VisibleForTesting
static final String DEFAULT_BASE_PATH = "/_ah/api";
// boolean flag which determines whether this action needs to show up in the help message
private boolean helpDisplayNeeded = true;
private String exampleString = null;
public EndpointsToolAction(String name) {
super(name);
}
/**
* Returns a usage string for help output.
*/
public abstract String getUsageString();
/**
* Executes the command with the given arguments.
*
* @return whether or not all required arguments were provided
*/
public abstract boolean execute() throws ClassNotFoundException, IOException, ApiConfigException;
@Override
public void apply() {
// We don't handle execution here because we want to further check that all arguments are
// present in EndpointsTool beforehand.
}
@Override
protected List getHelpLines() {
ImmutableList.Builder helpLines = ImmutableList.builder();
helpLines.add(getNameString());
helpLines.add("");
helpLines.addAll(wrap(getShortDescription(), MAX_WIDTH, 0));
helpLines.add("");
helpLines.add("Usage: " + getUsageString());
if (getOptions().size() > 0) {
helpLines.add("");
helpLines.add("Options:");
for (Option option : getOptions()) {
for (String optionHelpLine : option.getHelpLines()) {
helpLines.add(Strings.repeat(" ", OPTION_INDENT) + optionHelpLine);
}
}
}
helpLines.add("");
if (!Strings.isNullOrEmpty(exampleString)) {
helpLines.add("Example:");
helpLines.add(" " + exampleString);
helpLines.add("");
}
return helpLines.build();
}
protected String getWarPath(Option warOption) {
return getOptionOrDefault(warOption, DEFAULT_WAR_PATH);
}
protected String getOutputPath(Option outputOption) {
return getOptionOrDefault(outputOption, DEFAULT_OUTPUT_PATH);
}
protected String getWarOutputPath(Option outputOption, String warPath) {
if (outputOption.getValue() != null) {
return outputOption.getValue();
}
return warPath + File.separator + DEFAULT_WAR_OUTPUT_PATH_SUFFIX;
}
protected String getSwaggerOutputPath(Option outputOption) {
return getOptionOrDefault(outputOption, DEFAULT_SWAGGER_OUTPUT_PATH);
}
protected String getClassPath(Option classPathOption) {
return getOptionOrDefault(classPathOption, DEFAULT_CLASS_PATH);
}
protected String getLanguage(Option languageOption) {
return getOptionOrDefault(languageOption, DEFAULT_LANGUAGE);
}
protected String getBuildSystem(Option buildSystemOption) {
return getOptionOrDefault(buildSystemOption, DEFAULT_BUILD_SYSTEM);
}
protected String getFormat(Option formatOption) {
return getOptionOrDefault(formatOption, DEFAULT_FORMAT);
}
protected boolean getDebug(Option debugOption) {
return debugOption.getValue() != null;
}
protected String getHostname(Option hostnameOption, String warPath) {
return getOptionOrDefault(hostnameOption, AppEngineUtil.getApplicationDefaultHostname(warPath));
}
protected String getBasePath(Option basePathOption) {
return getOptionOrDefault(basePathOption, DEFAULT_BASE_PATH);
}
protected String getOptionOrDefault(Option option, String defaultValue) {
if (option.getValue() != null) {
return option.getValue();
}
return defaultValue;
}
protected Option makeWarOption() {
return EndpointsOption.makeVisibleNonFlagOption(
OPTION_WAR_SHORT,
OPTION_WAR_LONG,
"WAR_PATH",
"Sets the path to the war directory where web-appengine.xml and other metadata are "
+ "located. Default: " + DEFAULT_WAR_PATH + ".");
}
protected Option makeOutputOption() {
return EndpointsOption.makeVisibleNonFlagOption(
OPTION_OUTPUT_DIR_SHORT,
OPTION_OUTPUT_DIR_LONG,
"OUTPUT_DIR",
"Sets the directory where the output will be written to. Default: the directory the tool "
+ "is invoked from.");
}
protected Option makeWarOutputOption() {
return EndpointsOption.makeVisibleNonFlagOption(
OPTION_OUTPUT_DIR_SHORT,
OPTION_OUTPUT_DIR_LONG,
"OUTPUT_DIR",
"Sets the directory where the output will be written to. Default: the directory the tool "
+ "is invoked from.");
}
protected Option makeSwaggerOutputOption() {
return EndpointsOption.makeVisibleNonFlagOption(
OPTION_OUTPUT_DIR_SHORT,
OPTION_OUTPUT_DIR_LONG,
"OUTPUT_FILE",
"Sets the file where output will be written to. Default: " + DEFAULT_SWAGGER_OUTPUT_PATH);
}
protected Option makeClassPathOption() {
return EndpointsOption.makeVisibleNonFlagOption(
OPTION_CLASS_PATH_SHORT,
OPTION_CLASS_PATH_LONG,
"CLASSPATH",
"Lets you specify the service class or classes from a path other than the default "
+ "/WEB-INF/libs and /WEB-INF/classes, where urls = ImmutableList.builder();
File warDir = new File(warPath).getAbsoluteFile();
File webInfDir = new File(warDir, "WEB-INF");
File classesDir = new File(webInfDir, "classes");
urls.add(classesDir.toURI().toURL());
File libDir = new File(webInfDir, "lib");
String[] files = libDir.list();
if (files != null) {
for (String file : files) {
File maybeJar = new File(libDir, file);
if (maybeJar.isFile() && file.endsWith(".jar")) {
urls.add(maybeJar.toURI().toURL());
}
}
}
if (!Strings.isNullOrEmpty(classPath)) {
for (String classPathEntry : classPath.split(File.pathSeparator)) {
urls.add(new File(classPathEntry).toURI().toURL());
}
}
return urls.build().toArray(new URL[0]);
}
protected List getServiceClassNames(String warPath) {
if (!getArgs().isEmpty()) {
return getArgs();
}
try {
File warDir = new File(warPath).getAbsoluteFile();
File webInfDir = new File(warDir, "WEB-INF");
File webXmlFile = new File(webInfDir, "web.xml");
if (webXmlFile.exists()) {
return WebXml.parse(webXmlFile).endpointsServiceClasses();
}
} catch (ParserConfigurationException | IOException | SAXException e) {
log.log(Level.WARNING, "could not parse web.xml for service classes", e);
}
return Collections.emptyList();
}
/**
* Tells whether help lines of this action needs to be displayed in the usage.
*/
public boolean isHelpDisplayNeeded() {
return helpDisplayNeeded;
}
/**
* Controls whether the help lines of the action need to be displayed in the usage.
*/
public void setHelpDisplayNeeded(boolean helpDisplayNeeded) {
this.helpDisplayNeeded = helpDisplayNeeded;
}
/**
* Return the example string which will be displayed in the usage.
*/
public String getExampleString() {
return exampleString;
}
/**
* Set the example string which will be displayed in the usage.
*/
public void setExampleString(String exampleString) {
this.exampleString = exampleString;
}
/**
* Base class for {@link Option}s used by the Cloud Endpoints tool.
*/
@VisibleForTesting
protected static class EndpointsOption extends Option {
private String description;
private String placeHolderValue;
private boolean isVisible;
/**
* Creates a new {@code EndpointsOption}.
*
* @param shortName The short name to support.
* @param longName The long name to support.
* @param isFlag true to indicate that this represents a boolean value.
* @param isVisible true to display the flag in help text.
* @param placeHolderValue The placeholder value to display in help text. May be null if isFlag
* or isVisible is false.
* @param description The description to display in help text. May be null if isVisible is
* false.
*/
private EndpointsOption(
@Nullable String shortName,
@Nullable String longName,
boolean isFlag,
boolean isVisible,
@Nullable String placeHolderValue,
@Nullable String description) {
super(shortName, longName, isFlag);
Preconditions.checkArgument(isFlag || !isVisible || placeHolderValue != null,
"non-null placeholder value required");
Preconditions.checkArgument(!isVisible || description != null,
"non-null description required");
this.description = description;
this.placeHolderValue = placeHolderValue;
this.isVisible = isVisible;
}
public static EndpointsOption makeVisibleNonFlagOption(
@Nullable String shortName,
@Nullable String longName,
@Nullable String placeHolderValue,
@Nullable String description) {
return new EndpointsOption(shortName, longName, false, true, placeHolderValue, description);
}
public static EndpointsOption makeInvisibleFlagOption(
@Nullable String shortName,
@Nullable String longName) {
return new EndpointsOption(shortName, longName, true, false, null, null) {
@Override
public void apply() {
getValues().add("true");
}
};
}
@Override
public void apply() {
}
/**
* Returns the short form of an option, e.g. '-p PORT' or null if the option defines no short
* name.
*/
public Optional getShortForm(boolean includePlaceholder) {
if (getShortName() == null) {
return Optional.absent();
}
StringBuffer form = new StringBuffer();
form.append("-" + getShortName());
if (!isFlag() && includePlaceholder) {
form.append(" " + placeHolderValue);
}
return Optional.of(form.toString());
}
/**
* Returns the long form of an option, e.g. '--port=PORT' or null if the option defines no long
* name.
*/
public Optional getLongForm(boolean includePlaceholder) {
if (getLongName() == null) {
return Optional.absent();
}
StringBuffer form = new StringBuffer();
form.append("--" + getLongName());
if (!isFlag() && includePlaceholder) {
form.append("=" + placeHolderValue);
}
return Optional.of(form.toString());
}
@Override
public List getHelpLines() {
if (!isVisible) {
return super.getHelpLines();
} else {
Optional shortForm = getShortForm(true);
Optional longForm = getLongForm(true);
StringBuffer option = new StringBuffer();
if (shortForm.isPresent()) {
option.append(shortForm.get());
if (longForm.isPresent()) {
option.append(", ");
}
}
if (longForm.isPresent()) {
option.append(longForm.get());
}
ImmutableList.Builder lines = ImmutableList.builder();
lines.add(option.toString());
lines.addAll(wrap(description, MAX_WIDTH - OPTION_INDENT, OPTION_DESCRIPTION_INDENT));
return lines.build();
}
}
}
@VisibleForTesting
static List wrap(String source, int maxWidth, int indent) {
Iterable words = Splitter.on(" ").split(source);
ImmutableList.Builder wrappedLines = ImmutableList.builder();
StringBuffer line = new StringBuffer(Strings.repeat(" ", indent));
int lineLength = indent;
for (String word : words) {
if ((lineLength + word.length()) >= maxWidth) {
wrappedLines.add(line.toString());
line = new StringBuffer(Strings.repeat(" ", indent));
lineLength = indent;
}
// If lineLength == maxWidth, then we'll definitely wrap the next word. No trailing space
// is required.
if (lineLength > indent && lineLength < maxWidth) {
line.append(" ");
lineLength++;
}
line.append(word);
lineLength += (word.length());
}
wrappedLines.add(line.toString());
return wrappedLines.build();
}
}