Adding feature selector application to help download distribution with selected featu...
authorHarman Singh <harmasin@cisco.com>
Tue, 30 Sep 2014 16:33:38 +0000 (09:33 -0700)
committerHarman Singh <harmasin@cisco.com>
Tue, 30 Sep 2014 16:33:38 +0000 (09:33 -0700)
Please check README file to understand the configuration parameters

Change-Id: I5a5b82065515ee24e46f663bfa04f0676e6fd4a9
Signed-off-by: Harman Singh <harmasin@cisco.com>
feature-selector/README.md [new file with mode: 0644]
feature-selector/pom.xml [new file with mode: 0644]
feature-selector/src/main/java/org/opendaylight/release/app/FeatureSelectorController.java [new file with mode: 0644]
feature-selector/src/main/java/org/opendaylight/release/app/FileUtil.java [new file with mode: 0644]
feature-selector/src/main/resources/config.properties [new file with mode: 0644]
feature-selector/src/main/webapp/WEB-INF/feature-selector-servlet.xml [new file with mode: 0644]
feature-selector/src/main/webapp/WEB-INF/web.xml [new file with mode: 0644]
feature-selector/src/main/webapp/download/original/distribution-dlux-0.1.0-SNAPSHOT.zip [new file with mode: 0644]
feature-selector/src/main/webapp/pages/index.jsp [new file with mode: 0644]

