summaryrefslogtreecommitdiffstats
path: root/framework/src/onos/tools/package/maven-plugin/src
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
commit13d05bc8458758ee39cb829098241e89616717ee (patch)
tree22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/tools/package/maven-plugin/src
parent6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff)
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/tools/package/maven-plugin/src')
-rw-r--r--framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java372
-rw-r--r--framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosCfgMojo.java141
-rw-r--r--framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosSwaggerMojo.java451
-rw-r--r--framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml22
-rw-r--r--framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/features.xml24
-rw-r--r--framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/registrator.javat31
6 files changed, 1041 insertions, 0 deletions
diff --git a/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java b/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java
new file mode 100644
index 00000000..bfc6127a
--- /dev/null
+++ b/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.onosproject.maven;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+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.project.MavenProject;
+import org.apache.maven.project.MavenProjectHelper;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import static com.google.common.io.ByteStreams.toByteArray;
+import static org.codehaus.plexus.util.FileUtils.*;
+
+/**
+ * Produces ONOS application archive using the app.xml file information.
+ */
+@Mojo(name = "app", defaultPhase = LifecyclePhase.PACKAGE)
+public class OnosAppMojo extends AbstractMojo {
+
+ private static final String APP = "app";
+ private static final String NAME = "[@name]";
+ private static final String VERSION = "[@version]";
+ private static final String FEATURES_REPO = "[@featuresRepo]";
+ private static final String ARTIFACT = "artifact";
+
+ private static final String APP_XML = "app.xml";
+ private static final String FEATURES_XML = "features.xml";
+
+ private static final String MVN_URL = "mvn:";
+ private static final String M2_PREFIX = "m2";
+
+ private static final String ONOS_APP_NAME = "onos.app.name";
+ private static final String ONOS_APP_ORIGIN = "onos.app.origin";
+
+ private static final String JAR = "jar";
+ private static final String XML = "xml";
+ private static final String APP_ZIP = "oar";
+ private static final String PACKAGE_DIR = "oar";
+
+ private static final String DEFAULT_ORIGIN = "ON.Lab";
+ private static final String DEFAULT_VERSION = "${project.version}";
+
+ private static final String DEFAULT_FEATURES_REPO =
+ "mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features";
+ private static final String DEFAULT_ARTIFACT =
+ "mvn:${project.groupId}/${project.artifactId}/${project.version}";
+
+ private static final int BUFFER_SIZE = 8192;
+
+ private String name;
+ private String origin;
+ private String version = DEFAULT_VERSION;
+ private String featuresRepo = DEFAULT_FEATURES_REPO;
+ private List<String> artifacts;
+
+ /**
+ * The project base directory.
+ */
+ @Parameter(defaultValue = "${basedir}")
+ protected File baseDir;
+
+ /**
+ * The directory where the generated catalogue file will be put.
+ */
+ @Parameter(defaultValue = "${project.build.directory}")
+ protected File dstDirectory;
+
+ /**
+ * The project group ID.
+ */
+ @Parameter(defaultValue = "${project.groupId}")
+ protected String projectGroupId;
+
+ /**
+ * The project artifact ID.
+ */
+ @Parameter(defaultValue = "${project.artifactId}")
+ protected String projectArtifactId;
+
+ /**
+ * The project version.
+ */
+ @Parameter(defaultValue = "${project.version}")
+ protected String projectVersion;
+
+ /**
+ * The project version.
+ */
+ @Parameter(defaultValue = "${project.description}")
+ protected String projectDescription;
+
+ @Parameter(defaultValue = "${localRepository}")
+ protected ArtifactRepository localRepository;
+
+ /**
+ * Maven project
+ */
+ @Parameter(defaultValue = "${project}")
+ protected MavenProject project;
+
+ /**
+ * Maven project helper.
+ */
+ @Component
+ protected MavenProjectHelper projectHelper;
+
+
+ private File m2Directory;
+ protected File stageDirectory;
+ protected String projectPath;
+
+ @Override
+ public void execute() throws MojoExecutionException {
+ File appFile = new File(baseDir, APP_XML);
+ File featuresFile = new File(baseDir, FEATURES_XML);
+
+ name = (String) project.getProperties().get(ONOS_APP_NAME);
+
+ // If neither the app.xml file exists, nor the onos.app.name property
+ // is defined, there is nothing for this Mojo to do, so bail.
+ if (!appFile.exists() && name == null) {
+ return;
+ }
+
+ m2Directory = new File(localRepository.getBasedir());
+ stageDirectory = new File(dstDirectory, PACKAGE_DIR);
+ projectPath = M2_PREFIX + "/" + artifactDir(projectGroupId, projectArtifactId, projectVersion);
+
+ origin = (String) project.getProperties().get(ONOS_APP_ORIGIN);
+ origin = origin != null ? origin : DEFAULT_ORIGIN;
+
+ if (appFile.exists()) {
+ loadAppFile(appFile);
+ } else {
+ artifacts = ImmutableList.of(eval(DEFAULT_ARTIFACT));
+ }
+
+ // If there are any artifacts, stage the
+ if (!artifacts.isEmpty()) {
+ getLog().info("Building ONOS application package for " + name + " (v" + eval(version) + ")");
+ artifacts.forEach(a -> getLog().debug("Including artifact: " + a));
+
+ if (stageDirectory.exists() || stageDirectory.mkdirs()) {
+ processAppXml(appFile);
+ processFeaturesXml(featuresFile);
+ processArtifacts();
+ generateAppPackage();
+ } else {
+ throw new MojoExecutionException("Unable to create directory: " + stageDirectory);
+ }
+ }
+ }
+
+ // Loads the app.xml file.
+ private void loadAppFile(File appFile) throws MojoExecutionException {
+ XMLConfiguration xml = new XMLConfiguration();
+ xml.setRootElementName(APP);
+
+ try (FileInputStream stream = new FileInputStream(appFile)) {
+ xml.load(stream);
+ xml.setAttributeSplittingDisabled(true);
+ xml.setDelimiterParsingDisabled(true);
+
+ name = xml.getString(NAME);
+ version = eval(xml.getString(VERSION));
+ featuresRepo = eval(xml.getString(FEATURES_REPO));
+
+ artifacts = xml.configurationsAt(ARTIFACT).stream()
+ .map(cfg -> eval(cfg.getRootNode().getValue().toString()))
+ .collect(Collectors.toList());
+
+ } catch (ConfigurationException e) {
+ throw new MojoExecutionException("Unable to parse app.xml file", e);
+ } catch (FileNotFoundException e) {
+ throw new MojoExecutionException("Unable to find app.xml file", e);
+ } catch (IOException e) {
+ throw new MojoExecutionException("Unable to read app.xml file", e);
+ }
+ }
+
+ // Processes and stages the app.xml file.
+ private void processAppXml(File appFile) throws MojoExecutionException {
+ try {
+ File file = new File(stageDirectory, APP_XML);
+ forceMkdir(stageDirectory);
+ String contents;
+
+ if (appFile.exists()) {
+ contents = fileRead(appFile);
+ } else {
+ byte[] bytes = toByteArray(getClass().getResourceAsStream(APP_XML));
+ contents = new String(bytes);
+ }
+ fileWrite(file.getAbsolutePath(), eval(contents));
+ } catch (IOException e) {
+ throw new MojoExecutionException("Unable to process app.xml", e);
+ }
+ }
+
+ private void processFeaturesXml(File featuresFile) throws MojoExecutionException {
+ boolean specified = featuresRepo != null && featuresRepo.length() > 0;
+
+ // If featuresRepo attribute is specified and there is a features.xml
+ // file present, add the features repo as an artifact
+ try {
+ if (specified && featuresFile.exists()) {
+ processFeaturesXml(new FileInputStream(featuresFile));
+ } else if (specified) {
+ processFeaturesXml(getClass().getResourceAsStream(FEATURES_XML));
+ }
+ } catch (FileNotFoundException e) {
+ throw new MojoExecutionException("Unable to find features.xml file", e);
+ } catch (IOException e) {
+ throw new MojoExecutionException("Unable to process features.xml file", e);
+ }
+ }
+
+ // Processes and stages the features.xml file.
+ private void processFeaturesXml(InputStream stream) throws IOException {
+ String featuresArtifact =
+ artifactFile(projectArtifactId, projectVersion, XML, "features");
+ File dstDir = new File(stageDirectory, projectPath);
+ forceMkdir(dstDir);
+ String s = eval(new String(toByteArray(stream)));
+ fileWrite(new File(dstDir, featuresArtifact).getAbsolutePath(), s);
+ }
+
+ // Stages all artifacts.
+ private void processArtifacts() throws MojoExecutionException {
+ for (String artifact : artifacts) {
+ processArtifact(artifact);
+ }
+ }
+
+ // Stages the specified artifact.
+ private void processArtifact(String artifact) throws MojoExecutionException {
+ if (!artifact.startsWith(MVN_URL)) {
+ throw new MojoExecutionException("Unsupported artifact URL:" + artifact);
+ }
+
+ String[] fields = artifact.substring(4).split("/");
+ if (fields.length < 3) {
+ throw new MojoExecutionException("Illegal artifact URL:" + artifact);
+ }
+
+ try {
+ String file = artifactFile(fields);
+
+ if (projectGroupId.equals(fields[0]) && projectArtifactId.equals(fields[1])) {
+ // Local artifact is not installed yet, package it from target directory.
+ File dstDir = new File(stageDirectory, projectPath);
+ forceMkdir(dstDir);
+ copyFile(new File(dstDirectory, file), new File(dstDir, file));
+ } else {
+ // Other artifacts are packaged from ~/.m2/repository directory.
+ String m2Path = artifactDir(fields);
+ File srcDir = new File(m2Directory, m2Path);
+ File dstDir = new File(stageDirectory, M2_PREFIX + "/" + m2Path);
+ forceMkdir(dstDir);
+ copyFile(new File(srcDir, file), new File(dstDir, file));
+ }
+ } catch (IOException e) {
+ throw new MojoExecutionException("Unable to stage artifact " + artifact, e);
+ }
+ }
+
+ // Generates the ONOS package ZIP file.
+ private void generateAppPackage() throws MojoExecutionException {
+ File appZip = new File(dstDirectory, artifactFile(projectArtifactId, projectVersion,
+ APP_ZIP, null));
+ try (FileOutputStream fos = new FileOutputStream(appZip);
+ ZipOutputStream zos = new ZipOutputStream(fos)) {
+ zipDirectory("", stageDirectory, zos);
+ projectHelper.attachArtifact(this.project, APP_ZIP, null, appZip);
+ } catch (IOException e) {
+ throw new MojoExecutionException("Unable to compress application package", e);
+ }
+ }
+
+ // Generates artifact directory name from the specified fields.
+ private String artifactDir(String[] fields) {
+ return artifactDir(fields[0], fields[1], fields[2]);
+ }
+
+ // Generates artifact directory name from the specified elements.
+ private String artifactDir(String gid, String aid, String version) {
+ return gid.replace('.', '/') + "/" + aid.replace('.', '/') + "/" + version;
+ }
+
+ // Generates artifact file name from the specified fields.
+ private String artifactFile(String[] fields) {
+ return fields.length < 5 ?
+ artifactFile(fields[1], fields[2],
+ (fields.length < 4 ? JAR : fields[3]), null) :
+ artifactFile(fields[1], fields[2], fields[3], fields[4]);
+ }
+
+ // Generates artifact file name from the specified elements.
+ private String artifactFile(String aid, String version, String type,
+ String classifier) {
+ return classifier == null ? aid + "-" + version + "." + type :
+ aid + "-" + version + "-" + classifier + "." + type;
+ }
+
+ // Returns the given string with project variable substitutions.
+ private String eval(String string) {
+ return string == null ? null :
+ string.replaceAll("\\$\\{onos.app.name\\}", name)
+ .replaceAll("\\$\\{onos.app.origin\\}", origin)
+ .replaceAll("\\$\\{project.groupId\\}", projectGroupId)
+ .replaceAll("\\$\\{project.artifactId\\}", projectArtifactId)
+ .replaceAll("\\$\\{project.version\\}", projectVersion)
+ .replaceAll("\\$\\{project.description\\}", projectDescription);
+ }
+
+ // Recursively archives the specified directory into a given ZIP stream.
+ private void zipDirectory(String root, File dir, ZipOutputStream zos)
+ throws IOException {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ File[] files = dir.listFiles();
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ String path = root + file.getName() + "/";
+ zos.putNextEntry(new ZipEntry(path));
+ zipDirectory(path, file, zos);
+ zos.closeEntry();
+ } else {
+ FileInputStream fin = new FileInputStream(file);
+ zos.putNextEntry(new ZipEntry(root + file.getName()));
+ int length;
+ while ((length = fin.read(buffer)) > 0) {
+ zos.write(buffer, 0, length);
+ }
+ zos.closeEntry();
+ fin.close();
+ }
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosCfgMojo.java b/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosCfgMojo.java
new file mode 100644
index 00000000..3e3e17a0
--- /dev/null
+++ b/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosCfgMojo.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.onosproject.maven;
+
+import com.thoughtworks.qdox.JavaProjectBuilder;
+import com.thoughtworks.qdox.model.JavaAnnotation;
+import com.thoughtworks.qdox.model.JavaClass;
+import com.thoughtworks.qdox.model.JavaField;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Produces ONOS component configuration catalogue resources.
+ */
+@Mojo(name = "cfg", defaultPhase = LifecyclePhase.GENERATE_RESOURCES)
+public class OnosCfgMojo extends AbstractMojo {
+
+ private static final String COMPONENT = "org.apache.felix.scr.annotations.Component";
+ private static final String PROPERTY = "org.apache.felix.scr.annotations.Property";
+ private static final String SEP = "|";
+
+ /**
+ * The directory where the generated catalogue file will be put.
+ */
+ @Parameter(defaultValue = "${basedir}")
+ protected File srcDirectory;
+
+ /**
+ * The directory where the generated catalogue file will be put.
+ */
+ @Parameter(defaultValue = "${project.build.outputDirectory}")
+ protected File dstDirectory;
+
+ @Override
+ public void execute() throws MojoExecutionException {
+ getLog().info("Generating ONOS component configuration catalogues...");
+ try {
+ JavaProjectBuilder builder = new JavaProjectBuilder();
+ builder.addSourceTree(new File(srcDirectory, "src/main/java"));
+ builder.getClasses().forEach(this::processClass);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ private void processClass(JavaClass javaClass) {
+ boolean isComponent = javaClass.getAnnotations().stream()
+ .map(ja -> ja.getType().getName().equals(COMPONENT))
+ .findFirst().isPresent();
+ if (isComponent) {
+ List<String> lines = new ArrayList<>();
+ javaClass.getFields().forEach(field -> processField(lines, javaClass, field));
+ if (!lines.isEmpty()) {
+ writeCatalog(javaClass, lines);
+ }
+ }
+ }
+
+ private void writeCatalog(JavaClass javaClass, List<String> lines) {
+ File dir = new File(dstDirectory, javaClass.getPackageName().replace('.', '/'));
+ dir.mkdirs();
+
+ File cfgDef = new File(dir, javaClass.getName().replace('.', '/') + ".cfgdef");
+ try (FileWriter fw = new FileWriter(cfgDef);
+ PrintWriter pw = new PrintWriter(fw)) {
+ pw.println("# This file is auto-generated by onos-maven-plugin");
+ lines.forEach(pw::println);
+ } catch (IOException e) {
+ System.err.println("Unable to write catalog for " + javaClass.getName());
+ e.printStackTrace();
+ }
+ }
+
+ private void processField(List<String> lines, JavaClass javaClass, JavaField field) {
+ field.getAnnotations().forEach(ja -> {
+ if (ja.getType().getName().equals(PROPERTY)) {
+ lines.add(expand(javaClass, ja.getNamedParameter("name").toString()) +
+ SEP + type(field) +
+ SEP + defaultValue(javaClass, field, ja) +
+ SEP + description(ja));
+ }
+ });
+ }
+
+ // TODO: Stuff below is very much hack-ish and should be redone; it works for now though.
+
+ private String description(JavaAnnotation annotation) {
+ String description = (String) annotation.getNamedParameter("label");
+ return description.replaceAll("\" \\+ \"", "")
+ .replaceFirst("^[^\"]*\"", "").replaceFirst("\"$", "");
+ }
+
+ private String type(JavaField field) {
+ String ft = field.getType().getName().toUpperCase();
+ return ft.equals("INT") ? "INTEGER" : ft;
+ }
+
+ private String defaultValue(JavaClass javaClass, JavaField field,
+ JavaAnnotation annotation) {
+ String ft = field.getType().getName().toLowerCase();
+ String defValueName = ft.equals("boolean") ? "boolValue" :
+ ft.equals("string") ? "value" : ft + "Value";
+ Object dv = annotation.getNamedParameter(defValueName);
+ return dv == null ? "" : expand(javaClass, dv.toString());
+ }
+
+ private String stripQuotes(String string) {
+ return string.trim().replaceFirst("^[^\"]*\"", "").replaceFirst("\"$", "");
+ }
+
+ private String expand(JavaClass javaClass, String value) {
+ JavaField field = javaClass.getFieldByName(value);
+ return field == null ? stripQuotes(value) :
+ stripQuotes(field.getCodeBlock().replaceFirst(".*=", "").replaceFirst(";$", ""));
+ }
+
+}
diff --git a/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosSwaggerMojo.java b/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosSwaggerMojo.java
new file mode 100644
index 00000000..ea847459
--- /dev/null
+++ b/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosSwaggerMojo.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.onosproject.maven;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+import com.thoughtworks.qdox.JavaProjectBuilder;
+import com.thoughtworks.qdox.model.DocletTag;
+import com.thoughtworks.qdox.model.JavaAnnotation;
+import com.thoughtworks.qdox.model.JavaClass;
+import com.thoughtworks.qdox.model.JavaMethod;
+import com.thoughtworks.qdox.model.JavaParameter;
+import com.thoughtworks.qdox.model.JavaType;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+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.project.MavenProject;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ * Produces ONOS Swagger api-doc.
+ */
+@Mojo(name = "swagger", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
+public class OnosSwaggerMojo extends AbstractMojo {
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private static final String JSON_FILE = "swagger.json";
+ private static final String GEN_SRC = "generated-sources";
+ private static final String REG_SRC = "registrator.javat";
+
+ private static final String PATH = "javax.ws.rs.Path";
+ private static final String PATH_PARAM = "javax.ws.rs.PathParam";
+ private static final String QUERY_PARAM = "javax.ws.rs.QueryParam";
+ private static final String POST = "javax.ws.rs.POST";
+ private static final String GET = "javax.ws.rs.GET";
+ private static final String PUT = "javax.ws.rs.PUT";
+ private static final String DELETE = "javax.ws.rs.DELETE";
+ private static final String PRODUCES = "javax.ws.rs.Produces";
+ private static final String CONSUMES = "javax.ws.rs.Consumes";
+ private static final String JSON = "MediaType.APPLICATION_JSON";
+
+ /**
+ * The directory where the generated catalogue file will be put.
+ */
+ @Parameter(defaultValue = "${basedir}")
+ protected File srcDirectory;
+
+ /**
+ * The directory where the generated catalogue file will be put.
+ */
+ @Parameter(defaultValue = "${project.build.directory}")
+ protected File dstDirectory;
+
+ /**
+ * REST API web-context
+ */
+ @Parameter(defaultValue = "${web.context}")
+ protected String webContext;
+
+ /**
+ * REST API version
+ */
+ @Parameter(defaultValue = "${api.version}")
+ protected String apiVersion;
+
+ /**
+ * REST API description
+ */
+ @Parameter(defaultValue = "${api.description}")
+ protected String apiDescription;
+
+ /**
+ * REST API title
+ */
+ @Parameter(defaultValue = "${api.title}")
+ protected String apiTitle;
+
+ /**
+ * REST API title
+ */
+ @Parameter(defaultValue = "${api.package}")
+ protected String apiPackage;
+
+ /**
+ * Maven project
+ */
+ @Parameter(defaultValue = "${project}")
+ protected MavenProject project;
+
+
+ @Override
+ public void execute() throws MojoExecutionException {
+ try {
+ JavaProjectBuilder builder = new JavaProjectBuilder();
+ builder.addSourceTree(new File(srcDirectory, "src/main/java"));
+
+ ObjectNode root = initializeRoot();
+ ArrayNode tags = mapper.createArrayNode();
+ ObjectNode paths = mapper.createObjectNode();
+
+ root.set("tags", tags);
+ root.set("paths", paths);
+
+ builder.getClasses().forEach(jc -> processClass(jc, paths, tags));
+
+ if (paths.size() > 0) {
+ getLog().info("Generating ONOS REST API documentation...");
+ genCatalog(root);
+
+ if (!isNullOrEmpty(apiPackage)) {
+ genRegistrator();
+ }
+ }
+
+ project.addCompileSourceRoot(new File(dstDirectory, GEN_SRC).getPath());
+
+ } catch (Exception e) {
+ getLog().warn("Unable to generate ONOS REST API documentation", e);
+ throw e;
+ }
+ }
+
+ // initializes top level root with Swagger required specifications
+ private ObjectNode initializeRoot() {
+ ObjectNode root = mapper.createObjectNode();
+ root.put("swagger", "2.0");
+ ObjectNode info = mapper.createObjectNode();
+ root.set("info", info);
+
+ root.put("basePath", webContext);
+ info.put("version", apiVersion);
+ info.put("title", apiTitle);
+ info.put("description", apiDescription);
+
+ ArrayNode produces = mapper.createArrayNode();
+ produces.add("application/json");
+ root.set("produces", produces);
+
+ ArrayNode consumes = mapper.createArrayNode();
+ consumes.add("application/json");
+ root.set("consumes", consumes);
+
+ return root;
+ }
+
+ // Checks whether javaClass has a path tag associated with it and if it does
+ // processes its methods and creates a tag for the class on the root
+ void processClass(JavaClass javaClass, ObjectNode paths, ArrayNode tags) {
+ // If the class does not have a Path tag then ignore it
+ JavaAnnotation annotation = getPathAnnotation(javaClass);
+ if (annotation == null) {
+ return;
+ }
+
+ String path = getPath(annotation);
+ if (path == null) {
+ return;
+ }
+
+ String resourcePath = "/" + path;
+ String tagPath = path.isEmpty() ? "/" : path;
+
+ // Create tag node for this class.
+ ObjectNode tagObject = mapper.createObjectNode();
+ tagObject.put("name", tagPath);
+ if (javaClass.getComment() != null) {
+ tagObject.put("description", shortText(javaClass.getComment()));
+ }
+ tags.add(tagObject);
+
+ // Create an array node add to all methods from this class.
+ ArrayNode tagArray = mapper.createArrayNode();
+ tagArray.add(tagPath);
+
+ processAllMethods(javaClass, resourcePath, paths, tagArray);
+ }
+
+ private JavaAnnotation getPathAnnotation(JavaClass javaClass) {
+ Optional<JavaAnnotation> optional = javaClass.getAnnotations()
+ .stream().filter(a -> a.getType().getName().equals(PATH)).findAny();
+ return optional.isPresent() ? optional.get() : null;
+ }
+
+ // Checks whether a class's methods are REST methods and then places all the
+ // methods under a specific path into the paths node
+ private void processAllMethods(JavaClass javaClass, String resourcePath,
+ ObjectNode paths, ArrayNode tagArray) {
+ // map of the path to its methods represented by an ObjectNode
+ Map<String, ObjectNode> pathMap = new HashMap<>();
+
+ javaClass.getMethods().forEach(javaMethod -> {
+ javaMethod.getAnnotations().forEach(annotation -> {
+ String name = annotation.getType().getName();
+ if (name.equals(POST) || name.equals(GET) || name.equals(DELETE) || name.equals(PUT)) {
+ // substring(12) removes "javax.ws.rs."
+ String method = annotation.getType().toString().substring(12).toLowerCase();
+ processRestMethod(javaMethod, method, pathMap, resourcePath, tagArray);
+ }
+ });
+ });
+
+ // for each path add its methods to the path node
+ for (Map.Entry<String, ObjectNode> entry : pathMap.entrySet()) {
+ paths.set(entry.getKey(), entry.getValue());
+ }
+
+
+ }
+
+ private void processRestMethod(JavaMethod javaMethod, String method,
+ Map<String, ObjectNode> pathMap,
+ String resourcePath, ArrayNode tagArray) {
+ String fullPath = resourcePath, consumes = "", produces = "",
+ comment = javaMethod.getComment();
+ for (JavaAnnotation annotation : javaMethod.getAnnotations()) {
+ String name = annotation.getType().getName();
+ if (name.equals(PATH)) {
+ fullPath = resourcePath + "/" + getPath(annotation);
+ fullPath = fullPath.replaceFirst("^//", "/");
+ }
+ if (name.equals(CONSUMES)) {
+ consumes = getIOType(annotation);
+ }
+ if (name.equals(PRODUCES)) {
+ produces = getIOType(annotation);
+ }
+ }
+ ObjectNode methodNode = mapper.createObjectNode();
+ methodNode.set("tags", tagArray);
+
+ addSummaryDescriptions(methodNode, comment);
+ processParameters(javaMethod, methodNode);
+
+ processConsumesProduces(methodNode, "consumes", consumes);
+ processConsumesProduces(methodNode, "produces", produces);
+
+ addResponses(methodNode);
+
+ ObjectNode operations = pathMap.get(fullPath);
+ if (operations == null) {
+ operations = mapper.createObjectNode();
+ operations.set(method, methodNode);
+ pathMap.put(fullPath, operations);
+ } else {
+ operations.set(method, methodNode);
+ }
+ }
+
+ private void processConsumesProduces(ObjectNode methodNode, String type, String io) {
+ if (!io.equals("")) {
+ ArrayNode array = mapper.createArrayNode();
+ methodNode.set(type, array);
+ array.add(io);
+ }
+ }
+
+ private void addSummaryDescriptions(ObjectNode methodNode, String comment) {
+ String summary = "", description;
+ if (comment != null) {
+ if (comment.contains(".")) {
+ int periodIndex = comment.indexOf(".");
+ summary = comment.substring(0, periodIndex);
+ description = comment.length() > periodIndex + 1 ?
+ comment.substring(periodIndex + 1).trim() : "";
+ } else {
+ description = comment;
+ }
+ methodNode.put("summary", summary);
+ methodNode.put("description", description);
+ }
+ }
+
+ // Temporary solution to add responses to a method
+ // TODO Provide annotations in the web resources for responses and parse them
+ private void addResponses(ObjectNode methodNode) {
+ ObjectNode responses = mapper.createObjectNode();
+ methodNode.set("responses", responses);
+
+ ObjectNode success = mapper.createObjectNode();
+ success.put("description", "successful operation");
+ responses.set("200", success);
+
+ ObjectNode defaultObj = mapper.createObjectNode();
+ defaultObj.put("description", "Unexpected error");
+ responses.set("default", defaultObj);
+ }
+
+ // Checks if the annotations has a value of JSON and returns the string
+ // that Swagger requires
+ private String getIOType(JavaAnnotation annotation) {
+ if (annotation.getNamedParameter("value").toString().equals(JSON)) {
+ return "application/json";
+ }
+ return "";
+ }
+
+ // If the annotation has a Path tag, returns the value with leading and
+ // trailing double quotes and slash removed.
+ private String getPath(JavaAnnotation annotation) {
+ String path = annotation.getNamedParameter("value").toString();
+ return path == null ? null : path.replaceAll("(^[\\\"/]*|[/\\\"]*$)", "");
+ }
+
+ // Processes parameters of javaMethod and enters the proper key-values into the methodNode
+ private void processParameters(JavaMethod javaMethod, ObjectNode methodNode) {
+ ArrayNode parameters = mapper.createArrayNode();
+ methodNode.set("parameters", parameters);
+ boolean required = true;
+
+ for (JavaParameter javaParameter : javaMethod.getParameters()) {
+ ObjectNode individualParameterNode = mapper.createObjectNode();
+ Optional<JavaAnnotation> optional = javaParameter.getAnnotations().stream().filter(
+ annotation -> annotation.getType().getName().equals(PATH_PARAM) ||
+ annotation.getType().getName().equals(QUERY_PARAM)).findAny();
+ JavaAnnotation pathType = optional.isPresent() ? optional.get() : null;
+
+ String annotationName = javaParameter.getName();
+
+
+ if (pathType != null) { //the parameter is a path or query parameter
+ individualParameterNode.put("name",
+ pathType.getNamedParameter("value").toString().replace("\"", ""));
+ if (pathType.getType().getName().equals(PATH_PARAM)) {
+ individualParameterNode.put("in", "path");
+ } else if (pathType.getType().getName().equals(QUERY_PARAM)) {
+ individualParameterNode.put("in", "query");
+ }
+ individualParameterNode.put("type", getType(javaParameter.getType()));
+ } else { // the parameter is a body parameter
+ individualParameterNode.put("name", annotationName);
+ individualParameterNode.put("in", "body");
+
+ // TODO add actual hardcoded schemas and a type
+ // body parameters must have a schema associated with them
+ ArrayNode schema = mapper.createArrayNode();
+ individualParameterNode.set("schema", schema);
+ }
+ for (DocletTag p : javaMethod.getTagsByName("param")) {
+ if (p.getValue().contains(annotationName)) {
+ try {
+ String description = p.getValue().split(" ", 2)[1].trim();
+ if (description.contains("optional")) {
+ required = false;
+ }
+ individualParameterNode.put("description", description);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ individualParameterNode.put("required", required);
+ parameters.add(individualParameterNode);
+ }
+ }
+
+ // Returns the Swagger specified strings for the type of a parameter
+ private String getType(JavaType javaType) {
+ String type = javaType.getFullyQualifiedName();
+ String value;
+ if (type.equals(String.class.getName())) {
+ value = "string";
+ } else if (type.equals("int")) {
+ value = "integer";
+ } else if (type.equals(boolean.class.getName())) {
+ value = "boolean";
+ } else if (type.equals(long.class.getName())) {
+ value = "number";
+ } else {
+ value = "";
+ }
+ return value;
+ }
+
+ // Writes the swagger.json file using the supplied JSON root.
+ private void genCatalog(ObjectNode root) {
+ File swaggerCfg = new File(dstDirectory, JSON_FILE);
+ if (dstDirectory.exists() || dstDirectory.mkdirs()) {
+ try (FileWriter fw = new FileWriter(swaggerCfg);
+ PrintWriter pw = new PrintWriter(fw)) {
+ pw.println(root.toString());
+ } catch (IOException e) {
+ getLog().warn("Unable to write " + JSON_FILE);
+ }
+ } else {
+ getLog().warn("Unable to create " + dstDirectory);
+ }
+ }
+
+ // Generates the registrator Java component.
+ private void genRegistrator() {
+ File dir = new File(dstDirectory, GEN_SRC);
+ File reg = new File(dir, apiPackage.replaceAll("\\.", "/") + "/ApiDocRegistrator.java");
+ File pkg = reg.getParentFile();
+ if (pkg.exists() || pkg.mkdirs()) {
+ try {
+ String src = new String(ByteStreams.toByteArray(getClass().getResourceAsStream(REG_SRC)));
+ src = src.replace("${api.package}", apiPackage)
+ .replace("${web.context}", webContext)
+ .replace("${api.title}", apiTitle)
+ .replace("${api.description}", apiTitle);
+ Files.write(src.getBytes(), reg);
+ } catch (IOException e) {
+ getLog().warn("Unable to write " + reg);
+ }
+ } else {
+ getLog().warn("Unable to create " + reg);
+ }
+ }
+
+ // Returns "nickname" based on method and path for a REST method
+ private String setNickname(String method, String path) {
+ if (!path.equals("")) {
+ return (method + path.replace('/', '_').replace("{", "").replace("}", "")).toLowerCase();
+ } else {
+ return method.toLowerCase();
+ }
+ }
+
+ private String shortText(String comment) {
+ int i = comment.indexOf('.');
+ return i > 0 ? comment.substring(0, i) : comment;
+ }
+
+}
diff --git a/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml b/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml
new file mode 100644
index 00000000..0f3133d3
--- /dev/null
+++ b/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015 Open Networking Laboratory
+ ~
+ ~ 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.
+ -->
+<app name="${onos.app.name}" origin="${onos.app.origin}" version="${project.version}"
+ featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
+ features="${project.artifactId}">
+ <description>${project.description}</description>
+ <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact>
+</app>
diff --git a/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/features.xml b/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/features.xml
new file mode 100644
index 00000000..d5a91e2a
--- /dev/null
+++ b/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/features.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+ ~ Copyright 2015 Open Networking Laboratory
+ ~
+ ~ 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.
+ -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+ <repository>mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features</repository>
+ <feature name="${project.artifactId}" version="${project.version}"
+ description="${project.description}">
+ <feature>onos-api</feature>
+ <bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
+ </feature>
+</features>
diff --git a/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/registrator.javat b/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/registrator.javat
new file mode 100644
index 00000000..a8e26536
--- /dev/null
+++ b/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/registrator.javat
@@ -0,0 +1,31 @@
+/*
+ * Auto-generated by OnosSwaggerMojo.
+ *
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 ${api.package};
+
+import org.apache.felix.scr.annotations.Component;
+import org.onosproject.rest.AbstractApiDocRegistrator;
+import org.onosproject.rest.ApiDocProvider;
+
+@Component(immediate = true)
+public class ApiDocRegistrator extends AbstractApiDocRegistrator {
+ public ApiDocRegistrator() {
+ super(new ApiDocProvider("${web.context}",
+ "${api.title}",
+ ApiDocRegistrator.class.getClassLoader()));
+ }
+}