diff --git a/feature-selector/README.md b/feature-selector/README.md
new file mode 100644 (file)
index 0000000..e2fb643
--- /dev/null
@@ -0,0 +1,20 @@
+Feature Selector is a simple web application, that allows user to pick features from a list of available features.
+Then, add those selected features to the karaf feature config file "org.apache.karaf.features.cfg" as bootFeatures
+and return a link to download distribution that has new feature config file.
+
+config.properties file present under resources. Here are the details of config.properties -
+
+features=odl-restconf,odl-dlux-core,odl-mdsal-clustering : (Required) Comma separated list of features that you want user to select from.
+
+distribution-filename=distribution-dlux-0.1.0-SNAPSHOT.zip : (Required) File name of the distribution zip
+
+distribution-file-path=/opt/dist : (Optional) Location of the zip file, if you are not providing it within war file
+
+Release distribution zip file can be placed inside the war at location /webapp/download/original, Or
+it could be picked up from the file system path, specified by configuration property distribution-file-path.
+
+Note: Just make sure to provide either external location of distribution directory using property "distribution-file-path" or
+place zip file inside war at /webapp/download/original. One of these is required.
+
+
+
diff --git a/feature-selector/pom.xml b/feature-selector/pom.xml
new file mode 100644 (file)
index 0000000..a648661
--- /dev/null
@@ -0,0 +1,70 @@
+<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/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.opendaylight.release</groupId>
+  <artifactId>feature-selector</artifactId>
+  <packaging>war</packaging>
+  <version>0.1.0-SNAPSHOT</version>
+  <name>feature-selector Maven Webapp</name>
+  <url>http://maven.apache.org</url>
+  <properties>
+      <spring.version>3.1.3.RELEASE</spring.version>
+      <jackson.version>1.9.10</jackson.version>
+  </properties>
+  <build>
+    <finalName>feature-selector</finalName>
+      <plugins>
+          <plugin>
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-compiler-plugin</artifactId>
+              <version>3.1</version>
+              <configuration>
+                  <source>1.7</source>
+                  <target>1.7</target>
+              </configuration>
+          </plugin>
+      </plugins>
+  </build>
+
+    <dependencies>
+        <!-- Spring 3 dependencies -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>jstl</artifactId>
+            <version>1.1.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>taglibs</groupId>
+            <artifactId>standard</artifactId>
+            <version>1.1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/feature-selector/src/main/java/org/opendaylight/release/app/FeatureSelectorController.java b/feature-selector/src/main/java/org/opendaylight/release/app/FeatureSelectorController.java
new file mode 100644 (file)
index 0000000..d809116
--- /dev/null
@@ -0,0 +1,101 @@
+
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.release.app;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.ServletContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.Properties;
+
+
+@Controller
+@RequestMapping("/")
+public class FeatureSelectorController {
+
+    @Autowired
+    ServletContext context;
+    private static Properties properties = readProperties();
+    private static String[] featureList = getFeatureList(properties);
+
+
+    /**
+     * Method to list all the features on the UI
+     *
+     * @param model
+     * @return
+     */
+    @RequestMapping(value="/features", method = RequestMethod.GET)
+    public String viewFeatures(ModelMap model){
+        model.addAttribute("features", featureList);
+        return "index";
+    }
+
+    /**
+     * This method receives a list of the features in a comma separated string.
+     * It creates a new distribution zip that has those features as bootFeature in
+     * karaf config file.
+     * @param features
+     * @return
+     * @throws IOException
+     */
+    @RequestMapping(value="/selectFeatures", method = RequestMethod.POST)
+    @ResponseBody
+    public String selectFeatures(@RequestParam(value="selectedFeatures", required=false) String features) throws IOException {
+        FileUtil fileUtil = FileUtil.getInstance(properties, context);
+
+        // Copy original distribution to unique location
+        Path destFile = fileUtil.copyOriginalDistro(context);
+
+        // Update the config file at destination file
+        fileUtil.updateConfigFileWithFeatures(features, destFile);
+
+        // Return path to download new distribution
+        StringBuilder builder = new StringBuilder();
+        builder.append(context.getContextPath()).append(FileUtil.DEST_DISTRO_PATH).append(destFile.getParent().getFileName())
+            .append("/").append(destFile.getFileName());
+
+        return builder.toString();
+    }
+
+    private static Properties readProperties() {
+        Properties prop = new Properties();
+        String filename = "config.properties";
+
+        try(InputStream input = FeatureSelectorController.class.getClassLoader().getResourceAsStream(filename)) {
+            //load a properties file from class path, inside static method
+            prop.load(input);
+
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+        return prop;
+    }
+
+    private static String[] getFeatureList(Properties prop) {
+        String[] features = null;
+        String feature = prop.getProperty("features");
+        if(feature != null) {
+            features = feature.split(",");
+        }
+        return features;
+    }
+
+
+
+}
diff --git a/feature-selector/src/main/java/org/opendaylight/release/app/FileUtil.java b/feature-selector/src/main/java/org/opendaylight/release/app/FileUtil.java
new file mode 100644 (file)
index 0000000..2c40e7e
--- /dev/null
@@ -0,0 +1,163 @@
+
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.release.app;
+
+import javax.servlet.ServletContext;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+public class FileUtil {
+    private volatile static FileUtil instance;
+
+    // Path of the source distribution file
+    private Path sourcePath = null;
+
+    // content of karaf config file
+    private String featureConfigContent = null;
+
+    // config file location inside the zip file
+    private String configFileLocation = null;
+
+    public static String FEATURE_SELECTION_REPLACEMENT = "{CHANGE_ME}";
+
+    // location of all generated distributions inside tomcat
+    public static String DEST_DISTRO_PATH = "/download/generated/";
+
+    public static FileUtil getInstance(Properties properties, ServletContext context) throws IOException {
+        if (instance == null) {
+            synchronized (FileUtil.class) {
+                if (instance == null) {
+                    instance = new FileUtil(properties, context);
+                }
+            }
+        }
+        return instance;
+    }
+
+    private FileUtil(Properties properties, ServletContext context) throws IOException {
+        initPath(properties, context);
+        loadFeatureConfigFile();
+    }
+
+
+    /**
+     * Based on config param, use provided source distribution or
+     * get it from outside tomcat.
+     *
+     * @param context
+     */
+    private void initPath(Properties properties, ServletContext context) {
+        // Check if external path of file is provided or not
+        String externalFilePath = properties.getProperty("distribution-file-path");
+        String distFileName = properties.getProperty("distribution-filename");
+        if(distFileName == null || distFileName.isEmpty()) {
+            throw new IllegalStateException("Distribution file name is missing in config file.");
+        }
+        if(externalFilePath != null && !externalFilePath.isEmpty()) {
+            sourcePath = Paths.get(externalFilePath + "/" + distFileName);
+            System.out.println("picking external file");
+        } else {
+            sourcePath = Paths.get(context.getRealPath("/download/original/" + distFileName));
+        }
+
+        configFileLocation = "/" + distFileName.substring(0, distFileName.lastIndexOf("."))
+            + "/etc/org.apache.karaf.features.cfg";
+
+    }
+
+    /**
+     *
+     * Load the feature config files's content in String and keep it in memory. Add a CHANGE_ME keyword,
+     * that will be used during each new distribution creation
+     *
+     */
+
+    private void loadFeatureConfigFile() throws IOException {
+        try (FileSystem sourceFileSystem = createZipFileSystem(sourcePath)) {
+            final Path featureConfig = sourceFileSystem.getPath(configFileLocation);
+            try (BufferedReader reader = Files.newBufferedReader(featureConfig, Charset.forName("UTF-8"))) {
+                StringBuilder fileContent = new StringBuilder();
+                for (; ; ) {
+                    String line = reader.readLine();
+                    if (line == null)
+                        break;
+                    if (line.contains("featuresBoot=")) {
+                        line = line.concat(FEATURE_SELECTION_REPLACEMENT);
+                    }
+                    fileContent.append(line).append("\n");
+                }
+                featureConfigContent = fileContent.toString();
+            }
+
+        }
+    }
+
+    /**
+     * Creates a zipFile system based on the path of the zip file.
+     * It does not create a zip file, if it does not exists.
+     * @param path
+     * @return
+     * @throws IOException
+     */
+    public FileSystem createZipFileSystem(Path path) throws IOException {
+        final URI uri = URI.create("jar:file:" + path.toUri().getPath());
+
+        final Map<String, String> env = new HashMap<>();
+        env.put("create", "false");
+        return FileSystems.newFileSystem(uri, env);
+    }
+
+
+    /**
+     * Copy the Source distribution to a new location directory created
+     * based on the time stamp on every request
+     * @param context
+     * @return
+     * @throws IOException
+     */
+    public Path copyOriginalDistro(ServletContext context) throws IOException {
+        Date now = new Date();
+        String directoryName = String.valueOf(now.getTime());
+        Path destDir =  Paths.get(context.getRealPath(DEST_DISTRO_PATH + directoryName ));
+        Files.createDirectories(destDir);
+        Path destFile = destDir.resolve(sourcePath.getFileName());
+        Files.copy(sourcePath, destFile);
+        return destFile;
+    }
+
+    /**
+     * Creates a new config file with user selected features
+     * and move it inside the new destination zip file
+     */
+    public void updateConfigFileWithFeatures(String features, Path destFile) throws IOException {
+        // Create new config file
+        String fileContent = featureConfigContent.replace(FEATURE_SELECTION_REPLACEMENT, features);
+        Path newConfFile = destFile.getParent().resolve("org.apache.karaf.features.cfg");
+        Files.write(newConfFile, fileContent.getBytes());
+
+        // replace older config file with new one
+        try(FileSystem destFileSystem = createZipFileSystem(destFile)){
+            final Path oldConfFile = destFileSystem.getPath(configFileLocation);
+            Files.move(newConfFile, oldConfFile, StandardCopyOption.REPLACE_EXISTING);
+        }
+    }
+}
diff --git a/feature-selector/src/main/resources/config.properties b/feature-selector/src/main/resources/config.properties
new file mode 100644 (file)
index 0000000..50c31ed
--- /dev/null
@@ -0,0 +1,3 @@
+features=odl-restconf,odl-l2switch-switch,odl-dlux-core,odl-mdsal-clustering
+distribution-filename=distribution-dlux-0.1.0-SNAPSHOT.zip
+distribution-file-path=
\ No newline at end of file
diff --git a/feature-selector/src/main/webapp/WEB-INF/feature-selector-servlet.xml b/feature-selector/src/main/webapp/WEB-INF/feature-selector-servlet.xml
new file mode 100644 (file)
index 0000000..8ffc746
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xmlns:mvc="http://www.springframework.org/schema/mvc"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+                      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
+                      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+       <context:component-scan base-package="org.opendaylight.release.app" />
+
+    <mvc:resources mapping="/js/**" location="/js/" />
+  <mvc:resources mapping="/css/**" location="/css/" />
+  <mvc:resources mapping="/img/**" location="/img/" />
+    <mvc:resources mapping="/download/**" location="/download/" />
+
+       <mvc:annotation-driven />
+       
+       <bean
+               class="org.springframework.web.servlet.view.InternalResourceViewResolver">
+               <property name="prefix">
+                       <value>/pages/</value>
+               </property>
+               <property name="suffix">
+                       <value>.jsp</value>
+               </property>
+       </bean>
+</beans>
\ No newline at end of file
diff --git a/feature-selector/src/main/webapp/WEB-INF/web.xml b/feature-selector/src/main/webapp/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..7449f95
--- /dev/null
@@ -0,0 +1,26 @@
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+        version="3.0">
+  <display-name>Feature Selector Application</display-name>
+  
+  <servlet>
+               <servlet-name>feature-selector</servlet-name>
+               <servlet-class>
+                       org.springframework.web.servlet.DispatcherServlet
+               </servlet-class>
+               <load-on-startup>1</load-on-startup>
+       </servlet>
+       <servlet-mapping>
+               <servlet-name>feature-selector</servlet-name>
+               <url-pattern>/</url-pattern>
+       </servlet-mapping>
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>All Access</web-resource-name>
+            <url-pattern>/download/*</url-pattern>
+            <http-method>GET</http-method>
+        </web-resource-collection>
+    </security-constraint>
+</web-app>
diff --git a/feature-selector/src/main/webapp/download/original/distribution-dlux-0.1.0-SNAPSHOT.zip b/feature-selector/src/main/webapp/download/original/distribution-dlux-0.1.0-SNAPSHOT.zip
new file mode 100644 (file)
index 0000000..2e98f7f
Binary files /dev/null and b/feature-selector/src/main/webapp/download/original/distribution-dlux-0.1.0-SNAPSHOT.zip differ
diff --git a/feature-selector/src/main/webapp/pages/index.jsp b/feature-selector/src/main/webapp/pages/index.jsp
new file mode 100644 (file)
index 0000000..32dcbdd
--- /dev/null
@@ -0,0 +1,73 @@
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Feature Selector</title>
+
+    <!-- Latest compiled and minified CSS -->
+<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
+
+<!-- Optional theme -->
+<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
+<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
+    <!-- Latest compiled and minified JavaScript -->
+    <script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>  
+  </head>
+  <body>
+  <div class="container">
+
+    <div class="content">
+        <h5> Please select features that you want to be enabled at startup:</h5>
+        <form id="selectFeatures" role="form">
+        <c:forEach var="item" items="${features}">
+            <div class="form-group"><input type="checkbox" value="${item}">   ${item}</input></div>
+        </c:forEach>
+
+            <div class="form-group">
+                <button type="submit" class="btn btn-primary">Submit</button>
+                <a href="#" id="download_dist" class="btn btn-default hidden">Download</a>
+            </div>
+        </form>
+    </div>
+
+
+
+  </div>
+
+
+<script type="text/javascript">
+
+
+jQuery("form").on( "submit", function( event ) {
+         event.preventDefault();
+         jQuery("#download_dist").addClass("hidden");
+         var count = jQuery( "input:checked" ).length;
+         if(count < 1) {
+           alert("Please select a feature");
+           return;
+         }
+         var features = "";
+         for(i=0; i < count; i++) {
+             features = features + "," + jQuery( "input:checked" )[i].value;
+         }
+         var formData = '{"selectedFeatures":"' + features + '"}';
+
+         jQuery.ajax({
+                 url: "/feature-selector/selectFeatures",
+                 type: "POST",
+                 data: "selectedFeatures=" + features,
+                 success: function(data) {
+              jQuery("#download_dist").attr("href", data).removeClass("hidden");
+                         console.log("success");
+                 },
+                 error : function(){ console.log("Error");}
+         });
+
+       });
+</script>
+    
+  </body>
+</html>
\ No newline at end of file